> ## Documentation Index
> Fetch the complete documentation index at: https://docs.laravelshopper.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Orders

> Complete reference for the Order model, items, addresses, shipping, refunds, and order management in Shopper.

Orders represent customer purchases and are central to your e-commerce operations. Shopper provides a comprehensive order management system with support for items, addresses, shipping, refunds, and status tracking.

## Models

Shopper uses seven models to manage orders:

| Model                                    | Purpose                                                            |
| ---------------------------------------- | ------------------------------------------------------------------ |
| `Shopper\Core\Models\Order`              | The main order with statuses, totals, and foreign keys             |
| `Shopper\Core\Models\OrderItem`          | Products in the order with quantity, price, and fulfillment status |
| `Shopper\Core\Models\OrderAddress`       | Billing and shipping addresses snapshot                            |
| `Shopper\Core\Models\OrderShipping`      | Shipments with carrier, tracking, and delivery dates               |
| `Shopper\Core\Models\OrderShippingEvent` | Tracking events for each shipment (location, status changes)       |
| `Shopper\Core\Models\OrderTaxLine`       | Tax breakdown per item or shipment                                 |
| `Shopper\Core\Models\OrderRefund`        | Refund requests with status tracking                               |

The Order model implements `Shopper\Core\Models\Contracts\Order` and uses `SoftDeletes`. It is configurable via `config/shopper/models.php`.

### Extending the Model

To add custom behavior, extend the model and update your configuration:

```php theme={null}
namespace App\Models;

use Shopper\Core\Models\Order as BaseOrder;

class Order extends BaseOrder
{
}
```

Update `config/shopper/models.php`:

```php theme={null}
return [
    'order' => \App\Models\Order::class,
];
```

## Database Schema

### Order Table

| Column                    | Type         | Nullable | Default     | Description                                                                 |
| ------------------------- | ------------ | -------- | ----------- | --------------------------------------------------------------------------- |
| `id`                      | bigint       | no       | auto        | Primary key                                                                 |
| `number`                  | string(32)   | no       | -           | Order number                                                                |
| `price_amount`            | integer      | yes      | null        | Order total in cents                                                        |
| `tax_amount`              | integer      | yes      | null        | Total tax in cents                                                          |
| `status`                  | string(32)   | no       | new         | Order status enum                                                           |
| `payment_status`          | string(32)   | no       | pending     | Payment status enum                                                         |
| `shipping_status`         | string(32)   | no       | unfulfilled | Shipping status enum                                                        |
| `currency_code`           | string       | no       | -           | Currency code (USD, EUR, etc.)                                              |
| `notes`                   | text         | yes      | null        | Order notes                                                                 |
| `parent_order_id`         | bigint       | yes      | null        | FK to parent order                                                          |
| `payment_method_id`       | bigint       | yes      | null        | FK to payment method                                                        |
| `channel_id`              | bigint       | yes      | null        | FK to channel                                                               |
| `customer_id`             | bigint       | yes      | null        | FK to user                                                                  |
| `zone_id`                 | bigint       | yes      | null        | FK to zone                                                                  |
| `billing_address_id`      | bigint       | yes      | null        | FK to order address                                                         |
| `shipping_address_id`     | bigint       | yes      | null        | FK to order address                                                         |
| `shipping_option_id`      | bigint       | yes      | null        | FK to carrier option                                                        |
| `discount_id`             | bigint       | yes      | null        | FK to the discount that was redeemed (nullOnDelete)                         |
| `discount_code`           | string       | yes      | null        | Snapshot of the discount code at placement                                  |
| `discount_type`           | string(32)   | yes      | null        | Snapshot of the discount type (`percentage`/`fixed_amount`)                 |
| `discount_value_at_apply` | unsigned int | yes      | null        | Snapshot of the discount value (cents for fixed amount, raw for percentage) |
| `discount_currency_code`  | char(3)      | yes      | null        | Currency the discount was applied in                                        |
| `cancelled_at`            | timestamp    | yes      | null        | Cancellation date                                                           |
| `archived_at`             | timestamp    | yes      | null        | Archive date                                                                |
| `deleted_at`              | timestamp    | yes      | null        | Soft delete timestamp                                                       |
| `created_at`              | timestamp    | yes      | null        | Creation timestamp                                                          |
| `updated_at`              | timestamp    | yes      | null        | Last update timestamp                                                       |

