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

# Discounts and promotion codes

> Let buyers enter a Stripe promotion code, or apply a coupon before Numeral calculates tax.

Numeral for Stripe Checkout supports Stripe coupons and promotion codes in payment and subscription sessions. Choose one approach for each session:

| Approach                                      | Field inside `checkout`                        | When the discount is applied             |
| --------------------------------------------- | ---------------------------------------------- | ---------------------------------------- |
| Let the buyer enter a code in Stripe Checkout | `allow_promotion_codes: true`                  | After Numeral prepares the initial quote |
| Apply an existing coupon                      | `discounts: [{ coupon: "coupon_123" }]`        | Before Numeral calculates the quote      |
| Apply an existing promotion-code ID           | `discounts: [{ promotion_code: "promo_123" }]` | Before Numeral calculates the quote      |

These fields are optional. Omitting them preserves the existing checkout behavior. Continue using `X-API-Version: 2026-03-01`.

## Let buyers enter a promotion code

Set `checkout.allow_promotion_codes` to `true` when your server creates the Bridge session. Stripe displays the code-entry field on its payment page; the Numeral address collector does not collect the code.

```bash theme={null}
curl https://api.numeralhq.com/tax/bridge/sessions \
  -H "Authorization: Bearer $NUMERAL_API_KEY" \
  -H "X-API-Version: 2026-03-01" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: checkout_order_8421" \
  -d '{
    "config_id": "brcfg_123",
    "collection_mode": "hosted",
    "checkout": {
      "mode": "payment",
      "line_items": [{ "price": "price_123", "quantity": 1 }],
      "success_url": "https://store.example/success",
      "cancel_url": "https://store.example/cart",
      "allow_promotion_codes": true
    }
  }'
```

Replace the configuration and Price IDs with your own. The configuration supplies the tax-location policy; Numeral collects missing address fields before redirecting to Stripe. Use a new idempotency key for each logical checkout and reuse it only when retrying that request.

### Tax eligibility for buyer-entered codes

Buyer-entered codes are supported when the applicable tax rules remain valid as the discounted price changes. Percentage-based taxes and eligible zero-rate or exempt carts can qualify.

Rules with price thresholds, tax caps, flat fees, or other unsupported calculations cannot use this option. Numeral checks eligibility before returning the Stripe Checkout URL. If the cart is ineligible, use a pre-applied discount so Numeral can calculate tax from the discounted amounts.

<Note>
  Enabling promotion codes permits the buyer's final total to differ from the initial quote. In manual-confirmation flows, `accepted_total` still refers to that initial quote.
</Note>

## Apply a discount before calculating tax

Replace `allow_promotion_codes` in the request above with one of these `checkout.discounts` values:

<CodeGroup>
  ```json Coupon theme={null}
  {
    "discounts": [{ "coupon": "coupon_123" }]
  }
  ```

  ```json Promotion-code ID theme={null}
  {
    "discounts": [{ "promotion_code": "promo_123" }]
  }
  ```
</CodeGroup>

`promotion_code` takes Stripe's `promo_...` object ID, not the buyer-facing code such as `SUMMER20`. Create and manage coupons and promotion codes in the connected Stripe account. See [Stripe's discount guide](https://docs.stripe.com/payments/checkout/discounts).

Numeral applies percentage or fixed-amount discounts to eligible products before calculating tax, then verifies the amounts against Stripe before exposing the checkout URL.

### Restrictions

* `discounts` accepts at most one entry, containing exactly one of `coupon` or `promotion_code`.
* Do not combine a pre-applied discount with `allow_promotion_codes: true`.
* Prices, coupons, and promotion codes must belong to the configuration's connected Stripe account and the same test or live mode.
* If the Stripe connection uses a restricted key, it needs permission to read coupons and promotion codes.
* Stripe enforces redemption limits, customer restrictions, minimum spend, and expiration. Pass `checkout.customer` when a code is restricted to an existing Stripe Customer.
* Combine repeated instances of the same Stripe Price into one line item with the total quantity when using discounts.

Subscription discounts use Stripe's subscription behavior. Numeral verifies the initial checkout; subsequent renewal invoices continue through the existing Stripe invoice integration.

## Read the initial and final amounts

For sessions that opt into discounts, the server response includes `amount_discount` on the quote and its lines, plus a `settlement` field. Amounts use the currency's smallest unit, such as cents for USD.

| Field                    | Meaning                                                                       |
| ------------------------ | ----------------------------------------------------------------------------- |
| `quote.subtotal`         | Initial subtotal after discounts, excluding tax                               |
| `quote.amount_discount`  | Discount included in the initial quote; zero before a buyer enters a code     |
| `quote.total_tax_amount` | Tax calculated for the initial quote                                          |
| `quote.total`            | Initial subtotal plus tax                                                     |
| `settlement`             | Final verified discounted calculation, or `null` until verification completes |

The original subtotal before discounts is `subtotal + amount_discount`. For example, a \$24.00 cart with a 20% discount has `subtotal: 1920` and `amount_discount: 480` before tax.

Numeral retains `quote` after checkout. Retrieve the Bridge session from your server and read `settlement` for the verified final amounts, especially when the buyer entered a code in Stripe. Numeral verifies the final cart and tax before committing the discounted transaction.

Sessions that omit discount options retain their existing response shape; the additional discount and settlement fields are not required for those sessions.

## Handle errors and retries

| Error code                 | What to do                                                                                               |
| -------------------------- | -------------------------------------------------------------------------------------------------------- |
| `provider_option_conflict` | Choose either buyer-entered codes or one pre-applied discount.                                           |
| `tax_shape_not_supported`  | Create a new checkout using a pre-applied discount instead of buyer-entered codes.                       |
| `tax_rate_unavailable`     | If `last_error.retryable` is `true`, retry the create request with the same payload and idempotency key. |
| `provider_total_mismatch`  | Do not redirect or fulfill. Numeral could not verify Stripe's amounts against the calculated quote.      |

Codes in a session's `last_error.code` use lowercase, as shown above. Immediate API errors use an uppercase `type`, such as `PROVIDER_OPTION_CONFLICT`.

A temporary eligibility-check failure can return a session in `phase: "processing"` with a retryable `last_error`. Repeating the create request resumes that session with its original cart and configuration.

## Test discounted and zero-dollar orders

Test a valid code, an expired or ineligible code, a pre-applied discount, and a 100% discount using your test-mode Stripe connection.

For a zero-dollar payment checkout:

* Stripe does not collect a payment method. A 100% discount cannot be used to collect a card for future charges.
* Do not require a PaymentIntent to exist.
* Wait for the Bridge session to reach a completed, verified state. Fulfillment must also handle `status: "complete"`, `payment_status: "no_payment_required"`, and `tax_status: "not_applicable"`, rather than requiring every completed checkout to report `paid`.

Read [Metadata and saved cards](/integrations/stripe/stripe-checkout-metadata) to combine discounts with application tracking or future-payment setup.
