Skip to content

Checkout conversion: recovery emails, guest checkout lockdown, working promo codes - #2141

Open
richiemcilroy wants to merge 8 commits into
mainfrom
checkout-recovery-guest-lockdown
Open

Checkout conversion: recovery emails, guest checkout lockdown, working promo codes#2141
richiemcilroy wants to merge 8 commits into
mainfrom
checkout-recovery-guest-lockdown

Conversation

@richiemcilroy

@richiemcilroy richiemcilroy commented Aug 20, 2026

Copy link
Copy Markdown
Member

Greptile Summary

This PR adds abandoned-checkout recovery emails, tightens guest checkout validation and rate limiting, and applies allowlisted URL promotion codes.

  • Enables Stripe recovery URLs and sends transactional reminders for expired Pro checkout sessions.
  • Restricts guest checkout prices and quantities while adding request throttling.
  • Propagates campaign promo codes from pricing pages into Stripe Checkout.
  • Adds Loom migration marketing, analytics, structured data, and updated platform/compliance copy.

Confidence Score: 4/5

The PR is not yet safe to merge because checkout recovery can still evaluate or email the wrong account when a Stripe customer is reused.

The expired-session handler trusts the Stripe customer's mutable metadata.userId even though each checkout session records its initiating user in metadata.dubCustomerId, leaving the previously reported cross-account recovery failure reachable.

Files Needing Attention: apps/web/app/api/webhooks/stripe/route.ts

Important Files Changed

Filename Overview
apps/web/app/api/webhooks/stripe/route.ts Adds expired-session recovery emails, but account resolution still relies on mutable Stripe customer ownership rather than the initiating user stored on the session.
apps/web/app/api/settings/billing/guest-checkout/route.ts Adds price and quantity validation, rate limiting, recovery metadata, and allowlisted promotion-code application.
apps/web/app/api/settings/billing/subscribe/route.ts Adds recovery configuration and URL promotion-code resolution to authenticated checkout sessions.
apps/web/lib/checkout-promos.ts Resolves only allowlisted active Stripe promotion codes and selects compatible Checkout discount parameters.
apps/web/components/pages/HomePage/Pricing/ProCard.tsx Displays campaign-adjusted pricing and forwards the URL promo code into checkout requests.
packages/database/emails/checkout-recovery.tsx Introduces the transactional email template used to resume an expired Pro checkout.

Reviews (2): Last reviewed commit: "fix(web): list Linux, the Chrome extensi..." | Re-trigger Greptile

Context used:

@superagent-security

Copy link
Copy Markdown

🚨 Contributor flagged. Click here for more info: Superagent Dashboard

Comment on lines +691 to +693
foundUserId = User.UserId.make(customer.metadata.userId);
}
if ("email" in customer && customer.email) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Mutable customer ownership breaks recovery

If two Cap accounts with the same email reuse a Stripe customer, its metadata.userId points to the most recently associated account, so this handler checks that account instead of the owner recorded in the expired session's dubCustomerId. This can suppress recovery for a free checkout owner or send an upgrade reminder based on the wrong account's entitlement.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/app/api/webhooks/stripe/route.ts
Line: 691-693

Comment:
**Mutable customer ownership breaks recovery**

If two Cap accounts with the same email reuse a Stripe customer, its `metadata.userId` points to the most recently associated account, so this handler checks that account instead of the owner recorded in the expired session's `dubCustomerId`. This can suppress recovery for a free checkout owner or send an upgrade reminder based on the wrong account's entitlement.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +785 to +791
// Attaches a recovery URL to `checkout.session.expired` so abandoned
// upgrades can be emailed back (handled in the Stripe webhook).
after_expiration: {
recovery: { enabled: true, allow_promotion_codes: true },
},
// `priceId` is read back on `checkout.session.expired` for the recovery email.
metadata: {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Recovery comments duplicate implementation

These comments merely restate the adjacent after_expiration and priceId fields, and the same narration is repeated in the guest and authenticated web checkout creators. Removing these descriptive comments avoids maintenance noise and prevents them from drifting from the webhook implementation.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/app/api/desktop/[...route]/root.ts
Line: 785-791

Comment:
**Recovery comments duplicate implementation**

These comments merely restate the adjacent `after_expiration` and `priceId` fields, and the same narration is repeated in the guest and authenticated web checkout creators. Removing these descriptive comments avoids maintenance noise and prevents them from drifting from the webhook implementation.

**Context Used:** AGENTS.md ([source](https://github.com/capsoftware/cap/blob/main/AGENTS.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@richiemcilroy richiemcilroy changed the title Abandoned checkout recovery + guest checkout lockdown Checkout conversion: recovery emails, guest checkout lockdown, working promo codes Aug 20, 2026
@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai please review

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