### OrderItem Table

| Column               | Type    | Nullable | Description                      |
| -------------------- | ------- | -------- | -------------------------------- |
| `id`                 | bigint  | no       | Primary key                      |
| `name`               | string  | yes      | Product name at time of purchase |
| `sku`                | string  | yes      | Product SKU at time of purchase  |
| `quantity`           | integer | no       | Quantity ordered                 |
| `unit_price_amount`  | integer | no       | Unit price in cents              |
| `tax_amount`         | integer | no       | Tax amount in cents              |
| `discount_amount`    | integer | no       | Discount amount in cents         |
| `product_id`         | bigint  | no       | Polymorphic relation ID          |
| `product_type`       | string  | no       | Polymorphic relation type        |
| `order_id`           | bigint  | no       | FK to order                      |
| `order_shipping_id`  | bigint  | yes      | FK to order\_shipping (shipment) |
| `fulfillment_status` | string  | yes      | Fulfillment status enum value    |

### OrderAddress Table

| Column                | Type   | Nullable | Description             |
| --------------------- | ------ | -------- | ----------------------- |
| `id`                  | bigint | no       | Primary key             |
| `customer_id`         | bigint | no       | FK to user              |
| `first_name`          | string | no       | First name              |
| `last_name`           | string | no       | Last name               |
| `company`             | string | yes      | Company name            |
| `street_address`      | string | no       | Street address          |
| `street_address_plus` | string | yes      | Additional address line |
| `postal_code`         | string | no       | Postal/ZIP code         |
| `city`                | string | no       | City                    |
| `state`               | string | yes      | State/province/region   |
| `phone`               | string | yes      | Phone number            |
| `country_name`        | string | yes      | Country name            |

### OrderShipping Table

| Column            | Type       | Nullable | Description             |
| ----------------- | ---------- | -------- | ----------------------- |
| `id`              | bigint     | no       | Primary key             |
| `status`          | string(32) | yes      | Shipment status enum    |
| `shipped_at`      | datetime   | yes      | Shipment date           |
| `received_at`     | datetime   | yes      | Delivery date           |
| `returned_at`     | datetime   | yes      | Return date             |
| `tracking_number` | string     | yes      | Carrier tracking number |
| `tracking_url`    | string     | yes      | Tracking URL            |
| `voucher`         | json       | yes      | Shipping voucher data   |
| `order_id`        | bigint     | no       | FK to order             |
| `carrier_id`      | bigint     | yes      | FK to carrier           |

### OrderRefund Table

| Column          | Type    | Nullable | Description                  |
| --------------- | ------- | -------- | ---------------------------- |
| `id`            | bigint  | no       | Primary key                  |
| `refund_reason` | string  | yes      | Refund reason                |
| `refund_amount` | integer | yes      | Refund amount (in cents)     |
| `status`        | string  | no       | Refund status enum           |
| `currency`      | string  | no       | Currency code                |
| `notes`         | string  | yes      | Admin notes                  |
| `order_id`      | bigint  | no       | FK to order                  |
| `user_id`       | bigint  | yes      | FK to user processing refund |

## Order Status

Every order tracks three independent statuses: order status, payment status, and shipping status. This separation allows each aspect of the order to progress independently. For example, an order can be paid before it's shipped, or partially shipped while still processing.

### Order Status

The `OrderStatus` enum tracks the overall lifecycle of the order:

```php theme={null}
use Shopper\Core\Enum\OrderStatus;

OrderStatus::New         // new (default)
OrderStatus::Processing  // processing
OrderStatus::Completed   // completed
OrderStatus::Cancelled   // cancelled
OrderStatus::Archived    // archived
```

