Skip to main content
Custom shipping options are added to checkout by registering shipping modifiers against the ShippingManifest.

Overview

When a cart contains an item that needs shipping, the storefront typically needs to display a list of shipping options for the customer to choose from. Lunar does not assume how those options are calculated, whether that is a flat rate, a rate per shipping zone, a live carrier quote, or a mix of all three. Instead, a shipping modifier is registered that inspects the cart and adds one or more ShippingOption instances to the ShippingManifest. The manifest is resolved for a cart with:
For a complete, real-world shipping modifier, see the Table Rate Shipping add-on. It resolves shipping zones, rates, and drivers, and is a good reference to work from instead of starting a modifier from scratch.

How the manifest works

Lunar\Core\Manifests\ShippingManifest implements Lunar\Core\Contracts\ShippingManifest and is bound in the container as a scoped instance, resolvable through the Lunar\Core\Facades\ShippingManifest facade. It holds a collection of ShippingOption instances and exposes: Calling getOptions() sends the cart through every registered shipping modifier using Laravel’s Illuminate\Pipeline\Pipeline. Each modifier is expected to inspect the cart, call addOption() or addOptions() on the manifest as needed, and pass the cart to the next modifier in the pipeline.
Shipping option identifier values must be unique across every registered modifier. addOption() silently drops an option whose identifier already exists on the manifest, so pick identifiers that will not collide with another modifier a consumer might install (such as the Table Rate Shipping add-on).
A modifier is free to trigger a cart recalculation while it runs, which can call back into getOptions() for the same cart. The manifest guards against this: while it is already resolving options for a cart, a nested call returns whatever has been resolved so far instead of running the pipeline again. This is internal behavior a modifier does not need to manage, but it explains why a modifier should not assume getOptions() always runs the full pipeline from scratch.

Adding a shipping modifier

A shipping modifier extends Lunar\Core\Modifiers\ShippingModifier and implements handle(Cart $cart, Closure $next):
ShippingOption implements Lunar\Core\Contracts\Purchasable, so it participates in cart totals the same way a product or an order line does. Its constructor accepts: Register the modifier from a service provider’s boot() method:

Carriers and fulfilment methods

Shipping modifiers control what options a customer sees at checkout, before an order exists. Once an order is placed, fulfilling it involves a separate pair of extension points: Lunar\Core\Contracts\CarrierManifest (which carrier a shipment ships with, e.g. for generating a label or tracking reference) and Lunar\Core\Contracts\FulfilmentMethodManifest (which fulfilment workflow and states an order line moves through). These are registered independently of shipping modifiers and are documented in full at Fulfilments.