Skip to content

fix: verify checkout session metadata in team upgrade endpoint#28874

Open
pedroccastro wants to merge 1 commit intomainfrom
fix/team-upgrade-session-validation
Open

fix: verify checkout session metadata in team upgrade endpoint#28874
pedroccastro wants to merge 1 commit intomainfrom
fix/team-upgrade-session-validation

Conversation

@pedroccastro
Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a consistency check between the checkout session metadata and the team ID in the URL path for the team upgrade callback. Ensures the processed session was created for the specific team being upgraded.

Changes

  • When checkoutSession.metadata.teamId is present, verify it matches the team ID in the URL before processing billing mutations
  • Check is conditional to preserve compatibility with flows where teamId is not set in session metadata (API v2 uses pendingPaymentTeamId; legacy sessions predate the field)

How should this be tested?

  1. Team upgrade via Stripe checkout → complete payment → redirect succeeds normally
  2. Verify existing integrations that don't set teamId in session metadata continue to work

Mandatory Tasks

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • N/A I have updated the developer docs in /docs if this PR makes changes that would require a documentation change.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 14, 2026

📝 Walkthrough

Walkthrough

A validation step was added to the team upgrade API route to verify that the team identifier stored in the Stripe checkout session metadata matches the team identifier from the URL route parameter. When a checkout session is confirmed as paid, the handler now extracts the team ID from the session metadata and compares it against the requested team ID. If the metadata contains a team ID that differs from the route parameter, the request fails with an HTTP 400 error response. The change adds six lines and does not modify other control flow logic.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a validation step to verify checkout session metadata in the team upgrade endpoint.
Description check ✅ Passed The description is directly related to the changeset, explaining what the PR does, why it matters, how to test it, and confirming automated tests are in place.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/team-upgrade-session-validation

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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/app/api/teams/`[team]/upgrade/route.ts:
- Around line 49-51: The current guard uses a truthy check on
checkoutSession.metadata?.teamId (sessionTeamId) which treats empty string as
absent and skips validation; update the logic in route.ts to check for the
presence of the key (e.g., metadata hasOwnProperty or sessionTeamId !==
undefined) and then validate that sessionTeamId is a valid integer (use
Number/parseInt and Number.isInteger or /^\d+$/) before comparing to id; if the
value is missing, non-numeric, or not equal to id, throw the existing
HttpError({ statusCode: 400, message: "Checkout session does not match team" })
so malformed values are rejected and only valid integer team IDs are accepted.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3f8cb432-a70e-4119-8d6a-f02a4e51bd46

📥 Commits

Reviewing files that changed from the base of the PR and between 2911168 and 6c211e5.

📒 Files selected for processing (1)
  • apps/web/app/api/teams/[team]/upgrade/route.ts

Comment on lines +49 to +51
const sessionTeamId = checkoutSession.metadata?.teamId;
if (sessionTeamId && Number(sessionTeamId) !== id) {
throw new HttpError({ statusCode: 400, message: "Checkout session does not match team" });
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Apr 14, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use presence check for teamId and reject malformed values.

On Line 50, the truthy guard (if (sessionTeamId && ...)) skips validation when teamId is an empty string, even though metadata is present. That weakens the new consistency check. Validate when the key is defined, and fail on non-integer values.

Suggested fix
-    const sessionTeamId = checkoutSession.metadata?.teamId;
-    if (sessionTeamId && Number(sessionTeamId) !== id) {
-      throw new HttpError({ statusCode: 400, message: "Checkout session does not match team" });
-    }
+    const sessionTeamId = checkoutSession.metadata?.teamId;
+    if (sessionTeamId !== undefined) {
+      const parsedSessionTeamId = Number.parseInt(sessionTeamId, 10);
+      if (!Number.isInteger(parsedSessionTeamId) || parsedSessionTeamId !== id) {
+        throw new HttpError({ statusCode: 400, message: "Checkout session does not match team" });
+      }
+    }
📝 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.

Suggested change
const sessionTeamId = checkoutSession.metadata?.teamId;
if (sessionTeamId && Number(sessionTeamId) !== id) {
throw new HttpError({ statusCode: 400, message: "Checkout session does not match team" });
const sessionTeamId = checkoutSession.metadata?.teamId;
if (sessionTeamId !== undefined) {
const parsedSessionTeamId = Number.parseInt(sessionTeamId, 10);
if (!Number.isInteger(parsedSessionTeamId) || parsedSessionTeamId !== id) {
throw new HttpError({ statusCode: 400, message: "Checkout session does not match team" });
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/api/teams/`[team]/upgrade/route.ts around lines 49 - 51, The
current guard uses a truthy check on checkoutSession.metadata?.teamId
(sessionTeamId) which treats empty string as absent and skips validation; update
the logic in route.ts to check for the presence of the key (e.g., metadata
hasOwnProperty or sessionTeamId !== undefined) and then validate that
sessionTeamId is a valid integer (use Number/parseInt and Number.isInteger or
/^\d+$/) before comparing to id; if the value is missing, non-numeric, or not
equal to id, throw the existing HttpError({ statusCode: 400, message: "Checkout
session does not match team" }) so malformed values are rejected and only valid
integer team IDs are accepted.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@pedroccastro sir, should we address this?

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.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants