Overview
The checkout flow takes a customer from their cart through to a placed order. This typically involves collecting billing and shipping addresses, selecting a shipping method, reviewing the order, and processing payment. This guide walks through building a checkout using Lunar’sLunar\Core\Models\Cart model, Lunar\Core\Facades\CartSession facade, and Lunar\Core\Facades\Payments facade.
The examples below use standard Laravel controllers and Blade templates. The same concepts apply whether the storefront is built with Livewire, Inertia, or a headless API.
Checkout Flow
A typical checkout follows these steps:- Collect addresses — billing address (always required) and shipping address (required for shippable carts)
- Select shipping — choose a shipping method from the available options
- Review order — show the customer a summary before they commit
- Process payment — authorize the payment and place the order
Collecting Addresses
Before an order can be created, the cart needs at least a billing address. If the cart contains shippable items, a shipping address is also required.Setting the Billing Address
Setting the Shipping Address
Lunar\Core\Contracts\Addressable interface. When called, any existing address of that type on the cart is replaced.
Setting a new shipping address clears any tax zone override on the cart by default, so tax recalculates against the new address. Pass
setShippingAddress($address, clearTaxZone: false) to keep an existing override.Required Address Fields
When creating an order, Lunar validates that the following fields are present on the billing address (and shipping address, if applicable):Using the Same Address for Both
A common pattern is to let the customer check a box to copy their billing address to the shipping address:Populating the Country Select
Lunar ships with aCountry model that can be used to populate a country dropdown:
Shipping Options
Once a shipping address is set, the available shipping methods can be retrieved using theShippingManifest facade. Shipping options are configured by extending Lunar’s shipping system (see Extending Shipping).
Fetching Available Options
Lunar\Core\DataTypes\ShippingOption objects. Each option has the following properties:
Setting the Shipping Option
After the customer selects a shipping method, apply it to the cart. Use theShippingManifest facade to retrieve the full ShippingOption object by its identifier:
setShippingOption() recalculates the cart by default. After it completes, the shippingSubTotal, shippingTaxTotal, and shippingTotal properties on the cart reflect the selected shipping option.Order Review
Before placing the order, display a summary so the customer can verify their selections. At this point the cart has addresses, a shipping option (if applicable), and calculated totals.Creating the Order
Once the customer is ready to proceed, create the order from the cart. This validates the cart and runs it through the configured creation pipeline, which copies the cart’s data (lines, addresses, totals) onto a newLunar\Core\Models\Order and returns it as a draft (with placed_at set to null).
CartSession::createOrder() removes the cart from the session by default. To keep the cart in the session (for example, to allow the customer to return to their cart if payment fails), pass false: CartSession::createOrder(forget: false).What Happens During Order Creation
WhencreateOrder() is called, Lunar runs through the following steps:
- Recalculates the cart to ensure totals are up to date
- Validates the cart (billing address, shipping address, and shipping option, among other checks)
- Runs the order through the
lunar.orders.pipelines.creationpipeline, which by default: fills the order with the cart’s financial data, currency, and channel; creates the order lines; creates the order addresses; creates a shipping line (if a shipping option is set); and maps the cart’s discount breakdown onto the order
config/lunar/orders.php.
Handling Validation Errors
If validation fails, aLunar\Core\Exceptions\Carts\CartException is thrown. The exception contains a MessageBag accessible via the errors() method, and its getMessage() returns a human-readable summary.
MessageBag:
Checking if the Cart Can Create an Order
To check whether the cart is ready without throwing exceptions, usecanCreateOrder():
Processing Payment
After the order is created, process the payment using thePayments facade. Lunar uses a driver-based approach built on the Lunar\Core\Contracts\PaymentType contract, so the same API works regardless of the payment provider.
authorize() method returns a Lunar\Core\DataObjects\PaymentAuthorize object with the following properties:
For a full walkthrough of driver selection, the offline driver, and the first-party Stripe and PayPal drivers, see the Payment Integration guide.
Order Confirmation
After a successful payment, redirect the customer to a confirmation page. The order is now placed and contains all the data needed for a receipt. Order money fields (sub_total, discount_total, shipping_total, tax_total, total) are stored as plain integers in the currency’s minor unit. Format them with the model’s format() method rather than treating them as objects.
Cart Fingerprinting
Lunar provides a fingerprint mechanism to detect when cart contents change between checkout steps. This is useful for verifying that the cart has not been modified (in another tab, for example) between the time the customer reviewed their order and the time they submitted payment.Routes
Putting It All Together
Here is a complete controller covering the checkout flow:Next Steps
- Review the Payment Integration guide for driver selection, the Stripe and PayPal drivers, and post-payment order state.
- Review the Extending Shipping guide for adding custom shipping options.
- Review the Extending Payments guide for building a custom payment driver.
- Review the Extending Orders guide for customizing the order creation pipeline.
- Review the Cart guide for building the cart page that precedes checkout.