Skip to content

fix(entitlements): never let promo redemption throw into the grant response - #115

Merged
ralyodio merged 1 commit into
masterfrom
fix/promo-redemption-no-throw
Aug 1, 2026
Merged

fix(entitlements): never let promo redemption throw into the grant response#115
ralyodio merged 1 commit into
masterfrom
fix/promo-redemption-no-throw

Conversation

@ralyodio

@ralyodio ralyodio commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #110.

#110 made the promo duplicate guard atomic (INSERT OR IGNORE + rowsAffected) — good — but in doing so it dropped the try/catch that used to wrap those queries.

Both callers grant the entitlement before recording the redemption, and neither has its own error handling:

  • apps/web/app/api/payments/status/route.ts:58
  • apps/web/app/api/payments/create-checkout/route.ts:59

So a transient DB failure in the INSERT now throws out of recordPromoRedemption and 500s a response that has already granted the purchase. In the status route that response carries grant.apiKeyPlaintext, which the code notes is "shown exactly once" — the user would lose their API key to the 500.

Change

Restore the catch (log + return false) while keeping #110's atomic guard and capped increment intact. The recorded flag is tracked outside the try so a failed counter bump still reports the redemption row that did land, rather than claiming it never happened.

Verification

  • pnpm typecheck clean
  • pnpm test — 99 passed (2 new)
  • Both new tests confirmed non-vacuous: they fail with the catch removed, pass with it restored

🤖 Generated with Claude Code

…sponse

#110 dropped the try/catch around recordPromoRedemption to make the
duplicate guard atomic. Both callers grant the entitlement BEFORE
recording the redemption and neither handles errors:

  - app/api/payments/status/route.ts:58
  - app/api/payments/create-checkout/route.ts:59

So a transient DB failure in the INSERT now 500s a response that has
already granted the purchase — and the status route's payload carries
grant.apiKeyPlaintext, which is shown exactly once. The user would lose
their API key to that 500.

Restore the catch (log + return false) while keeping the atomic
INSERT OR IGNORE guard and the capped increment. The recorded flag is
tracked outside the try so a failed counter bump still reports the
redemption row that did land.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@ralyodio
ralyodio merged commit 207abde into master Aug 1, 2026
5 checks passed
@ralyodio
ralyodio deleted the fix/promo-redemption-no-throw branch August 1, 2026 03:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant