From cc2070908dc15e2b73572d17e92ede4c3fd48323 Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Wed, 25 Feb 2026 12:29:33 +0000 Subject: [PATCH] Update orders.mdx --- 1.x/reference/orders.mdx | 433 ++++++++++++++++++++++----------------- 1 file changed, 248 insertions(+), 185 deletions(-) diff --git a/1.x/reference/orders.mdx b/1.x/reference/orders.mdx index 61d99fe..99f2759 100644 --- a/1.x/reference/orders.mdx +++ b/1.x/reference/orders.mdx @@ -3,102 +3,131 @@ title: "Orders" --- ## Overview -As you'd expect, orders on an online system show what users have purchased. Orders are linked to Carts and although you -would generally only have one Order per cart, the system will support multiple if your store requires it. +Orders represent completed or in-progress purchases in a store. Orders are linked to carts, and although there is generally only one order per cart, the system supports multiple orders per cart if needed. + + +All monetary values (such as `sub_total`, `total`, `tax_total`) are cast to `Lunar\DataTypes\Price` objects, providing access to `value`, `formatted()`, and `decimal` properties. + ```php Lunar\Models\Order ``` -| Field | Description | -|:----------------------|:-------------------------------------------------------------------------------------------------------------------| -| id | | -| user_id | If this is not a guest order, this will have the users id | -| customer_id | Can be `null`, stores customer | -| cart_id | The related cart | -| channel_id | Which channel this was purchased through | -| status | A status that makes sense to you as the store owner | -| reference | Your store's own reference -| customer_reference | If you want customers to add their own reference, it goes here. -| sub_total | The sub total minus any discounts, excl. tax -| discount_breakdown | A json field for the discount breakdown e.g. `[{"discount_id": 1, "lines": [{"id": 1, "qty": 1}]], "total": 200}]` -| discount_total | Any discount amount excl. tax -| shipping_breakdown| A json field for the shipping breakdown e.g. `[{"name": "Standard Delivery", "identifier": "STD", "price": 123}]` -| shipping_total | The shipping total with tax -| tax_breakdown | A json field for the tax breakdown e.g. `[{"description": "VAT", "identifier" : "vat", "value": 123, "percentage": 20, "currency_code": "GBP"}]` -| tax_total | The total amount of tax applied -| total | The grand total with tax -| notes | Any additional order notes -| currency_code | The code of the currency the order was placed in -| compare_currency_code | The code of the default currency at the time -| exchange_rate | The exchange rate between `currency_code` and `compare_currency_code` -| placed_at | The datetime the order was considered placed. -| meta | Any additional meta info you wish to store -| created_at | | -| updated_at | | - -## Create an order - -You can either create an order directly, or the recommended way is via a `Cart` model. +### Fields + +| Field | Type | Description | +|:------|:-----|:------------| +| id | `id` | Primary key | +| customer_id | `foreignId` `nullable` | | +| user_id | `foreignId` `nullable` | The authenticated user who placed the order | +| channel_id | `foreignId` | The channel the order was placed through | +| new_customer | `boolean` | Whether the customer is a first-time buyer | +| cart_id | `foreignId` `nullable` | The cart used to create the order | +| status | `string` | The order status | +| reference | `string` `nullable` | The generated order reference | +| customer_reference | `string` `nullable` | A reference provided by the customer | +| sub_total | `unsignedBigInteger` | The subtotal minus any discounts, excluding tax | +| discount_total | `unsignedBigInteger` | The discount amount, excluding tax | +| discount_breakdown | `json` `nullable` | Breakdown of applied discounts | +| shipping_breakdown | `json` `nullable` | Breakdown of shipping charges | +| shipping_total | `unsignedBigInteger` | The shipping total including tax | +| tax_breakdown | `json` | Breakdown of applied taxes | +| tax_total | `unsignedBigInteger` | The total amount of tax applied | +| total | `unsignedBigInteger` | The grand total including tax | +| notes | `text` `nullable` | Additional order notes | +| currency_code | `string` | The currency code the order was placed in | +| compare_currency_code | `string` `nullable` | The default currency code at the time of the order | +| exchange_rate | `decimal` | The exchange rate between `currency_code` and `compare_currency_code` | +| placed_at | `dateTime` `nullable` | The datetime the order was considered placed | +| fingerprint | `string` `nullable` | A hash used to detect cart changes | +| meta | `json` `nullable` | Custom metadata | +| created_at | `timestamp` | | +| updated_at | `timestamp` | | + +### Relationships + +| Relationship | Type | Related Model | Description | +|:-------------|:-----|:--------------|:------------| +| `channel` | BelongsTo | `Lunar\Models\Channel` | | +| `cart` | BelongsTo | `Lunar\Models\Cart` | | +| `currency` | BelongsTo | `Lunar\Models\Currency` | Matched on `currency_code` | +| `customer` | BelongsTo | `Lunar\Models\Customer` | | +| `user` | BelongsTo | User | The authenticatable model from auth config | +| `lines` | HasMany | `Lunar\Models\OrderLine` | | +| `physicalLines` | HasMany | `Lunar\Models\OrderLine` | Lines where type is `physical` | +| `digitalLines` | HasMany | `Lunar\Models\OrderLine` | Lines where type is `digital` | +| `shippingLines` | HasMany | `Lunar\Models\OrderLine` | Lines where type is `shipping` | +| `productLines` | HasMany | `Lunar\Models\OrderLine` | All lines excluding shipping | +| `addresses` | HasMany | `Lunar\Models\OrderAddress` | | +| `shippingAddress` | HasOne | `Lunar\Models\OrderAddress` | Address where type is `shipping` | +| `billingAddress` | HasOne | `Lunar\Models\OrderAddress` | Address where type is `billing` | +| `transactions` | HasMany | `Lunar\Models\Transaction` | | +| `captures` | HasMany | `Lunar\Models\Transaction` | Transactions where type is `capture` | +| `intents` | HasMany | `Lunar\Models\Transaction` | Transactions where type is `intent` | +| `refunds` | HasMany | `Lunar\Models\Transaction` | Transactions where type is `refund` | + +## Creating an Order + +An order can be created directly or, the recommended approach, via a `Lunar\Models\Cart` model. ```php -$order = \Lunar\Models\Order::create([/** .. */]); +use Lunar\Models\Order; +use Lunar\Models\Cart; + +$order = Order::create([/** .. */]); -// Recommended way +// Recommended approach $order = Cart::first()->createOrder( allowMultipleOrders: false, orderIdToUpdate: null, ); ``` -- `allowMultipleOrders` - Generally carts will only have one draft order associated, however if you want to allow carts to - have multiple, you can pass `true` here. -- `orderIdToUpdate` - You can optionally pass the ID of an order to update instead of attempting to create a new order, this must be a draft order i.e. a null `placed_at` and related to the cart. +- `allowMultipleOrders` - Carts generally only have one draft order associated. Pass `true` to allow multiple orders per cart. +- `orderIdToUpdate` - Optionally pass the ID of an existing draft order to update instead of creating a new one. The order must have a null `placed_at` value and belong to the cart. -The underlying class for creating an order is `Lunar\Actions\Carts\CreateOrder`, you are free to override this in the -config file `config/cart.php` +The underlying class for creating an order is `Lunar\Actions\Carts\CreateOrder`. This can be overridden in `config/lunar/cart.php`: ```php return [ // ... 'actions' => [ // ... - 'order_create' => CustomCreateOrder::class + 'order_create' => CustomCreateOrder::class, ] -] +]; ``` -At minimum your class should look like the following: +At minimum, a custom class should extend `Lunar\Actions\AbstractAction`: ```php -final class CreateOrder extends Lunar\Actions\AbstractAction +use Lunar\Actions\AbstractAction; +use Lunar\Models\Cart; + +final class CreateOrder extends AbstractAction { - /** - * Execute the action. - */ public function execute( Cart $cart, bool $allowMultipleOrders = false, int $orderIdToUpdate = null ): self { + // ... return $this; } } ``` -### Validating a cart before creation. +### Validating a Cart Before Creation -If you also want to check before you attempt this if the cart is ready to create an order, you can call the helper -method: +To check whether a cart is ready to create an order: ```php $cart->canCreateOrder(); ``` -Under the hood this will use the `ValidateCartForOrderCreation` class which lunar provides and throw any validation -exceptions with helpful messages if the cart isn't ready to create an order. +This uses the `Lunar\Validation\Cart\ValidateCartForOrderCreation` class, which throws validation exceptions with helpful messages if the cart is not ready. -You can specify you're own class to handle this in `config/cart.php`. +A custom validation class can be specified in `config/lunar/cart.php`: ```php return [ @@ -106,17 +135,15 @@ return [ 'validators' => [ 'order_create' => MyCustomValidator::class, ] -] +]; ``` -Which may look something like: +A custom validator should extend `Lunar\Validation\BaseValidator`: ```php -isDraft(); +$order->isPlaced(); +``` ## Order Lines @@ -191,45 +219,53 @@ You can find out more in the Extending Lunar section for [Order Modifiers](/1.x/ Lunar\Models\OrderLine ``` -| Field | Description | -|:-----------------|:-------------------------------------------------------------------------------------------------------------------------------------------------| -| id | | -| order_id | | -| purchasable_type | Morph reference for the purchasable item e.g. `product_variant` | -| purchasable_id | -| type | Whether `digital`,`physical` etc -| description | A description of the line item -| option | If this was a variant, the option info is here -| identifier | Something to identify the purchasable item, usually an `sku` -| unit_price | The unit price of the line -| unit_quantity | The line unit quantity, usually this is 1 -| quantity | The amount of this item purchased -| sub_total | The sub total minus any discounts, excl. tax -| discount_total | Any discount amount excl. tax -| tax_breakdown | A json field for the tax breakdown e.g. `[{"description": "VAT", "identifier" : "vat", "value": 123, "percentage": 20, "currency_code": "GBP"}]` -| tax_total | The total amount of tax applied -| total | The grand total with tax -| notes | Any additional order notes -| meta | Any additional meta info you wish to store -| created_at | | -| updated_at | | - - -### Create an order line +### Fields + +| Field | Type | Description | +|:------|:-----|:------------| +| id | `id` | Primary key | +| order_id | `foreignId` | | +| purchasable_type | `string` | Polymorphic type for the purchasable item | +| purchasable_id | `unsignedBigInteger` | Polymorphic ID for the purchasable item | +| type | `string` | The line type, e.g. `physical`, `digital`, `shipping` | +| description | `string` | A description of the line item | +| option | `string` `nullable` | Option information if the item is a variant | +| identifier | `string` | An identifier for the purchasable item, typically a SKU | +| unit_price | `unsignedBigInteger` | The unit price of the line | +| unit_quantity | `unsignedInteger` | The unit quantity, typically `1` | +| quantity | `unsignedInteger` | The quantity purchased | +| sub_total | `unsignedBigInteger` | The subtotal minus any discounts, excluding tax | +| discount_total | `unsignedBigInteger` | The discount amount, excluding tax | +| tax_breakdown | `json` | Breakdown of applied taxes | +| tax_total | `unsignedBigInteger` | The total amount of tax applied | +| total | `unsignedBigInteger` | The grand total including tax | +| notes | `text` `nullable` | Additional line notes | +| meta | `json` `nullable` | Custom metadata | +| created_at | `timestamp` | | +| updated_at | `timestamp` | | + +### Relationships + +| Relationship | Type | Related Model | Description | +|:-------------|:-----|:--------------|:------------| +| `order` | BelongsTo | `Lunar\Models\Order` | | +| `purchasable` | MorphTo | — | The polymorphic purchasable item | +| `currency` | HasOneThrough | `Lunar\Models\Currency` | Resolved through the order | + +### Creating an Order Line -If you are using the `createOrder` method on a cart, this is all handled for you automatically. +When using the `createOrder` method on a cart, order lines are created automatically. ```php -\Lunar\Models\OrderLine::create([ +use Lunar\Models\OrderLine; + +OrderLine::create([ // ... ]); -``` - -Or via the relationship -```php +// Or via the relationship $order->lines()->create([ // ... ]); @@ -237,14 +273,56 @@ $order->lines()->create([ ## Order Addresses -An order can have many addresses, typically you would just have one for billing and one for shipping. +An order can have many addresses, typically one for billing and one for shipping. + +```php +Lunar\Models\OrderAddress +``` -If you are using the `createOrder` method on a cart, this is all handled for you automatically. +When using the `createOrder` method on a cart, order addresses are created automatically. +### Fields + +| Field | Type | Description | +|:------|:-----|:------------| +| id | `id` | Primary key | +| order_id | `foreignId` | | +| country_id | `foreignId` `nullable` | | +| title | `string` `nullable` | | +| first_name | `string` `nullable` | | +| last_name | `string` `nullable` | | +| company_name | `string` `nullable` | | +| tax_identifier | `string` `nullable` | A tax identification number | +| line_one | `string` `nullable` | | +| line_two | `string` `nullable` | | +| line_three | `string` `nullable` | | +| city | `string` `nullable` | | +| state | `string` `nullable` | | +| postcode | `string` `nullable` | | +| delivery_instructions | `string` `nullable` | | +| contact_email | `string` `nullable` | | +| contact_phone | `string` `nullable` | | +| type | `string` | The address type: `billing` or `shipping` | +| shipping_option | `string` `nullable` | A unique identifier for the selected shipping option | +| meta | `json` `nullable` | Custom metadata | +| created_at | `timestamp` | | +| updated_at | `timestamp` | | + +### Relationships + +| Relationship | Type | Related Model | Description | +|:-------------|:-----|:--------------|:------------| +| `order` | BelongsTo | `Lunar\Models\Order` | | +| `country` | BelongsTo | `Lunar\Models\Country` | | + +### Creating an Order Address + ```php -\Lunar\Models\OrderAddress::create([ +use Lunar\Models\OrderAddress; + +OrderAddress::create([ 'order_id' => 1, 'country_id' => 1, 'title' => null, @@ -260,17 +338,17 @@ If you are using the `createOrder` method on a cart, this is all handled for you 'delivery_instructions' => null, 'contact_email' => null, 'contact_phone' => null, - 'type' => 'shipping', // billing/shipping - 'shipping_option' => null, // A unique code for you to identify shipping + 'type' => 'shipping', + 'shipping_option' => null, ]); -// Or via the relationship. +// Or via the relationship $order->addresses()->create([ // ... ]); ``` -You can then use some relationship helpers to fetch the address you need: +The shipping and billing addresses can be accessed directly: ```php $order->shippingAddress; @@ -280,23 +358,22 @@ $order->billingAddress; ## Shipping Options -A Shipping Tables addon is planned to make setting up shipping in the admin hub easy for most scenarios. +A Shipping Tables add-on is planned to simplify shipping configuration in the admin panel. -To add Shipping Options you will need to [extend Lunar](/1.x/extending/shipping) to add in your own logic. +To add shipping options, [extend Lunar](/1.x/extending/shipping) with custom logic. -Then in your checkout, or where ever you want, you can fetch these options: +Shipping options can be fetched using the `ShippingManifest` facade: ```php \Lunar\Facades\ShippingManifest::getOptions(\Lunar\Models\Cart $cart); ``` -This will return a collection of `Lunar\DataTypes\ShippingOption` objects. +This returns a collection of `Lunar\DataTypes\ShippingOption` objects. -### Adding the shipping option to the cart +### Adding a Shipping Option to the Cart -Once the user has selected the shipping option they want, you will need to add this to the cart so it can calculate the -new totals. +Once a shipping option has been selected, add it to the cart so totals can be recalculated: ```php $cart->setShippingOption(\Lunar\DataTypes\ShippingOption $option); @@ -308,34 +385,46 @@ $cart->setShippingOption(\Lunar\DataTypes\ShippingOption $option); Lunar\Models\Transaction ``` -| Field | Description | -|:-----------|:----------------------------------------------------------------------------------------------| -| id | | -| success | Whether the transaction was successful | -| refund | `true` if this was a refund | -| driver | The payment driver used e.g. `stripe` | -| amount | An integer amount | -| reference | The reference returned from the payment Provider. Used to identify the transaction with them. -| status | A string representation of the status, unlinked to Lunar e.g. `settled` | -| notes | Any relevant notes for the transaction -| card_type | e.g. `visa` -| last_four | Last 4 digits of the card -| meta | Any additional meta info you wish to store -| created_at | | -| updated_at | | - -### Create a transaction +### Fields + +| Field | Type | Description | +|:------|:-----|:------------| +| id | `id` | Primary key | +| parent_transaction_id | `foreignId` `nullable` | A reference to a parent transaction | +| order_id | `foreignId` | | +| success | `boolean` | Whether the transaction was successful | +| type | `enum` | The transaction type: `capture`, `intent`, or `refund` | +| driver | `string` | The payment driver used, e.g. `stripe` | +| amount | `unsignedBigInteger` | The transaction amount | +| reference | `string` | The reference returned from the payment provider | +| status | `string` | The transaction status, e.g. `settled` | +| notes | `string` `nullable` | Any relevant notes | +| card_type | `string` | The card type, e.g. `visa` | +| last_four | `string` `nullable` | The last four digits of the card | +| meta | `json` `nullable` | Custom metadata | +| created_at | `timestamp` | | +| updated_at | `timestamp` | | +| deleted_at | `timestamp` `nullable` | | + +### Relationships + +| Relationship | Type | Related Model | Description | +|:-------------|:-----|:--------------|:------------| +| `order` | BelongsTo | `Lunar\Models\Order` | | +| `currency` | HasOneThrough | `Lunar\Models\Currency` | Resolved through the order | + +### Creating a Transaction -Just because an order has a transaction does not mean it has been placed. Lunar determines whether an order is -considered placed when the `placed_at` column has a datetime, regardless if any transactions exist or not. +An order having transactions does not mean it has been placed. Lunar determines whether an order is placed based on whether the `placed_at` column has a datetime value, regardless of any transactions. -Most stores will likely want to store a transaction against the order, this helps determining how much has been paid, -how it was paid and give a clue on the best way to issue a refund if needed. +Most stores will want to store transactions against orders to track how much has been paid, the payment method used, and how to issue refunds if needed. ```php -\Lunar\Models\Transaction::create([ +use Lunar\Models\Transaction; + +Transaction::create([ //... ]); @@ -345,46 +434,27 @@ $order->transactions()->create([ ]); ``` -These can then be returned via the relationship. +Transactions can be retrieved via relationships: ```php -$order->transactions; // Get all transactions. - -$order->charges; // Get all transactions that are charges. - -$order->refunds; // Get all transactions that are refunds. +$order->transactions; // All transactions +$order->captures; // Capture transactions +$order->intents; // Intent transactions +$order->refunds; // Refund transactions ``` ## Payments -We will be looking to add support for the most popular payment providers, so keep an eye out here as we will list them -all out. - -In the meantime, you can absolutely still get a storefront working, at the end of the day Lunar doesn't really mind what payment provider you use or plan to use. +Lunar is payment-provider agnostic. Any payment provider can be integrated with a storefront. -In terms of an order, all it's worried about is whether or not the `placed_at` column is populated on the orders table, -the rest is completely up to you how you want to handle that. We have some helper utilities to make such things easier -for you as laid out above. - -And as always, if you have any questions you can reach out on our Discord! - -## Order Status - -The `placed_at` field determines whether an Order is considered draft or placed. The Order model has two helper methods -to determine the status of an Order. - -```php -$order->isDraft(); -$order->isPlaced(); -``` +The key factor for an order is whether the `placed_at` column is populated. Everything else about payment handling is left to the store implementation. Lunar provides helper utilities (as described above) to manage the payment lifecycle. ## Order Notifications -Lunar allows you to specify what Laravel mailers/notifications should be available for sending when you update an -order's status. These are configured in the `lunar/orders` config file and are defined like so: +Lunar allows specifying which Laravel mailers and notifications should be available when updating an order's status. These are configured in `config/lunar/orders.php`: ```php -'statuses' => [ +'statuses' => [ 'awaiting-payment' => [ 'label' => 'Awaiting Payment', 'color' => '#848a8c', @@ -398,24 +468,19 @@ order's status. These are configured in the `lunar/orders` config file and are d ], ``` -Now when you update an order's status in the hub, you will have these mailers available if the new status -is `awaiting-payment`. You can then choose the email addresses which the email should be sent to and also add an -additional email address if required. +When updating an order's status in the admin panel, any configured mailers for the new status are available to select. Email addresses can be chosen, and additional addresses can be added. -Once updated, Lunar will keep a render of the email sent out in the activity log so you have a clear history of what's -been sent out. +Lunar stores a render of the sent email in the activity log, providing a clear history of communications. -These email notifications do not get sent out automatically if you update the status outside of the hub. +These email notifications are not sent automatically when updating the status programmatically outside of the admin panel. -### Mailer template +### Mailer Template -When building out the template for your mailer, you should assume you have access to the `$order` model. When the status -is updated this is passed through to the view data for the mailer, along with any additional content entered. -Since you may not always have additional content when sending out the mailer, you should check the existence first. +When building a mailer template, the `$order` model is available in the view data. When the status is updated, the order is passed through along with any additional content entered. Since additional content may not always be present, check for its existence first. -Here's an example of what the template could look like: +Example template: ```blade

It's on the way!

@@ -436,12 +501,10 @@ Here's an example of what the template could look like: ## Order Invoice PDF -By default when you click "Download PDF" in the admin panel when viewing an order, you will get a basic PDF generated -for you to download. You can publish the view that powers this to create your own PDF template. +By default, clicking "Download PDF" in the admin panel when viewing an order generates a basic PDF. The view powering this PDF can be published for customization: ```bash php artisan vendor:publish --tag=lunarpanel.pdf ``` -This will create a view called `resources/vendor/lunarpanel/pdf/order.blade.php`, where you will be able to freely -customise the PDF you want displayed on download. +This creates a view at `resources/vendor/lunarpanel/pdf/order.blade.php` that can be freely customized.