---
title: "Orders & pricing — Shops for Laravel | Roundly"
description: "The Price DTO — subtotal, discount, net, tax, shipping and final total — with placement snapshots, exact per-line tax and a per-rate summary."
url: https://roundly-consulting.com/open-source/docs/shops-for-laravel/pricing
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Shops for Laravel](https://roundly-consulting.com/open-source/docs/shops-for-laravel.md)

# Orders & pricing

An order has many items, an optional coupon, a generated number and its own currency. Its price accessor returns a Price DTO computing discount, shipping, tax and the final total from the items, the snapshotted shipping (shipping\_cost) and the placed discount:

```php
$price = $order->price;          // RoundlyConsulting\Shops\Orders\DataTransferObjects\Price

$price->getSubtotal();           // sum of line totals (quantity-aware), before discount
$price->getDiscountValue();      // amount saved by the placed discount
$price->getPriceAfterDiscount(); // subtotal minus the discount
$price->getNetPrice();           // tax-exclusive value of the goods
$price->getTaxPrice();           // tax across the goods (after discount)
$price->shippingCost();          // the shipping charged: shipping_cost, or zero with free shipping
$price->getFinalPrice();         // what the customer pays (goods + shipping, tax-correct)
$price->taxSummary();            // money TaxSummary: net / tax / gross per rate
```

## Placed orders never re-price

- Discount — the price reads the discount snapshotted at placement (discount, free\_shipping, coupon\_code). A coupon that later expires, is revoked or runs out of uses never changes a placed order.
- Tax — each line keeps the rate it was added with (order\_items.tax\_rate and tax\_label), and the order keeps the price\_type it was created under. Editing a shop’s rates, changing the shipping address or flipping shops.pricing.price\_type never changes what a placed order costs.
- An item written without a snapshot (tax\_rate null) is taxed at the live rate for its shop and shipping country.
- Shipping — the price includes the shipping cost snapshotted at checkout (shipping\_cost); a free-shipping coupon takes it off. Quoting another destination later never changes a placed order.

## Price caching

$order->price is computed once per model instance and cached together with its loaded items. After changing an order in memory (adding items, applying a discount), call $order->refresh() before reading the price again — and before handing it to StoreCreditTender, which debits the price it reads. Shops::order($order)->charge() re-reads the order as stored, under its row lock, before charging. checkout() already returns a refreshed order:

```php
use RoundlyConsulting\Money\Money;
use RoundlyConsulting\Shops\Orders\Item;

$order->price->getFinalPrice();             // computed and cached on this instance

Item::create([
    'order_id' => $order->id,
    'name' => 'Gift wrap',
    'quantity' => 1,
    'price' => Money::ofMinor(300, 'EUR'),  // must match the order's currency
]);

$order->refresh();                          // drop the cached price and items
$order->price->getFinalPrice();             // now includes the new line
```

## Tax summary

```php
$summary = $order->price->taxSummary();

$summary->perRate();   // net / tax / gross grouped per rate — an invoice VAT table
$summary->net();       // Money
$summary->tax();       // Money
$summary->gross();     // Money
```

## How tax is computed

- An order-level discount is first spread over the lines in proportion to their totals with money’s DiscountAllocator — the shares sum to the discount exactly.
- Each line is then taxed once on its discounted amount with money’s TaxRate — gross: taxable − round(taxable × 100 / (100 + rate)); net: round(taxable × rate).
- Rounding is half away from zero with bcmath — no floats. A gross catalog always satisfies net + tax = price after discount.
- gross (default) extracts tax from the price; net adds it on top. Shipping is never taxed by this engine.

## Pricing arbitrary lines

Construct a Price directly to price any lines or to include a shipping cost:

```php
use RoundlyConsulting\Money\Money;
use RoundlyConsulting\Shops\Orders\DataTransferObjects\Price;
use RoundlyConsulting\Shops\Orders\DataTransferObjects\PriceLine;
use RoundlyConsulting\Shops\Orders\Enums\PriceType;

$price = new Price(
    lines: [new PriceLine(unitPrice: Money::ofMinor(1000, 'EUR'), quantity: 2, taxClass: 'standard')],
    shipping: Money::ofMinor(490, 'EUR'),
    priceType: PriceType::Gross,     // or PriceType::Net
    taxResolver: null,               // defaults to app(TaxResolver::class)
    discount: Money::ofMinor(300, 'EUR'),
    freeShipping: false,
    currency: 'EUR',                 // the currency of an empty price
    shop: null,
    country: null,
);

$price->getFinalPrice();   // 21.90 EUR — 20.00 - 3.00 discount + 4.90 shipping (gross)
```

## Shipping in the total

A placed order’s price already includes its shipping: checkout quotes the shipping address (or takes the customer’s chosen shippingCost) and snapshots it as shipping\_cost — see Placing orders. $order->price->shippingCost() is the shipping charged: shipping\_cost, or zero when a free-shipping coupon applied. Shops::order($order)->quoteShipping() prices another destination without touching the order.

## Show 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 crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## 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.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/shops-for-laravel.md)
