Coupon and Promotion Code Controls: Redemption Reservation, Stacking Order, and Refund Replenishment
Two valid discounts don't mean they can stack, and seeing one slot left doesn't mean concurrent checkouts all succeed. This article versions eligibility, mutual-exclusion groups, calculation order, and redemption triggers, and uses a reservation ledger and provider receipts to control over-issuance, unknown checkouts, and refund replenishment.
Key Points
- Topic Focus: DevOps、独立开发
- Suitable for: indie hackers, full-stack engineers, AI application developers
- Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
Marketing creates "20% off the first year" and "Developer Conference, $50 off." The checkout page sees that both objects are valid and subtracts them in turn; meanwhile a code limited to 100 uses is seen by 20 simultaneous requests in the final second as "1 remaining." If the payment response is lost, the client retries with a new checkout ID and may end up taking both quota slots and receiving both discounts twice. A Coupon defines the discount capability, while a Promotion Code provides customer-facing redemption limits; the real risk lies at the junction of these two with orders, subscriptions, and fund state.
AI Pricing Migration handles base prices and existing-customer cohorts, and Developer Referral Controls handles referral attribution and rewards. This article only governs how a promotion gains eligibility, combines with other promotions, binds to a single commercial operation, and consumes quota; it does not allow a discount to override an unapproved price version.
Freezing promotionPolicy.v1 and the Composition Graph
Publish an immutable promotion.policy.v1 that at minimum binds the internal promotion ID, the provider coupon/promotion-code ID with a summary, the discount type and value, currency, applicable product/price/cohort, customer eligibility, minimum amount, valid [start,end), total and per-subject caps, duration, consume trigger, refund-replenishment rule, mutually exclusive group, composable set, calculation priority, and allocation/rounding rules. Display strings and user-entered codes are not identity; the normalized code is used only for lookup, and authorization references the immutable promotion generation.
schema: promotion.policy.v1
promotion: conf-2026@4
benefit: {type: FIXED_AMOUNT, amountMinor: 5000, currency: USD}
scope: {prices: [pro-annual@7], cohort: new-paid-customer@3}
valid: {start: 2026-08-01T00:00:00Z, end: 2026-09-01T00:00:00Z}
limits: {global: 1000, perCustomer: 1}
exclusiveGroup: acquisition-offer
stackOrder: 20
consumeOn: INVOICE_FINALIZED
Composition is not "executed in the order the user clicks." The Evaluator first fixes the cart/price/tax-input digest, then builds a directed order by policy generation and outputs a composition root: for example, first apply item-percent, then allocate the fixed amount, and finally apply the non-composable account credit. Each step uses the smallest integer unit or an explicit decimal fixed-point; a fixed discount cannot push a subtotal within scope below zero; rounding remainders are distributed by a stable line ID. Pre-tax vs. post-tax is decided by the approved tax configuration, and the order cannot be switched on the fly just to match a page total.
A Stripe coupon can define percent-off or amount-off, and a promotion code can also restrict first-time transactions, minimum amount, expiry, and redemption count; platform availability does not prove local eligibility and stacking legality. Each checkout outputs a single promotionDecision.v1 binding the eligible/rejected reasons of all candidates, the canonical benefit lines, the policy/composition root, the cart digest, and the expiresAt; the page must not recompute them one by one.
Reserve Quota First, Accept Discounts Against the Invoice Receipt
The redemption state machine is ELIGIBLE -> RESERVED -> PROVIDER_PENDING -> APPLIED -> CONSUMED, and may enter RELEASED/EXPIRED/REVERSED/PROVIDER_UNKNOWN. A reservation is unique per checkoutOperation and binds the subject, the promotionDecision/composition root, the cart digest, and the whole set of promotion generations; a single serialized CAS simultaneously rechecks mutual-exclusion/stacking and each consumed + reserved cap and reserves the quota for the entire group—if any fails, the whole group is not committed. Member promotions no longer each get their own independently-acceptable reservation.
The only business acceptance point is after retrieving the provider invoice/checkout receipt: it uses a CAS to match the checkout reservation, the invoice operation, the amount/currency, the entire group's composition root, and the discount-line root, atomically advancing the whole group to APPLIED and issuing one promotionApplicationReceipt.v1. Mutually exclusive discounts from the same checkout must not be split into two reservations or acceptances. A page success, a provider 2xx, or a quota decrement does not count as acceptance; consumeOn decides when to transition to CONSUMED.
Externally created checkouts/invoices use a stable operation ID and a provider idempotency key. After a timeout the reservation enters PROVIDER_UNKNOWN; switching keys to recreate the order is prohibited—query the same provider operation, release only if non-existence is proven, accept only if application is proven, and if the counterparty is still unknown, keep holding it and inform the user of the status. Bulk import of one-time codes can go through Operations CSV Import into staging, but policy/identity conflicts per code still need to be isolated and must not directly update the remaining count. HTTP Idempotency Key Correctness closes out checkout retries.
Refunds, voids, and disputes do not delete consumption in place. Policy clarifies whether a full refund creates a REVERSED and replenishes available quota, while a partial refund generates a correction allocated according to the original discount; campaigns that have already expired or are marked non-replenishable only record an income adjustment. Discount impact must enter AI Gross-Margin Versioned Cost Attribution at the original price and promotion generation; you cannot reverse-engineer a "discount rate" from net receipts alone.
Attacking the Last Slot and the Calculation Order
Test 101 requests fighting for 100 quota slots, same-customer concurrency, similar codes, validity-window boundaries, switching price after reservation, currency mismatch, mutually exclusive discounts being evaluated/reserved in two passes, lost provider responses, duplicate Webhooks, expired reservations, refunds, and mid-flight policy-generation swaps. Gate requirements: over-issuance, multiple active compositions in the same checkout, two APPLIEDs for the same operation, unapproved stacking, and negative subtotals must all be 0; invoice discount root vs. composition root match rate must be 100%; blind UNKNOWN releases/retries must be 0. Roll out by shadowing first, then enabling internal codes and 5% of new checkouts; on anomalies, stop new reservations and roll back to the previous policy, and correct already-accepted discounts only with credit/compensation invoices.
Related
Related Content
Article
What Is Vibe Coding? Will It Change How Programmers Work?
Technical trends, engineering practices, and an indie developer perspective on Vibe Coding, AI, and independent development.
Article
How Should a Technical Community Design Its Tag System and Recommendation Feed?
Centered on independent development, Next.js technology trends, engineering practices, and the indie developer perspective.
Article
Learning Technical Community Product Design from dev.to
Technical trends, engineering practices, and an independent developer's perspective on independent development and open source.
Article
How Can Indie Developers Use AI to Validate Product Ideas Quickly?
Centered on indie development and AI—technical trends, engineering practices, and the indie-developer perspective.