vanilo/order
Vanilo Order provides the core order management layer for Laravel e-commerce applications built on Vanilo. It includes order and order item models, status handling, adjustments, totals, and the business logic needed to create, calculate, and process customer orders.
vanilo/order is the order/order-item backbone of the Vanilo e-commerce stack. It does not ship a storefront or admin UI; it gives you the models, contracts, factories, status handling, number generation and events you build checkout/order management on top of.
Install:
composer require vanilo/order
The module registers via Laravel package discovery. Publish and run migrations before touching anything else:
php artisan vendor:publish --tag=vanilo-order-migrations
php artisan migrate
Also publish config/order.php if you plan to change model bindings or order-number behaviour (check vendor/vanilo/order/src/Providers/ModuleServiceProvider.php for the exact publish tags in your installed version).
Where to look first, in this order:
src/Contracts/ — Order, OrderItem, OrderFactory, OrderStatus, OrderNumberGenerator. These define the public seam.src/Models/Order.php — the concrete implementation, its relations (items(), customer()), and total calculations.config/order.php — model swaps and the number-generator block.src/Events/ — OrderCreated, OrderUpdated, OrderStatusChanged, etc. These are your integration hooks.First use case: turn a cart/cart-like object into an order.
The canonical flow is through the OrderFactory contract, not Order::create():
use Vanilo\Order\Contracts\OrderFactory;
$order = app(OrderFactory::class)->createFromPayable($cart);
Anything implementing the Payable contract (the Vanilo cart does) can be converted. From there:
$order->number; // generated per config
$order->items; // OrderItem collection
$order->total(); // Money object
$order->status; // OrderStatus enum/instance
If you are not using Vanilo's cart, use createFromDataArray() with the shape the factory expects, or subclass the factory and bind your own.
Checkout via the factory, then decorate. Don't build orders by hand in controllers. Let OrderFactory create the order and items from the payable, then layer your own domain data (payment method, shipping address, notes) onto the returned order before saving:
$order = app(OrderFactory::class)->createFromPayable($cart, [
'payment_method' => 'stripe',
'notes' => $request->input('notes'),
]);
$order->save();
Keeping creation inside the factory preserves numbering, item copying and event dispatch.
Status is a first-class concept, not a string. Cast/read $order->status through the OrderStatus contract. Transitioning should be explicit:
use Vanilo\Order\Contracts\OrderStatus;
$order->status = OrderStatus::PROCESSING;
$order->save(); // fires OrderStatusChanged
Build a small transition policy (pending → processing → completed/cancelled) in your app layer; the package deliberately does not enforce a state machine.
React to events rather than editing vendor code. Listen for OrderCreated to send confirmations, push to a fulfilment queue, or sync to an ERP. Listen for OrderStatusChanged for customer notifications. Register listeners in your own EventServiceProvider — nothing needs to be hacked into the module.
Swap models via config, not by patching. When you need extra columns or behaviour:
// config/order.php
'models' => [
'order' => App\Models\Order::class, // extends Vanilo\Order\Models\Order
'order_item' => App\Models\OrderItem::class,
],
Always extend the base model so traits, casts and relations survive.
Number generation strategy. Defaults are typically a sequential generator; for high-volume or multi-node setups switch to the time-hash generator in the number_generator config block. Implement OrderNumberGenerator if you need invoice-style prefixes (INV-2024-000123).
Admin integration. If you use vanilo/admin (Orchid-based), the module's order screens light up automatically once installed. For custom back-office screens, query Order::with('items') and paginate — the module gives you no list controllers of its own.
Totals. Treat $order->total() as the source of truth and recompute after any item mutation; don't cache totals in your own column unless you also hook the update events.
Payable mismatch is the #1 failure. createFromPayable() expects an object implementing the contract with the right item/total accessors. If you roll your own cart, either implement Payable or use createFromDataArray(); do not Order::create() and hope.number column unique and prefer the time-hash generator in production.vanilo/order version, and if you compare $order->status === 'pending', verify it — use $order->status->value or the enum cases instead of loose strings.Order/OrderItem guard or fill specific attributes. Extra fields you add via a swapped model need to be added to $fillable on your subclass, or you'll silently drop them.$order->total() and item counts iterate items. Always Order::with('items') on index pages.(float) cast totals for storage; convert only at the presentation boundary.vanilo/cart/vanilo/product leaves foreign keys or customer relations unbacked. Run migrations in dependency order and check order_items constraints.vendor:publish --tag=... fails, list providers with php artisan vendor:publish and pick the module's tag from the interactive menu.OrderNumberGenerator binding, OrderFactory binding, and event listeners. Avoid subclassing models and overriding relations unless the config swap is genuinely insufficient.Event::fake() around factory calls and assert OrderCreated rather than inspecting DB rows; it keeps tests aligned with how the package is meant to be consumed.How can I help you explore Laravel packages today?