Checkout coupon code: from loose request to spec packet

This is the homepage example expanded into a full case: one vague request becomes a bounded spec, implementation slices, acceptance criteria, and evidence reviewers can check before merge.

RiskMoney, totals, invalid states
BoundaryOne coupon before payment
EvidenceValidation, total, rollback checks

The request before the spec

Weak ticket

Add coupon codes to checkout.
Make sure invalid codes do not break payment.

Spec-first rewrite

Feature: Checkout coupon code
Owner: Web Checkout
Status: Ready for review

Context:
- Checkout UI lives in apps/web/routes/checkout.
- Totals are computed in packages/billing/totals.ts.
- Coupon table exists but is not wired into checkout.

Goal:
- Let signed-in users apply one valid coupon before payment.
- Show invalid, expired, and already-used codes inline.

Non-goals:
- No coupon admin UI.
- No coupon stacking.
- No rewrite of billing totals.

The packet reviewers actually need

tasks.md

- [ ] Add coupon lookup by code and user.
- [ ] Validate expired, disabled, and reused codes.
- [ ] Recompute checkout total with one discount.
- [ ] Show inline errors without clearing payment form.
- [ ] Add feature flag for rollback.

acceptance-criteria.md

- Given a valid coupon
  When the user applies it before payment
  Then the discounted total is shown and used for payment.

- Given an expired code
  When the user applies it
  Then an inline error appears and the original total remains.

test-evidence.md

Automated:
- coupon validation unit test
- checkout total integration test
- reused coupon regression test

Manual:
- screenshot for valid code
- screenshot for invalid code
- rollback flag documented

AI coding guardrail

Implement only coupon application at checkout.
Do not add admin UI, stacking, price rules, or totals refactors.
Every changed file must map to one task and one acceptance criterion.

Why this catches rework early

It protects billing boundaries

The spec names the totals module and blocks unrelated refactors before an AI coding tool broadens the task.

It makes errors testable

Expired, reused, and invalid coupons become concrete acceptance paths rather than late QA discoveries.

It gives release a stop signal

The rollback flag and evidence list tell reviewers how to pause the change if checkout metrics move.

Reviewer walkthrough

The first review question is not "can the model build coupons?" It is "can the model change checkout without touching unrelated money logic?" That is why the packet names the totals module, blocks coupon stacking, and keeps admin tooling out of scope. Those decisions make the work smaller and make the review sharper.

The second review question is about invalid states. Coupon features fail in boring ways: expired codes still discount totals, reused coupons pass a second time, payment forms clear after an error, or rollback removes the UI without restoring the old calculation path. A thin request does not surface those cases. A useful spec packet makes them acceptance criteria before implementation starts.

Scope check

The diff should only touch checkout coupon lookup, validation, total display, and the rollback flag. Admin rules, stacking, pricing plans, and billing refactors need a separate spec.

Evidence check

Reviewers should see a valid-code path, invalid-code path, expired-code path, reused-code path, and a total calculation test that proves the payment amount matches the displayed amount.

Release check

The feature flag must be named in the packet, and support should know what metric or complaint pattern would trigger rollback during the first release window.

How to adapt this case

Use this case when the change touches a value that users can see or dispute: money, credits, limits, entitlements, seats, quotas, or usage. The exact coupon rules may not fit your product, but the review shape usually does. Start by naming the calculation source of truth, then list the states that must not corrupt it: invalid input, expired entitlement, replayed request, partial rollout, and rollback.

The important habit is to write the failure mode before implementation starts. If the team waits until QA to discover duplicate discounts or stale totals, the fix usually requires touching more code than the original feature. The spec packet moves those questions earlier, when changing the plan is still cheap.

When an AI coding assistant is involved, paste the non-goals directly above the implementation prompt. Ask the assistant to state which task each file change satisfies. If it proposes a totals refactor, admin UI, coupon stacking, or pricing model change, the answer should be a new spec, not a larger diff inside this one.

Anti-patterns to reject

Reject these patterns during review even when the demo looks correct. Checkout bugs often hide in state transitions, rollback behavior, and mismatches between what the page displays and what the payment system charges.

Discount logic in the UI only

The displayed discount and charged amount must come from the same source of truth, or support will see mismatched totals.

Coupon stacking by accident

If stacking is not in the spec, the implementation should reject or replace a second code instead of inventing a rule.

Rollback without data checks

A feature flag is not enough if applied coupons leave totals, orders, or analytics in an inconsistent state.

What to watch after launch

Checkout changes need a short observation window because payment problems can appear only after real coupons, retries, and support workflows touch the feature.

Total mismatch

Watch for any gap between displayed totals, order totals, and payment processor amounts. One mismatch should pause rollout.

Invalid-code noise

Track invalid and expired code errors separately from payment failures so support can tell UX confusion from billing risk.

Related cases

If the coupon touches API responses, compare with the API error case; if it requires schema changes, use the database case.

Use this pattern on your next checkout change

Start with the generator, then paste the packet into the issue or PR before asking an AI coding assistant to implement it.

Editorial note

This case is intentionally small and concrete: the goal is to show the spec boundary that prevents AI-generated implementation drift.