Skip to content

feat: open billing portal via Recoup API; remove local portal route#1730

Open
ahmednahima0-beep wants to merge 3 commits intotestfrom
feature/chat-subscription-portal
Open

feat: open billing portal via Recoup API; remove local portal route#1730
ahmednahima0-beep wants to merge 3 commits intotestfrom
feature/chat-subscription-portal

Conversation

@ahmednahima0-beep
Copy link
Copy Markdown
Collaborator

@ahmednahima0-beep ahmednahima0-beep commented May 4, 2026

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/portal using the Privy access token, with the account inferred from auth. Improved error handling and portal link security; removed the local Stripe portal route.

  • Refactors
    • Updated useSubscribeClick to 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.
    • Reworked createClientPortalSession to POST to ${getClientApiBaseUrl()}/api/subscriptions/portal with Authorization: Bearer <token> and { returnUrl }, parse error responses, validate the url, and open it with noopener,noreferrer; returns errors to the caller.
    • Deleted app/api/stripe/portal/create and lib/stripe/createBillingPortalSession.

Written for commit 0304bb9. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Subscribed users can open the billing portal from the app to manage subscriptions and billing.
  • Improvements

    • Portal access now uses a secure client token flow and validates responses before opening links.
    • Click handling reliably prompts sign-in when needed and shows clearer error toasts for portal/checkout issues.

…rts. Update client portal session creation to use access tokens for authentication and adjust API endpoint accordingly.
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chat Ready Ready Preview May 5, 2026 7:52pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

Warning

Rate limit exceeded

@ahmednahima0-beep has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 43 minutes and 5 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 282f39e6-3051-4034-a727-e6e29455383d

📥 Commits

Reviewing files that changed from the base of the PR and between bcc831f and 0304bb9.

📒 Files selected for processing (2)
  • hooks/useSubscribeClick.ts
  • lib/stripe/createClientPortalSession.ts
📝 Walkthrough

Walkthrough

Server-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.

Changes

Migrate Stripe Billing Portal to Client-Side Access Token Pattern

Layer / File(s) Summary
API Removal
app/api/stripe/portal/create/route.ts, lib/stripe/createBillingPortalSession.ts
Deleted server-side POST route and server helper that created Stripe billing portal sessions using accountId.
Client API Contract
lib/stripe/createClientPortalSession.ts
Changed signature to accept accessToken and POST to ${getClientApiBaseUrl()}/api/subscriptions/portal with Authorization: Bearer. Adds runtime validation of parsed JSON and explicit error extraction; opens validated url on success.
Hook Wiring / UX
hooks/useSubscribeClick.ts
Hook becomes async, integrates usePrivy() to call getAccessToken(), early-returns with toast when token or account_id missing, calls createClientPortalSession(accessToken) for subscribed users; non-subscribed checkout flow unchanged.
Error Handling / Logging
lib/stripe/createClientPortalSession.ts, hooks/useSubscribeClick.ts
Errors are normalized from { error: string } payloads, logged, and surfaced to the user via toasts instead of silent failures.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • sweetmantech

Poem

🌊 From server gates to client flow,
Privy tokens guide where sessions go,
Bearer headers call the shore,
Portal links fly out the door —
Small refactor, smoother row.

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/chat-subscription-portal

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1cfd528 and 9f35fe7.

📒 Files selected for processing (4)
  • app/api/stripe/portal/create/route.ts
  • hooks/useSubscribeClick.ts
  • lib/stripe/createBillingPortalSession.ts
  • lib/stripe/createClientPortalSession.ts
💤 Files with no reviewable changes (2)
  • lib/stripe/createBillingPortalSession.ts
  • app/api/stripe/portal/create/route.ts

Comment thread hooks/useSubscribeClick.ts Outdated
Comment thread hooks/useSubscribeClick.ts Outdated
Comment thread lib/stripe/createClientPortalSession.ts Outdated
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 4 files

Confidence score: 3/5

  • There is concrete user-impact risk in hooks/useSubscribeClick.ts: gating the portal flow on userData.account_id may incorrectly block subscribed users from accessing the portal despite Privy bearer-token auth.
  • lib/stripe/createClientPortalSession.ts opens an external URL with "_blank" without noopener,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.ts and lib/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.

Comment thread lib/stripe/createClientPortalSession.ts Outdated
Comment on lines +12 to 21
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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

already implemented

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for the update!

… for access token retrieval in useSubscribeClick hook. Notify users when account data is loading or when sign-in is required.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 1 file (changes from recent commits).

Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9f35fe7 and bcc831f.

📒 Files selected for processing (1)
  • hooks/useSubscribeClick.ts

Comment thread 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.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 2 files (changes from recent commits).

Requires human review: Auto-approval blocked by 1 unresolved issue from previous reviews.

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