fix: checkout flow for 0 dollar subscription#1465
Conversation
0 dollar subs on stripe don't create any client secrets
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds end-to-end free ($0) subscription support: detect free prices in the purchase UI, skip Stripe confirmation in checkout, propagate ChangesFree Subscription Checkout Bypass
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Fixes the dashboard purchase/checkout flow for $0 subscription prices where the backend intentionally does not return a Stripe client_secret, causing the existing “confirm payment” flow to fail/hang.
Changes:
- Adds an
isFreepath inCheckoutFormto skip Stripe confirmation and route through/purchase/return?free=1. - Updates the purchase session setup to allow missing
client_secretwhen the selected price is $0. - Updates the return page to render a terminal success state when
free=1is present.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| apps/dashboard/src/components/payments/checkout.tsx | Adds isFree prop and redirects to return flow for free subscriptions instead of confirming via Stripe. |
| apps/dashboard/src/app/(main)/purchase/return/page.tsx | Plumbs free query param through to the client component. |
| apps/dashboard/src/app/(main)/purchase/return/page-client.tsx | Adds a free=1 terminal success branch on the return page. |
| apps/dashboard/src/app/(main)/purchase/[code]/page-client.tsx | Detects free selection and allows missing client_secret; passes isFree to CheckoutForm. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| onTestModeBypass?: () => Promise<void>, | ||
| chargesEnabled: boolean, | ||
| isFree: boolean, | ||
| }; |
| if (free === "1") { | ||
| // $0 subs activate synchronously on the Stripe side and produce no | ||
| // PaymentIntent / client_secret, so there's nothing to retrieve — | ||
| // mirror the bypass branch and show terminal success. | ||
| runAsynchronously(checkAndReturnUser()); |
| const result = await response.json(); | ||
| if (!result.client_secret) { | ||
| if (!result.client_secret && !isFreeSelected) { | ||
| throw new Error("Failed to setup subscription"); | ||
| } | ||
| return result.client_secret; |
Greptile SummaryThis PR fixes the dashboard checkout flow for $0 (free) subscriptions, where Stripe does not generate a
Confidence Score: 3/5The free-plan redirect path in The core fix correctly guards the missing-
Important Files Changed
Sequence DiagramsequenceDiagram
participant U as User
participant CF as CheckoutForm
participant BE as Backend API
participant S as Stripe Elements
participant RP as /purchase/return
Note over U,RP: Free ($0) subscription flow (this PR)
U->>CF: Click Submit
CF->>S: elements.submit() ⚠️ runs before isFree check
S-->>CF: OK (or validation error if no card)
CF->>BE: "POST /purchase-session (price=$0)"
BE-->>CF: "{ } (no client_secret)"
CF->>RP: "redirect → /purchase/return?free=1&purchase_full_code=..."
RP->>BE: validate-code (async, for redirect)
RP-->>U: Free subscription activated
Note over U,RP: Paid subscription flow (unchanged)
U->>CF: Click Submit
CF->>S: elements.submit()
S-->>CF: OK
CF->>BE: POST /purchase-session
BE-->>CF: "{ client_secret }"
CF->>S: stripe.confirmPayment(clientSecret)
S-->>RP: "redirect → /purchase/return?payment_intent=..."
RP->>S: retrievePaymentIntent
S-->>RP: succeeded
RP-->>U: Payment succeeded
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/dashboard/src/components/payments/checkout.tsx`:
- Around line 62-72: handle the $0 (isFree) checkout before any Stripe Elements
readiness or submission: inside handleSubmit, move the isFree check (the block
that sets stripeReturnUrl.searchParams.set("free","1") and
window.location.assign(...)) to occur before any gating on stripe or elements
and before calling elements.submit(); ensure that when isFree is true you return
immediately without referencing stripe, elements, or calling elements.submit();
update any related control flow so handleSubmit exits early for isFree and does
not await elements.submit() or require stripe readiness.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d13a0098-a183-473a-a636-0c3f4ab8d474
📒 Files selected for processing (4)
apps/dashboard/src/app/(main)/purchase/[code]/page-client.tsxapps/dashboard/src/app/(main)/purchase/return/page-client.tsxapps/dashboard/src/app/(main)/purchase/return/page.tsxapps/dashboard/src/components/payments/checkout.tsx
Context
There was a small bug via dashboard checkout flow where it would fail on trying to create a checkout flow for a free product subscription because no client secret is generated for a 0 dollar subscription.
Summary of Changes
The flow should be fine now. There's special carve out logic for it. That being said, users attempting to mimic a free plan grant are encouraged to follow the
ensureFreePlanpattern.Summary by CodeRabbit