Promotion Codes

Discounts belong entirely to Stripe. Nothing about a code is stored locally, there's no coupons table, and the API never looks a code up. A code is a string the customer types, and Stripe decides what it's worth.

One flag

SUBSCRIPTION_PROMOTION_CODES (on by default) decides whether the checkout session is created with allow_promotion_codes. That one parameter is the entire API side of the feature. There's no verify endpoint and no code travelling on a request, so there's nothing for the API to resolve afterwards. With the flag off the session is created without it and the client has nothing to apply a code against.

The settings endpoint exposes the flag as subscription_promotion_codes, so the client knows whether to show the field at all.

In the client

Stripe's checkout SDK doesn't ship a promotion code element, so the client writes the field and its apply control by hand and applies or removes a code through the SDK's actions. Stripe validates the code against the session, the discount lands on the session total, tax recalculates against the reduced amount, and the client re-reads the session to show it. The API subtracts nothing.

A code that's rejected, whether expired, unknown, or not valid for the plan, comes back from Stripe and the client can show it against the field. The user can also take an applied code back off and see the total return to what it was. By the time the user confirms, the discount is already on the session, so the confirm charges the discounted total and the sync call carries no code.

Against a trial

A code applied to a trial takes nothing off what the user pays at signup, since that's $0 either way. The discount sits on the recurring total the session carries, rides onto the subscription at confirm, and comes off the first real invoice when the trial ends.

The client has two figures to show for that to read correctly, $0 today and the discounted amount from the trial end. Showing only the discounted figure reads as a charge that isn't happening, and showing only the $0 throws away the reason the user typed a code. Whether the discount survives to the trial end is the coupon's own duration rather than anything the API sets, so a code that lasts one billing period is spent on the first invoice after the trial. See Trials.

What Stripe owns

Percentage or fixed amount, currency, expiry, redemption limits, first-time-customer restrictions, and which products a code applies to. All of it lives in the Stripe dashboard, none of it is mirrored locally, and marketing can create or retire codes without a deploy.

The tradeoff is that the starter has no admin tooling for discounts. An admin can't apply a code to an existing subscription or see what a subscription is carrying, and that's left to the Stripe dashboard on purpose. Mirroring discounts locally gets complicated quickly, since a subscription can hold several and they change outside the app. If the admin ever needs to show them, reading them live from Stripe is the simpler route.

© Websanova 2026 About Privacy