---
title: "Discounts — Money for Laravel | Roundly"
description: "Percentage, fixed and free-shipping discounts with caps, priorities and exclusivity, three stacking strategies and an itemised breakdown."
url: https://roundly-consulting.com/open-source/docs/money-for-laravel/discounts
language: en
---

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

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

# Discounts

Discount primitives for coupons, promotions and carts. A discount never removes more than its base and never produces a negative total:

```php
use RoundlyConsulting\Money\Discounts\Discount;

$subtotal = Money::ofMajor('100', 'EUR');

Discount::percentage('12.5')->cappedAt(Money::ofMajor('20', 'EUR'))->applyTo($subtotal); // 87.50 EUR
Discount::percentage('12.5')->amountFor($subtotal);                                     // 12.50 EUR
Discount::fixed(Money::ofMajor('150', 'EUR'))->applyTo($subtotal);                      // 0.00 EUR — never negative
```

- Discount::fixed(Money) — a fixed amount off the subtotal.
- Discount::percentage($percent) — 0 to 100 %.
- Discount::freeShipping() — 100 % on the shipping target.
- \->cappedAt(Money), ->withPriority(int) (higher applies first), ->exclusive(), ->labelled('CODE'), ->onShipping() / ->onSubtotal().
- amountFor($base) — how much is removed (0 ≤ amount ≤ base, and ≤ the cap); applyTo($base) — the base minus that amount.

The target is metadata for stacks: amountFor() applies to whatever base it is given, so never applyTo() a free-shipping discount to the goods price — route by target().

## Stacking

```php
use RoundlyConsulting\Money\Discounts\Discount;
use RoundlyConsulting\Money\Discounts\DiscountStack;
use RoundlyConsulting\Money\Enums\StackingStrategy;

$subtotal = Money::ofMajor('100', 'EUR');
$shipping = Money::ofMajor('5', 'EUR');

$breakdown = DiscountStack::of(
    Discount::percentage(10)->labelled('WELCOME10'),
    Discount::fixed(Money::ofMajor('5', 'EUR'))->withPriority(10),
    Discount::freeShipping(),
    Discount::percentage(25)->exclusive(),
)->using(StackingStrategy::Sequential)
 ->capTotalAt(Money::ofMajor('30', 'EUR'))
 ->apply($subtotal, $shipping);

$breakdown->totalDiscount();   // 25.00 EUR — the exclusive 25 % wins
$breakdown->total();           // 80.00 EUR
```

- Sequential (default) — each discount applies to the running remainder of its target, so percentages compound.
- Additive — each discount applies to the original base.
- BestOf — only the single largest discount applies.
- Exclusivity — the best exclusive discount competes with the whole non-exclusive set; the larger total wins, a tie goes to the non-exclusive set.
- Order — subtotal discounts by priority (descending) then insertion order, then shipping discounts.
- Each target is clamped to its base, then capTotalAt() applies; both trim in reverse application order.

In the example, the exclusive 25 % (25.00) beats the non-exclusive set (5.00 + 9.50 + 5.00 shipping = 19.50), so the breakdown holds one row. Without the exclusive discount, all three apply and the total is 85.50 EUR.

## The breakdown

```php
$breakdown->subtotal;             // 100.00 EUR
$breakdown->subtotalDiscount;     // Money
$breakdown->shipping;             // 5.00 EUR (zero when none was passed)
$breakdown->shippingDiscount;     // Money
$breakdown->discountedSubtotal();
$breakdown->discountedShipping();

foreach ($breakdown->applied as $row) {   // list<AppliedDiscount>
    $row->discount->label();              // e.g. "WELCOME10"
    $row->target;                         // DiscountTarget::Subtotal or DiscountTarget::Shipping
    $row->amount;                         // the Money removed
}
```

The applied amounts always sum to subtotalDiscount + shippingDiscount, and neither exceeds its base. To spread an order-level discount over line items, use DiscountAllocator (see Allocation & splitting).

## 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/money-for-laravel.md)
