feat: open billing portal via Recoup API; remove local portal route#1730
feat: open billing portal via Recoup API; remove local portal route#1730ahmednahima0-beep wants to merge 3 commits intotestfrom
Conversation
…rts. Update client portal session creation to use access tokens for authentication and adjust API endpoint accordingly.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughServer-side Stripe billing-portal route and helper were removed; client-side portal creation now uses Privy access tokens and calls the external subscriptions API with Bearer auth. The subscribe hook was updated to obtain the token and call the new client portal helper. ChangesMigrate Stripe Billing Portal to Client-Side Access Token Pattern
Sequence Diagram(s)sequenceDiagram
actor User
participant Hook as useSubscribeClick
participant Privy as usePrivy
participant Client as createClientPortalSession
participant API as External API
User->>Hook: Click subscribe (already subscribed)
activate Hook
Hook->>Privy: getAccessToken()
activate Privy
Privy-->>Hook: accessToken
deactivate Privy
alt Token exists
Hook->>Client: createClientPortalSession(accessToken)
activate Client
Client->>API: POST /api/subscriptions/portal (Bearer)
activate API
API-->>Client: { url: string } / { error: string }
deactivate API
alt Valid response (url)
Client->>Client: window.open(url)
else Error / invalid payload
Client-->>Hook: return { error }
end
deactivate Client
else No token
Hook-->>User: show signin toast and return
end
deactivate Hook
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@hooks/useSubscribeClick.ts`:
- Around line 13-21: Remove the top-level account_id guard so subscribed users
can use the portal flow without account_id; instead, check for
userData?.account_id only when starting a checkout session. Concretely, in
useSubscribeClick (hooks/useSubscribeClick.ts) delete the initial `if
(!userData?.account_id) return;`, keep the `if (isSubscribed)` branch unchanged
(it should use getAccessToken() and createClientPortalSession), and wrap the
call to `createClientCheckoutSession(userData.account_id)` in an `if
(!userData?.account_id) return;` (or throw/handle) so checkout only runs when
account_id exists and avoid passing undefined to createClientCheckoutSession.
Ensure any TypeScript non-null assertions or type checks are applied when
calling createClientCheckoutSession.
- Around line 15-18: The subscribed click path currently silently returns when
there's no token or when createClientPortalSession is fired without awaiting;
update the handler around isSubscribed to await getAccessToken() and, if it
returns falsy, call the shared error/toast handler (e.g., showToast or setError)
instead of returning; also await createClientPortalSession(accessToken), catch
any thrown errors from createClientPortalSession and propagate them to the same
error/toast handler so the user sees a failure state; locate and modify the
logic in the isSubscribed branch in useSubscribeClick to perform these awaits
and error handling.
In `@lib/stripe/createClientPortalSession.ts`:
- Line 45: In createClientPortalSession, change the window.open call that
currently uses window.open((data as { url: string }).url, "_blank") to open the
API-supplied URL with no access back to the opener by passing the feature flags
and a fallback nulling of opener; e.g. call window.open((data as { url: string
}).url, "_blank", "noopener,noreferrer") and, for browsers that ignore the
feature string, capture the returned window and set its opener to null (const
newWin = window.open(...); if (newWin) newWin.opener = null).
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 9b22b517-986d-40d5-822d-38fac9c7ec7a
📒 Files selected for processing (4)
app/api/stripe/portal/create/route.tshooks/useSubscribeClick.tslib/stripe/createBillingPortalSession.tslib/stripe/createClientPortalSession.ts
💤 Files with no reviewable changes (2)
- lib/stripe/createBillingPortalSession.ts
- app/api/stripe/portal/create/route.ts
There was a problem hiding this comment.
2 issues found across 4 files
Confidence score: 3/5
- There is concrete user-impact risk in
hooks/useSubscribeClick.ts: gating the portal flow onuserData.account_idmay incorrectly block subscribed users from accessing the portal despite Privy bearer-token auth. lib/stripe/createClientPortalSession.tsopens an external URL with"_blank"withoutnoopener,noreferrer, which leaves a credible reverse-tabnabbing security gap.- Given two medium-severity, high-confidence issues (one functional regression risk and one security hardening gap), this carries some merge risk until addressed.
- Pay close attention to
hooks/useSubscribeClick.tsandlib/stripe/createClientPortalSession.ts- portal access gating and external-link safety need fixes.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/stripe/createClientPortalSession.ts">
<violation number="1" location="lib/stripe/createClientPortalSession.ts:45">
P2: Opening an external URL with `"_blank"` should include `noopener,noreferrer` to prevent reverse tabnabbing via `window.opener`.</violation>
</file>
<file name="hooks/useSubscribeClick.ts">
<violation number="1" location="hooks/useSubscribeClick.ts:12">
P2: The new portal flow is still gated by `userData.account_id`, which can block subscribed users from opening the portal even though portal auth now comes from the Privy bearer token.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| const handleClick = async () => { | ||
| if (!userData?.account_id) return; | ||
|
|
||
| if (isSubscribed) { | ||
| createClientPortalSession(userData.account_id); | ||
| const accessToken = await getAccessToken(); | ||
| if (!accessToken) return; | ||
| void createClientPortalSession(accessToken); | ||
| return; | ||
| } | ||
| createClientCheckoutSession(userData.account_id); |
There was a problem hiding this comment.
P2: The new portal flow is still gated by userData.account_id, which can block subscribed users from opening the portal even though portal auth now comes from the Privy bearer token.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At hooks/useSubscribeClick.ts, line 12:
<comment>The new portal flow is still gated by `userData.account_id`, which can block subscribed users from opening the portal even though portal auth now comes from the Privy bearer token.</comment>
<file context>
@@ -1,17 +1,21 @@
const { isSubscribed } = usePaymentProvider();
- const handleClick = () => {
+ const handleClick = async () => {
if (!userData?.account_id) return;
</file context>
There was a problem hiding this comment.
already implemented
… for access token retrieval in useSubscribeClick hook. Notify users when account data is loading or when sign-in is required.
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 `@hooks/useSubscribeClick.ts`:
- Around line 13-28: The subscribed branch in handleClick must catch thrown
errors from getAccessToken: wrap the logic that calls getAccessToken() and
subsequent createClientPortalSession(accessToken) in a try/catch so
token-refresh/network errors are handled; on catch, call toast.error with an
appropriate user-facing message (e.g., prompt to sign in or a generic "Could not
retrieve access token") and return, preserving the existing handling for
accessToken === null and the existing createClientPortalSession error handling.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 408d0f9c-43ec-40c0-a106-bcb142924f51
📒 Files selected for processing (1)
hooks/useSubscribeClick.ts
…ortal session creation. Added try-catch for access token retrieval and refined error messages for better user feedback. Updated createClientPortalSession to include 'noopener,noreferrer' in window.open for security.
Manage subscription now calls POST /api/subscriptions/portal on the Recoup API with the Privy Bearer token and { returnUrl } only (account comes from auth). Removes the redundant app/api/stripe/portal/create handler and createBillingPortalSession so portal creation lives solely in the API, matching OpenAPI and keeping the chat surface minimal.
Summary by cubic
Manage Subscription now opens the billing portal via the Recoup
/api/subscriptions/portalusing the Privy access token, with the account inferred from auth. Improved error handling and portal link security; removed the local Stripe portal route.useSubscribeClickto fetch the access token via@privy-io/react-auth, wrap token retrieval in try/catch, and show toasts for sign-in required or when account data is still loading; checkout flow unchanged.createClientPortalSessionto POST to${getClientApiBaseUrl()}/api/subscriptions/portalwithAuthorization: Bearer <token>and{ returnUrl }, parseerrorresponses, validate theurl, and open it withnoopener,noreferrer; returns errors to the caller.app/api/stripe/portal/createandlib/stripe/createBillingPortalSession.Written for commit 0304bb9. Summary will update on new commits.
Summary by CodeRabbit
New Features
Improvements