| Status     | Database Value | Color   | Description                   |
| ---------- | -------------- | ------- | ----------------------------- |
| New        | `new`          | info    | Newly placed order            |
| Processing | `processing`   | primary | Order is being prepared       |
| Completed  | `completed`    | teal    | Order fulfilled and delivered |
| Cancelled  | `cancelled`    | danger  | Order cancelled               |
| Archived   | `archived`     | gray    | Removed from active list      |

### Payment Status

The `PaymentStatus` enum tracks whether payment has been collected. It is updated automatically by the [`PaymentProcessingService`](/v2/payments#processing-payments) as transactions succeed.

```php theme={null}
use Shopper\Core\Enum\PaymentStatus;

PaymentStatus::Pending             // pending (default)
PaymentStatus::Authorized          // authorized
PaymentStatus::Paid                // paid
PaymentStatus::PartiallyRefunded   // partially_refunded
PaymentStatus::Refunded            // refunded
PaymentStatus::Voided              // voided
```

| Status             | Database Value       | Color   | Description                  |
| ------------------ | -------------------- | ------- | ---------------------------- |
| Pending            | `pending`            | warning | Awaiting payment             |
| Authorized         | `authorized`         | info    | Funds held, not yet captured |
| Paid               | `paid`               | success | Funds collected              |
| Partially Refunded | `partially_refunded` | orange  | Some amount refunded         |
| Refunded           | `refunded`           | gray    | Full amount refunded         |
| Voided             | `voided`             | danger  | Cancelled before capture     |

<Note>
  For the full payment lifecycle, capture strategies, and transaction recording, see the [Payments](/v2/payments) page.
</Note>

### Shipping Status

The `ShippingStatus` enum tracks the overall shipping state at the order level. It reflects the combined state of all shipments on the order.

```php theme={null}
use Shopper\Core\Enum\ShippingStatus;

ShippingStatus::Unfulfilled        // unfulfilled (default)
ShippingStatus::PartiallyShipped   // partially_shipped
ShippingStatus::Shipped            // shipped
ShippingStatus::PartiallyDelivered // partially_delivered
ShippingStatus::Delivered          // delivered
ShippingStatus::PartiallyReturned  // partially_returned
ShippingStatus::Returned           // returned
```

| Status              | Database Value        | Color   | Description             |
| ------------------- | --------------------- | ------- | ----------------------- |
| Unfulfilled         | `unfulfilled`         | warning | No items shipped yet    |
| Partially Shipped   | `partially_shipped`   | info    | Some items shipped      |
| Shipped             | `shipped`             | indigo  | All items shipped       |
| Partially Delivered | `partially_delivered` | primary | Some packages delivered |
| Delivered           | `delivered`           | success | All packages delivered  |
| Partially Returned  | `partially_returned`  | orange  | Some items returned     |
| Returned            | `returned`            | gray    | All items returned      |

<Note>
  For shipment-level tracking (individual packages with carriers and tracking numbers), see the [Fulfillment](/v2/fulfillment) page.
</Note>

### Status Check Methods

```php theme={null}
$order->isNew();               // true if status is New
$order->isProcessing();        // true if status is Processing
$order->isCompleted();         // true if status is Completed
$order->isArchived();          // true if status is Archived
$order->isNotCancelled();      // true if status is NOT Cancelled
$order->canBeCancelled();      // true if not Cancelled/Archived and shipping is Unfulfilled

$order->isPaid();              // true if payment_status is Paid
$order->isPaymentPending();    // true if payment_status is Pending
$order->isPaymentAuthorized(); // true if payment_status is Authorized
$order->isRefunded();          // true if payment_status is Refunded

$order->isShipped();           // true if shipping_status is Shipped
$order->isShippingPending();   // true if shipping_status is Unfulfilled
```

## Fulfillment Status

The `FulfillmentStatus` enum tracks individual item fulfillment, enabling partial shipments and multi-carrier delivery:

```php theme={null}
use Shopper\Core\Enum\FulfillmentStatus;

FulfillmentStatus::Pending              // pending
FulfillmentStatus::ForwardedToSupplier  // forwarded_to_supplier (dropshipping)
FulfillmentStatus::Processing           // processing
FulfillmentStatus::Shipped              // shipped
FulfillmentStatus::Delivered            // delivered
FulfillmentStatus::Cancelled            // cancelled
```

| Status                | Database Value          | Color   | Description                              |
| --------------------- | ----------------------- | ------- | ---------------------------------------- |
| Pending               | `pending`               | warning | Awaiting processing                      |
| Forwarded to Supplier | `forwarded_to_supplier` | info    | Sent to external supplier (dropshipping) |
| Processing            | `processing`            | primary | Being prepared                           |
| Shipped               | `shipped`               | indigo  | In transit                               |
| Delivered             | `delivered`             | success | Received by customer                     |
| Cancelled             | `cancelled`             | danger  | Fulfillment cancelled                    |

<Note>
  `FulfillmentStatus` is applied per **item**, not per order. This allows partial fulfillment when items ship at different times or from different carriers. See the [Fulfillment](/v2/fulfillment) page for complete workflows.
</Note>

## Refund Status

The `OrderRefundStatus` enum defines refund states:

```php theme={null}
use Shopper\Core\Enum\OrderRefundStatus;

OrderRefundStatus::Pending        // pending (default)
OrderRefundStatus::Awaiting       // awaiting
OrderRefundStatus::Treatment      // treatment
OrderRefundStatus::Partial_Refund // partial_refund
OrderRefundStatus::Refunded       // refunded
OrderRefundStatus::Rejected       // rejected
OrderRefundStatus::Cancelled      // cancelled
```

## Relationships

### Customer

```php theme={null}
$order->customer;

Order::query()
    ->where('customer_id', $userId)
    ->get();
```

### Items

```php theme={null}
$order->items;
```

To add an item to an order, retrieve the product price for the order's currency:

```php theme={null}
$price = $product->getPrice('USD');

$order->items()->create([
    'name' => $product->name,
    'sku' => $product->sku,
    'quantity' => 2,
    'unit_price_amount' => $price->amount,
    'product_id' => $product->id,
    'product_type' => $product->getMorphClass(),
]);
```

### Addresses

```php theme={null}
$order->shippingAddress;
$order->billingAddress;
$order->shippingAddress->full_name;
```

### Shipping

```php theme={null}
$order->shippingOption;
$order->shippings;
```

To create a shipment and link items:

```php theme={null}
$shipment = $order->shippings()->create([
    'carrier_id' => $carrierId,
    'shipped_at' => now(),
    'tracking_number' => 'TRACK123456',
    'tracking_url' => 'https://carrier.com/track/TRACK123456',
]);

$order->items()
    ->whereIn('id', [$itemId1, $itemId2])
    ->update([
        'order_shipping_id' => $shipment->id,
        'fulfillment_status' => FulfillmentStatus::Shipped,
    ]);
```

### Item Shipment

```php theme={null}
$item->shipment;
$item->shipment?->carrier;
$shipment->items;
```

<Note>
  For complete multi-shipment and dropshipping workflows, see the [Fulfillment](/v2/fulfillment) page.
</Note>

### Refund

```php theme={null}
$order->refund;
```

To create a refund:

```php theme={null}
$order->refund()->create([
    'refund_reason' => 'Product damaged',
    'refund_amount' => $order->total(),
    'currency' => $order->currency_code,
    'status' => OrderRefundStatus::Pending,
]);
```

### Channel and Zone

```php theme={null}
$order->channel;
$order->zone;
```

### Discount

Orders snapshot the discount that was redeemed at placement time. The `discount_id` foreign key still resolves the original `Discount` model while it exists, but the four snapshot columns (`discount_code`, `discount_type`, `discount_value_at_apply`, `discount_currency_code`) preserve the data even after the originating discount is edited or deleted. This follows the Shopify pattern of immutable order records.

```php theme={null}
$order->discount;
$order->discount_code;
$order->discount_type;
$order->discount_value_at_apply;
$order->discount_currency_code;
```

To find every order that redeemed a given discount, use the same query Shopper uses internally to enforce the per-user usage limit:

```php theme={null}
use Shopper\Models\Order;

Order::query()
    ->where('discount_id', $discount->id)
    ->where('customer_id', $customerId)
    ->exists();
```

<Note>
  For the full validation and atomic reservation rules, see [Usage Limit Enforcement](/v2/discounts#usage-limit-enforcement) on the Discounts page.
</Note>

### Parent/Child Orders

For split orders, an order can have a parent and children:

```php theme={null}
$order->parent;
$order->children;
```

## Computed Values

### Order Total

The `total()` method returns the sum of all item totals in cents:

```php theme={null}
$order->total();
```

## Events

Order lifecycle events are dispatched automatically throughout the order lifecycle:

```php theme={null}
use Shopper\Core\Events\Orders\OrderCreated;
use Shopper\Core\Events\Orders\OrderPaid;
use Shopper\Core\Events\Orders\OrderShipped;
use Shopper\Core\Events\Orders\OrderCompleted;
use Shopper\Core\Events\Orders\OrderCancelled;
use Shopper\Core\Events\Orders\OrderArchived;
use Shopper\Core\Events\Orders\OrderDeleted;
use Shopper\Core\Events\Orders\OrderShipmentCreated;
use Shopper\Core\Events\Orders\OrderShipmentDelivered;
use Shopper\Core\Events\Orders\OrderNoteAdded;
```

For the full events reference including shipment events, payload details, and usage examples, see the [Events](/v2/events#order-events) page.

## Creating Orders

### Basic Order

```php theme={null}
use Shopper\Core\Models\Order;
use Shopper\Core\Enum\OrderStatus;

$order = Order::query()->create([
    'number' => generate_number(),
    'customer_id' => auth()->id(),
    'currency_code' => current_currency(),
    'channel_id' => $channelId,
    'status' => OrderStatus::New,
]);
```

### Complete Order with Items and Addresses

Create the shipping and billing addresses:

```php theme={null}
$shippingAddress = OrderAddress::query()->create([
    'customer_id' => auth()->id(),
    'first_name' => 'John',
    'last_name' => 'Doe',
    'street_address' => '123 Main St',
    'city' => 'New York',
    'postal_code' => '10001',
    'country_name' => 'United States',
    'phone' => '+1234567890',
]);

$billingAddress = OrderAddress::query()->create([
    'customer_id' => auth()->id(),
    'first_name' => 'John',
    'last_name' => 'Doe',
    'street_address' => '123 Main St',
    'city' => 'New York',
    'postal_code' => '10001',
    'country_name' => 'United States',
]);

$order = Order::query()->create([
    'number' => generate_number(),
    'customer_id' => auth()->id(),
    'currency_code' => 'USD',
    'shipping_address_id' => $shippingAddress->id,
    'billing_address_id' => $billingAddress->id,
    'shipping_option_id' => $carrierOptionId,
    'zone_id' => $zoneId,
]);

foreach ($cart->items as $cartItem) {
    $price = $cartItem->product->getPrice($order->currency_code);

    $order->items()->create([
        'name' => $cartItem->product->name,
        'sku' => $cartItem->product->sku,
        'quantity' => $cartItem->quantity,
        'unit_price_amount' => $price->amount,
        'product_id' => $cartItem->product->id,
        'product_type' => $cartItem->product->getMorphClass(),
    ]);
}
```

## Query Scopes

The Order model provides two scopes for filtering by archive status:

```php theme={null}
Order::query()->archived()->get();     // Only archived orders
Order::query()->notArchived()->get();  // Exclude archived orders
```

## Retrieving Orders

To get all non-archived orders with their relationships:

```php theme={null}
use Shopper\Core\Models\Order;

$orders = Order::query()
    ->with(['customer', 'items', 'shippingAddress'])
    ->latest()
    ->get();

$newOrders = Order::query()
    ->where('status', OrderStatus::New)
    ->get();
```

Query orders by customer with pagination:

```php theme={null}
$customerOrders = Order::query()
    ->where('customer_id', $customerId)
    ->with('items.product')
    ->latest()
    ->paginate(10);
```

Find an order by number:

```php theme={null}
$order = Order::query()
    ->where('number', $orderNumber)
    ->with(['items', 'shippingAddress', 'billingAddress', 'refund'])
    ->firstOrFail();
```

## Order Number Generation

Use the `generate_number()` helper to create unique order numbers:

```php theme={null}
$orderNumber = generate_number();
```

This returns a formatted string like `ORD-20260327-001234`. The format is configurable in `config/shopper/orders.php`.

## Permissions

The admin panel generates five permissions for order management:

| Permission      | Description                |
| --------------- | -------------------------- |
| `browse_orders` | View the orders list       |
| `read_orders`   | View a single order detail |
| `add_orders`    | Create new orders          |
| `edit_orders`   | Edit existing orders       |
| `delete_orders` | Delete orders              |

## Components

To customize the admin UI for order management:

```bash theme={null}
php artisan shopper:component:publish order
```

Creates `config/shopper/components/order.php`:

```php theme={null}
use Shopper\Livewire;

return [
    'pages' => [
        'order-index' => Livewire\Pages\Order\Index::class,
        'order-detail' => Livewire\Pages\Order\Detail::class,
        'order-shipments' => Livewire\Pages\Order\Shipments::class,
        'order-abandoned-carts' => Livewire\Pages\Order\AbandonedCarts::class,
    ],
    'components' => [
        'order-customer' => Livewire\Components\Orders\OrderCustomer::class,
        'order-fulfillment' => Livewire\Components\Orders\Fulfillment::class,
        'order-items' => Livewire\Components\Orders\OrderItems::class,
        'order-notes' => Livewire\Components\Orders\OrderNotes::class,
        'order-summary' => Livewire\Components\Orders\OrderSummary::class,

        'slide-overs.create-shipping-label' => Livewire\SlideOvers\CreateShippingLabel::class,
        'slide-overs.shipment-add-event' => Livewire\SlideOvers\ShipmentAddEvent::class,
        'slide-overs.shipment-detail' => Livewire\SlideOvers\ShipmentDetail::class,
        'slide-overs.abandoned-cart-detail' => Livewire\SlideOvers\AbandonedCartDetail::class,
    ],
];
```

## Storefront Example

In most cases, orders are created automatically when a [cart is converted](/v2/cart#converting-to-order) via `CreateOrderFromCartAction`. You rarely need to create orders manually. Here is how the checkout flow and order display work:

```php theme={null}
namespace App\Http\Controllers;

use Shopper\Cart\Actions\CreateOrderFromCartAction;
use Shopper\Cart\Facades\Cart;
use Shopper\Core\Models\Order;

class CheckoutController extends Controller
{
    public function store()
    {
        $order = resolve(CreateOrderFromCartAction::class)
            ->execute(Cart::current());

        Cart::forget();

        return redirect()->route('orders.show', $order);
    }

    public function show(Order $order)
    {
        abort_unless($order->customer_id === auth()->id(), 403);

        return view('orders.show', [
            'order' => $order->load(['items.product', 'shippingAddress', 'billingAddress']),
        ]);
    }
}
```

<Note>
  The `CreateOrderFromCartAction` handles everything in a single transaction: calculating totals, creating addresses, creating order items, applying discounts, computing taxes, and marking the cart as completed. See the [Cart](/v2/cart#order-conversion) page for details.
</Note>

## Order Status Flow

| From Status | Possible Transitions            |
| ----------- | ------------------------------- |
| New         | Processing, Cancelled, Archived |
| Processing  | Completed, Cancelled            |
| Completed   | Archived                        |
| Cancelled   | (terminal state)                |
| Archived    | (terminal state)                |
