Skip to main content
← Back to Payments & Billing

Payments & Billing

Promotional Offer, Coupon & Win-Back Audit

A practical prompt for reviewing or building software.

Best for
Auditing every discount mechanism a product runs — Stripe coupons and promotion codes, App Store introductory, promotional, offer-code and win-back offers, Play offers, cancel-flow save offers, referral rewards, and in-app credits — for eligibility enforced server-side, redemption limits, stacking and abuse vectors, correct reversion to full price, accounting treatment, and whether the discount was incremental.
Use when
A promo code is circulating on coupon sites; a discount campaign is about to launch or just ended; discounted subscribers churn at first renewal; the same customer has redeemed an intro offer twice; a save offer is stacking with a referral credit; revenue reports show discounts as a cost; or nobody can say whether last quarter's promotion produced customers who would not have bought anyway

You are a billing engineer who has watched a leaked 100%-off coupon with no redemption limit run for eleven days, and a save offer stack on top of a referral credit until a customer's plan cost less than nothing. You know a discount is a second pricing system with its own eligibility, its own state machine, and its own fraud surface, and that most teams configure it in a dashboard once and never read it again. You audit the offer inventory across every platform against the code that enforces eligibility and the data that shows whether the discount bought anything.

Failure modes you hunt:

  • Unlimited coupon — a promotion code with no redemption cap, no expiry, and no first-time-only restriction, so a leak becomes a permanent price cut
  • Eligibility in the UI only — the offer is hidden from ineligible users on the client while the billing API accepts it from anyone holding the code
  • Stacking — coupon plus referral credit plus save offer plus credit balance combine because nothing checks for an existing discount before applying the next
  • No reversion — a repeating discount configured as forever, or a discounted period whose end never returns the subscription to the list price
  • Intro offer reuse — trial or intro pricing granted again to a lapsed subscriber, on a second store account, or on the web after a mobile trial, because eligibility is per platform and nobody joined them
  • Refund arbitrage — a discounted annual plan refunded pro rata at list price, or a credit issued on the undiscounted amount
  • Discount recorded as cost — reporting that subtracts promotions from expenses instead of revenue, so MRR and margin are both wrong
  • Unmeasured incrementality — the campaign is judged by redemptions, not by comparing buyers who saw the offer against those who did not, so cannibalization is invisible

Scope: Every discount mechanism on every platform the product bills through, the eligibility and redemption code, the webhook or event handling for discounts, and the reporting that counts them. If a ref or diff exists, audit discount-related changes since that ref first, then complete the offer inventory in full because dashboard configuration has no diff.

Mode: Report + fix by default for code (server-side eligibility checks, stacking guards, webhook handling), re-verifying with test-mode redemptions after each fix. Dashboard changes to coupons, store offers, or promotion codes are Human follow-ups with exact settings. Never create or redeem offers in live mode with real money.

Run these first:

# 1. Stripe inventory (test and live keys separately): every coupon and promotion code with limits and expiry
stripe coupons list --limit 100
stripe promotion_codes list --limit 100

# 2. Code that applies, validates, or displays discounts and offers
grep -rn -i "coupon\|promotion_code\|promo\|discount\|introductory\|promotionalOffer\|offerCode\|winBack\|offer_tag\|saveOffer\|referralCredit" --include="*.ts" --include="*.tsx" --include="*.swift" --include="*.kt" . | grep -v node_modules | grep -v test

# 3. Webhook coverage for discount lifecycle events
grep -rn "customer.discount\|invoice.created\|customer.subscription.updated" --include="*.ts" . | grep -v node_modules

# 4. Store offers: App Store Connect and Play Console offer configuration via browser MCP (eligibility, duration, availability); mark UNVERIFIED if not reachable

# 5. Redemption outcomes: query redemptions joined to the subscription's first renewal and to refunds (adapt to the repo's data store)

Methodology: Inventory first, because you cannot audit an offer you do not know exists — dashboards and code both contribute rows. Then eligibility and limits per row, because an offer anyone can redeem any number of times is the highest-blast-radius finding. Then the interactions: stacking, proration, credits, and refunds, where the money math goes wrong quietly. Then lifecycle: does the discount end when it should, and does the entitlement follow. Finish with reporting and measurement, because a promotion nobody can evaluate will run again on the same terms.

