All packages
Shops for Laravel
The Shops facade
RoundlyConsulting\Shops\Facades\Shops is the whole API in one place. Each area is a scoped handle — one cart, one order, one variant’s stock — or a sub-accessor. The package also registers it as the global Shops alias:
use RoundlyConsulting\Money\Money;
use RoundlyConsulting\Shops\Facades\Shops;
use RoundlyConsulting\Shops\Inventory\Enums\StockReason;
use RoundlyConsulting\Shops\Orders\DataTransferObjects\PlaceOrderData;
use RoundlyConsulting\Shops\Orders\Enums\Status;
// Cart — lines of another cart are refused (ForeignItemException)
Shops::cart($cart)->add($variant, 2); // CartItem; the same variant merges into its line
Shops::cart($cart)->update($item, 3); // ?CartItem — 0 removes the line
Shops::cart($cart)->remove($item);
Shops::cart($cart)->clear(); // the "empty cart" button
Shops::cart($cart)->price('SUMMER'); // Price DTO, optionally with a coupon code
Shops::cart($cart)->subtotal(); // Money
$order = Shops::cart($cart)->checkout(new PlaceOrderData(couponCode: 'SUMMER'));
// Order
Shops::order($order)->transition(Status::InProgress);
Shops::order($order)->charge(); // PaymentResult — Paid on success; New/InProgress only
Shops::order($order)->fulfil(); // reservation becomes a sale
Shops::order($order)->cancel(); // reservation released, store credit returned
Shops::order($order)->refund(); // an unfulfilled order's reservation released too
Shops::order($order)->quoteShipping($address); // Money, through the bound ShippingMethod
// Inventory — every change is a StockAdjustment ledger row
Shops::inventory($variant)->receive(10, ref: $purchaseOrder, note: 'PO-17');
Shops::inventory($variant)->returned(1, $order); // the variant must be on that order
Shops::inventory($variant)->adjust(-2, StockReason::Manual, note: 'Damaged');
Shops::inventory($variant)->available(); // stock - reserved
// Coupons, tenancy, addresses
Shops::coupons()->preview('WELCOME10', Money::ofMinor(1000, 'EUR')); // DiscountResult, no redemption
Shops::current()->set($shop); // CurrentShop: set / get / id / forget / run
Shops::addresses()->defaults($customer); // OrderAddresses (billing, shipping)Entry points
| Call | Returns | What it is |
|---|---|---|
Shops::cart(Cart $cart) | Handles\CartHandle | One cart: lines, price, checkout. |
Shops::order(Order $order) | Handles\OrderHandle | One order: transitions, charge, shipping quote. |
Shops::inventory(ProductVariant $variant) | Handles\InventoryHandle | One variant’s stock. |
Shops::coupons() | Handles\CouponsHandle | Coupon previews, no redemption. |
Shops::current() | Shops\CurrentShop | The bound tenant (see Shops & tenancy). |
Shops::addresses() | Orders\AddressBook | A customer’s default order addresses. |
Shops::fake() | Testing\ShopsFake | The recording manager (see Testing). |
Handle methods
| Method | Returns | Does |
|---|---|---|
cart($cart)->add(ProductVariant $variant, int $quantity = 1) | CartItem | Snapshots the variant; the same variant merges into its line. |
cart($cart)->update(CartItem $item, int $quantity) | ?CartItem | Sets a line’s quantity; 0 removes it and returns null. |
cart($cart)->remove(CartItem $item) | void | Removes a line. |
cart($cart)->clear() | Cart | Removes every line; the cart stays. |
cart($cart)->price(?string $couponCode = null) | Price | Prices the cart, with the stored or given coupon code. Never redeems. |
cart($cart)->subtotal() | Money | Goods subtotal before any discount. |
cart($cart)->checkout(PlaceOrderData $data = new PlaceOrderData) | Order | Places the order in one transaction, holding the cart’s row lock — a double submit places one order. |
order($order)->transition(Status $to) | Order | Any allowed status move. |
order($order)->cancel() / refund() / fulfil() | Order | Shortcuts for Canceled (store credit returned), Refunded and Fulfilled. |
order($order)->charge() | PaymentResult | New or InProgress orders only, decided under the order’s row lock. Store credit first when enabled, the rest through the gateway; Paid on success. |
order($order)->quoteShipping(Address $to) | Money | Quote through the bound ShippingMethod. |
inventory($variant)->receive(int $quantity, ?Model $ref = null, ?string $note = null) | StockAdjustment | A delivery: on-hand stock goes up. |
inventory($variant)->returned(int $quantity, ?Model $ref = null, ?string $note = null) | StockAdjustment | A customer return; with an Order ref the variant must be on it. |
inventory($variant)->adjust(int $delta, StockReason $reason = StockReason::Manual, ?Model $ref = null, ?string $note = null) | StockAdjustment | Any signed adjustment; a manual correction by default. |
inventory($variant)->available() | int | Stock minus reserved. |
inventory($variant)->inStock(int $quantity = 1) | bool | Whether the quantity can be sold now. |
coupons()->preview(string $code, Money $goods) | DiscountResult | What a code would take off, via the bound DiscountResolver. |
addresses()->defaults(Addressable $customer, ?bool $billingSameAsShipping = null) | OrderAddresses | Primary billing and shipping addresses as order snapshots. |
addresses()->map(?AddressModel $address) | ?Address | One saved address as an order Address. |
- A line of another cart is refused with ForeignItemException before anything is written — scope carts to the current customer and let the handle guard the line ids that arrive in a request.
- returned() with an Order reference refuses a variant that was never on that order, also with ForeignItemException.
- Illegal status moves throw IllegalStatusTransitionException and change nothing; an oversell at checkout throws InsufficientStockException, and an empty cart or a line whose variant was removed throws CheckoutRefusedException — both write nothing.
- Shops::current() methods (set, get, id, forget, run) are listed under Shops & tenancy.
Model shorthand
The model convenience methods go through the same manager, so they behave identically and Shops::fake() sees them:
use RoundlyConsulting\Shops\Orders\Enums\Status;
use RoundlyConsulting\Shops\Shops\Shop;
$cart->add($variant, 2); // Shops::cart($cart)->add($variant, 2)
$order->transitionTo(Status::Paid); // Shops::order($order)->transition(Status::Paid)
$order->markInProgress(); // → InProgress
$order->markPaid(); // → Paid
$order->markFulfilled(); // → Fulfilled
$order->cancel(); // Shops::order($order)->cancel()
$order->refund(); // Shops::order($order)->refund()
Shop::current(); // Shops::current()->get(), typed to the package's Shop modelShow your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.