Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis PR adds a charges_enabled capability check throughout the payment purchase flow. The backend now retrieves the Stripe connected account's charges_enabled status and propagates it through verification code handlers to the frontend, where the checkout form displays a warning and disables submission if charges are not enabled on the Stripe account. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Dashboard as Frontend
participant Backend as Backend API
participant Stripe as Stripe API
User->>Dashboard: Open purchase link
Dashboard->>Backend: GET /validate-code?code=xxx
Backend->>Stripe: Get connected account details
Stripe-->>Backend: Return account with charges_enabled status
Backend-->>Dashboard: Return ProductData with charges_enabled
alt charges_enabled = true
Dashboard->>Dashboard: Show checkout form
User->>Dashboard: Submit payment
else charges_enabled = false
Dashboard->>Dashboard: Show "Payments not enabled" warning
Dashboard->>Dashboard: Disable checkout submit button
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes The changes span multiple files with mixed complexity: backend Stripe integration refactoring requires careful validation of the account retrieval logic; frontend adds a new prop surface and UI guard logic that are straightforward; test updates are repetitive snapshot corrections; and the test mode toggle simplification is low-risk but deserves verification that error handling removal is intentional. Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 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 |
|
Greptile encountered an error while reviewing this PR. Please reach out to support@greptile.com for assistance. |
There was a problem hiding this comment.
Review by RecurseML
🔍 Review performed on b6a2ab5..998aaf5
| Severity | Location | Issue | Delete |
|---|---|---|---|
| apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx:1592 | Unhandled promise rejection |
✅ Files analyzed, no issues (8)
• apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts
• apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx
• apps/backend/src/app/api/latest/payments/purchases/validate-code/route.ts
• apps/backend/src/app/api/latest/payments/purchases/verification-code-handler.tsx
• apps/dashboard/src/app/(main)/purchase/[code]/page-client.tsx
• apps/dashboard/src/components/payments/checkout.tsx
• apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--validate-code.test.ts
• apps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.ts
| checked={paymentsConfig.testMode === true} | ||
| disabled={isUpdatingTestMode} | ||
| onCheckedChange={(checked) => void handleToggleTestMode(checked)} | ||
| onCheckedChange={(checked) => handleToggleTestMode(checked)} |
There was a problem hiding this comment.
Unhandled promise rejection vulnerability. The async function handleToggleTestMode is called directly in the event handler without error handling. Previously (lines 1571-1579 in the old code), this was wrapped in a try-catch block. If project.updateConfig() throws an error, it will result in an unhandled promise rejection, causing the application to potentially crash or behave unpredictably in production. The error handling was intentionally removed, making this a regression that will lead to poor user experience when the API call fails (e.g., network issues, validation errors).
React with 👍 to tell me that this comment was useful, or 👎 if not (and I'll stop posting more comments like this in the future)
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/backend/src/app/api/latest/payments/purchases/validate-code/route.ts (1)
96-103: Guard against legacy codes missingchargesEnabled.Older verification codes won’t have
chargesEnabled, so this will yieldundefinedand violate the response schema (and likely throw before replying). Default tofalsewhen the field is absent so previously issued links keep working.- charges_enabled: verificationCode.data.chargesEnabled, + charges_enabled: verificationCode.data.chargesEnabled ?? false,apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx (1)
52-192: Add charges_enabled validation to prevent payment operations when charges are disabled.The
data.chargesEnabledfield is available from the verification code but is never checked before creating payment intents (line 128) or subscriptions (lines 79, 155). Add a validation check near the start of the handler:if (!data.chargesEnabled) { throw new StatusError(400, "Charges are not enabled for this Stripe account"); }This should occur after validating the tenancy but before any Stripe API operations to ensure disabled accounts cannot initiate payments.
🧹 Nitpick comments (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx (1)
1569-1572: Consider adding loading state for better UX.The removal of the loading state creates two concerns:
- Users lack visual feedback during the update operation
- Multiple rapid clicks could trigger concurrent update requests
Consider restoring the loading state:
+const [isUpdatingTestMode, setIsUpdatingTestMode] = useState(false); + const handleToggleTestMode = async (enabled: boolean) => { + if (isUpdatingTestMode) return; + setIsUpdatingTestMode(true); try { await project.updateConfig({ "payments.testMode": enabled }); toast({ title: enabled ? "Test mode enabled" : "Test mode disabled" }); } catch (error) { toast({ title: "Failed to update test mode", description: error instanceof Error ? error.message : "An error occurred", variant: "destructive" }); + } finally { + setIsUpdatingTestMode(false); } };Then update the Switch component:
<Switch id={testModeSwitchId} checked={paymentsConfig.testMode === true} onCheckedChange={(checked) => handleToggleTestMode(checked)} + disabled={isUpdatingTestMode} />apps/dashboard/src/components/payments/checkout.tsx (2)
101-112: Consider using Alert component for blocking error states.The guard UI correctly prevents checkout when charges are disabled, but per the coding guidelines for this directory pattern, blocking alerts and errors should use Alert components rather than Typography with variant styling.
As per coding guidelines.
Apply this diff to use the Alert component:
+import { Alert, AlertDescription, AlertTitle, Button, Typography } from "@stackframe/stack-ui"; -import { Button, Typography } from "@stackframe/stack-ui";if (!chargesEnabled) { return ( <div className="flex flex-col gap-4 max-w-md w-full p-6 rounded-md bg-background"> - <div className="space-y-1"> - <Typography type="h3" variant="destructive">Payments not enabled</Typography> - <p className="text-sm text-muted-foreground"> - This project does not have payments enabled yet. Please contact the app developer to finish setting up payments. - </p> - </div> + <Alert variant="destructive"> + <AlertTitle>Payments not enabled</AlertTitle> + <AlertDescription> + This project does not have payments enabled yet. Please contact the app developer to finish setting up payments. + </AlertDescription> + </Alert> </div> ); }
118-118: Remove redundant disabled condition.The
!chargesEnabledcheck in the Submit button's disabled condition is unreachable because of the early return guard at lines 101-112. When!chargesEnabledis true, the component returns early and never renders this button.Apply this diff to remove the redundant check:
<Button - disabled={!stripe || !elements || disabled || !chargesEnabled} + disabled={!stripe || !elements || disabled} onClick={handleSubmit} >
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts(3 hunks)apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx(1 hunks)apps/backend/src/app/api/latest/payments/purchases/validate-code/route.ts(2 hunks)apps/backend/src/app/api/latest/payments/purchases/verification-code-handler.tsx(2 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx(2 hunks)apps/dashboard/src/app/(main)/purchase/[code]/page-client.tsx(2 hunks)apps/dashboard/src/components/payments/checkout.tsx(3 hunks)apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--validate-code.test.ts(2 hunks)apps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.ts(3 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.test.{ts,tsx,js}
📄 CodeRabbit inference engine (AGENTS.md)
In tests, prefer .toMatchInlineSnapshot where possible; refer to snapshot-serializer.ts for snapshot formatting and handling of non-deterministic values
Files:
apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--validate-code.test.tsapps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Prefer ES6 Map over Record when representing key–value collections
Files:
apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--validate-code.test.tsapps/backend/src/app/api/latest/payments/purchases/validate-code/route.tsapps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.tsapps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsxapps/dashboard/src/app/(main)/purchase/[code]/page-client.tsxapps/backend/src/app/api/latest/payments/purchases/verification-code-handler.tsxapps/dashboard/src/components/payments/checkout.tsxapps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts
apps/backend/src/app/api/latest/**
📄 CodeRabbit inference engine (AGENTS.md)
apps/backend/src/app/api/latest/**: Organize backend API routes by resource under /api/latest (e.g., auth at /api/latest/auth/, users at /api/latest/users/, teams at /api/latest/teams/, oauth providers at /api/latest/oauth-providers/)
Use the custom route handler system in the backend to ensure consistent API responses
Files:
apps/backend/src/app/api/latest/payments/purchases/validate-code/route.tsapps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsxapps/backend/src/app/api/latest/payments/purchases/verification-code-handler.tsxapps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts
{apps/dashboard,apps/dev-launchpad,packages/stack-ui,packages/react}/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
For blocking alerts and errors in UI, do not use toast notifications; use alerts instead
Files:
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsxapps/dashboard/src/app/(main)/purchase/[code]/page-client.tsxapps/dashboard/src/components/payments/checkout.tsx
{apps/dashboard,apps/dev-launchpad,packages/stack-ui,packages/react}/**/*.{tsx,jsx,css}
📄 CodeRabbit inference engine (AGENTS.md)
Keep hover/click animations snappy; avoid pre-transition delays on hover and apply transitions after the action (e.g., fade-out on hover end)
Files:
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsxapps/dashboard/src/app/(main)/purchase/[code]/page-client.tsxapps/dashboard/src/components/payments/checkout.tsx
🧬 Code graph analysis (3)
apps/backend/src/app/api/latest/payments/purchases/validate-code/route.ts (1)
packages/stack-shared/src/schema-fields.ts (1)
yupBoolean(195-198)
apps/backend/src/app/api/latest/payments/purchases/verification-code-handler.tsx (1)
packages/stack-shared/src/schema-fields.ts (1)
yupBoolean(195-198)
apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts (2)
packages/stack-shared/src/utils/errors.tsx (1)
throwErr(10-19)apps/backend/src/lib/stripe.tsx (1)
getStackStripe(18-23)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: Vercel Agent Review
- GitHub Check: build (22.x)
- GitHub Check: all-good
- GitHub Check: docker
- GitHub Check: setup-tests
- GitHub Check: build (22.x)
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: restart-dev-and-test
- GitHub Check: lint_and_build (latest)
- GitHub Check: docker
- GitHub Check: Security Check
🔇 Additional comments (4)
apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx (1)
28-28: LGTM! Documentation clarification.The updated description accurately reflects that this is a Stack auth price ID rather than a direct Stripe price ID.
apps/dashboard/src/app/(main)/purchase/[code]/page-client.tsx (2)
22-22: LGTM! Type definition is clean.The
charges_enabledfield addition follows the existing snake_case convention and integrates cleanly with the ProductData type.
286-286: Backend validation endpoint always returnscharges_enabled—no issues found.Verification confirms the field is guaranteed in all successful responses:
- Response schema (line 47 of validate-code/route.ts) requires
charges_enabled: yupBoolean().defined()- Verification code data schema requires
chargesEnabled: yupBoolean().defined()- Value always sourced from Stripe's
accounts.retrieve()API, which always includes this field- All test snapshots confirm presence in 200 responses
- No code paths omit the field
The prop wiring is correct and safe.
apps/dashboard/src/components/payments/checkout.tsx (1)
27-27: LGTM! Prop addition is consistent.The
chargesEnabledprop is correctly typed and integrated into the component signature.Also applies to: 37-37
| chargesEnabled: yupBoolean().defined(), | ||
| }), |
There was a problem hiding this comment.
Keep verification-code data backward compatible.
Marking chargesEnabled as .defined() will reject every existing verification code created before this deploy, because those documents never had the field. We need to coerce missing values (default false) so legacy codes still validate until they expire.
- chargesEnabled: yupBoolean().defined(),
+ chargesEnabled: yupBoolean()
+ .transform((value) => value ?? false)
+ .defined(),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| chargesEnabled: yupBoolean().defined(), | |
| }), | |
| chargesEnabled: yupBoolean() | |
| .transform((value) => value ?? false) | |
| .defined(), | |
| }), |
🤖 Prompt for AI Agents
In
apps/backend/src/app/api/latest/payments/purchases/verification-code-handler.tsx
around lines 15-16, the schema marks chargesEnabled as .defined() which will
reject existing verification code documents that lack that field; replace the
.defined() with a coercion/default so missing values validate as false (e.g. use
yup.boolean().default(false) or yup.boolean().transform((v) => (v === undefined
? false : v)).default(false)), and ensure validation/casting is applied (use
schema.validate or schema.cast as appropriate) so legacy documents without
chargesEnabled are treated as false.
| const handleToggleTestMode = async (enabled: boolean) => { | ||
| setIsUpdatingTestMode(true); | ||
| try { | ||
| await project.updateConfig({ "payments.testMode": enabled }); | ||
| toast({ title: enabled ? "Test mode enabled" : "Test mode disabled" }); | ||
| } catch (_error) { | ||
| toast({ title: "Failed to update test mode", variant: "destructive" }); | ||
| } finally { | ||
| setIsUpdatingTestMode(false); | ||
| } | ||
| await project.updateConfig({ "payments.testMode": enabled }); | ||
| toast({ title: enabled ? "Test mode enabled" : "Test mode disabled" }); | ||
| }; |
There was a problem hiding this comment.
Add error handling to prevent unhandled promise rejections.
The removal of try/catch error handling means failures in project.updateConfig will result in unhandled promise rejections with no user feedback. Additionally, the toggle's visual state may become inconsistent with the actual server state if the update fails.
Apply this diff to restore error handling:
const handleToggleTestMode = async (enabled: boolean) => {
+ try {
await project.updateConfig({ "payments.testMode": enabled });
toast({ title: enabled ? "Test mode enabled" : "Test mode disabled" });
+ } catch (error) {
+ toast({
+ title: "Failed to update test mode",
+ description: error instanceof Error ? error.message : "An error occurred",
+ variant: "destructive"
+ });
+ }
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleToggleTestMode = async (enabled: boolean) => { | |
| setIsUpdatingTestMode(true); | |
| try { | |
| await project.updateConfig({ "payments.testMode": enabled }); | |
| toast({ title: enabled ? "Test mode enabled" : "Test mode disabled" }); | |
| } catch (_error) { | |
| toast({ title: "Failed to update test mode", variant: "destructive" }); | |
| } finally { | |
| setIsUpdatingTestMode(false); | |
| } | |
| await project.updateConfig({ "payments.testMode": enabled }); | |
| toast({ title: enabled ? "Test mode enabled" : "Test mode disabled" }); | |
| }; | |
| const handleToggleTestMode = async (enabled: boolean) => { | |
| try { | |
| await project.updateConfig({ "payments.testMode": enabled }); | |
| toast({ title: enabled ? "Test mode enabled" : "Test mode disabled" }); | |
| } catch (error) { | |
| toast({ | |
| title: "Failed to update test mode", | |
| description: error instanceof Error ? error.message : "An error occurred", | |
| variant: "destructive" | |
| }); | |
| } | |
| }; |
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx
around lines 1569 to 1572, wrap the async updateConfig call in a try/catch so
failures don't produce unhandled promise rejections: call await
project.updateConfig(...) inside try, on success show the existing success
toast, and in catch show an error toast with the error message; also ensure the
toggle's visual state is reverted when the update fails (e.g., update any local
state or call the setter to restore the previous value) so the UI remains
consistent with the server.
High-level PR Summary
This PR adds a warning mechanism for incomplete Stripe payments setup by checking if
charges_enabledis true on the connected Stripe account. The backend now retrieves and passes thecharges_enabledstatus through the purchase flow, and the frontend checkout form displays an error message when payments are not fully enabled, preventing users from attempting purchases on misconfigured accounts. Additionally, minor cleanup was performed including removing unused test mode toggle state management and fixing a description typo.⏱️ Estimated Review Time: 15-30 minutes
💡 Review Order Suggestion
apps/backend/src/app/api/latest/payments/purchases/verification-code-handler.tsxapps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.tsapps/backend/src/app/api/latest/payments/purchases/validate-code/route.tsapps/dashboard/src/components/payments/checkout.tsxapps/dashboard/src/app/(main)/purchase/[code]/page-client.tsxapps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.tsapps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--validate-code.test.tsapps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsxapps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsxSummary by CodeRabbit
New Features
Documentation
Tests