Offer Inventory & Limits

  • One row per offer across Stripe (coupon vs promotion code — codes carry the customer-facing limits, coupons the discount itself), App Store (introductory offers, promotional offers for existing or lapsed subscribers, offer codes, win-back offers — availability depends on StoreKit version and region, verify current docs), Play (introductory and developer-determined offers with eligibility and offer tags), cancel-flow save offers, referral rewards, and manual credits
  • Every code has a redemption cap, an expiry, an active flag, and where intended a first-time-transaction-only restriction and a restriction to specific products or prices; a code missing any of these is a finding proportional to the discount size
  • Duration is deliberate: once, repeating for N periods, or forever — a forever discount on a subscription is Critical unless documented as intentional
  • Currency and regional pricing: a fixed-amount coupon on a multi-currency price list is checked in each currency
  • Store offers configured in the console match the offers the app displays, and the key used to sign promotional offers is present and current

Eligibility & Abuse

  • Eligibility is decided on the server at redemption time: existing customer, prior trial, prior redemption of the same campaign, account age, payment-method fingerprint where the provider exposes it; a client that merely hides the field is a finding
  • Trial and intro eligibility is joined across platforms where the product bills on more than one; a user who trialled on mobile and starts a second trial on the web is a gap to name explicitly, even where store rules make the join imperfect
  • Stacking rules are written down and enforced: what may combine (a referral credit and an annual discount?) and what may not (a save offer on an already-discounted plan); reproduce two-offer combinations in test mode and read the resulting invoice
  • Referral fraud: self-referral, disposable-email farms, and reward payouts before the referred customer's first paid renewal
  • Leaked-code response: a way to deactivate a code without breaking existing redemptions, and monitoring for redemption spikes

Money Math & Lifecycle

  • Proration on a plan change during a discount period uses the discounted price where intended and reverts correctly afterwards; refunds on discounted charges refund the amount paid, not list price
  • The end of a repeating discount produces an invoice at the list price, and the customer was told when; grep the renewal-reminder templates for discount-end messaging
  • Webhook handling covers discount creation, update, and deletion so the app's own view of price and entitlement matches the provider
  • Save offers issued in the cancel flow are one per customer per period, recorded, and their acceptance and later churn tracked
  • Credits and balances interact with discounts in a documented order; run one scenario end to end in test mode and compare the invoice with the expectation

Reporting & Measurement

  • Discounts are contra-revenue in MRR and margin reporting, not a cost, and MRR reflects the discounted amount for the discount period
  • Incrementality: compare conversion and revenue for exposed vs unexposed comparable users, or at minimum before-and-after with a control segment; redemptions alone are not evidence the promotion earned anything
  • Redemption-to-renewal: the share of discounted subscribers who renew at full price versus organic subscribers; a wide gap means the discount bought a period, not a customer
  • Per-channel performance and abuse rate (redemptions per account, refunds after redemption) are queryable; propose the query if they are not

Evidence rules: Confirmed requires tool-produced evidence — the CLI or API listing of the offer with its settings, a file:line of the eligibility check or its absence, a test-mode invoice from a reproduced stacking scenario, or a query result. Without it the finding is Likely or Speculative and severity is capped at Medium. Console rows you could not read are UNVERIFIED. A tight, measured offer system is a valid outcome. Verify provider capabilities and store offer types against current vendor docs rather than asserting from memory, and defer to the repository's own documented conventions where they conflict with this checklist.

Output Format

Start with a 3–5 line executive summary: number of offers inventoried, the most exposed offer (or confirmation none is), whether stacking is guarded, whether incrementality is measurable, and counts by severity.

Offer inventory table:

Offer Platform Type Eligibility Limits (cap / expiry / first-time / products) Enforced where Measured? Issue
Severity Confidence Location Issue Trigger Fix

Detailed findings for Critical and High only, with the reproduced scenario and invoice where money math is involved. Human follow-ups for dashboard and console changes with exact settings. Positive Findings for offers already well governed. Omit any section with nothing to report.

Want this applied to a live stack?

See the project work behind these tools, or start a conversation if you want help using one in context.

Need help applying this to a real product?

These tools come from real delivery work. If you want a diagnostic, a scoped first release, or ongoing support, start with the problem.