Skip to content

Conversation

devkiran
Copy link
Collaborator

@devkiran devkiran commented Aug 28, 2025

Summary by CodeRabbit

  • New Features

    • Webhooks now use explicit test/sandbox/live modes with separate endpoints; handlers, utilities, and scripts accept and propagate a Stripe mode value.
  • Chores

    • Bumped Stripe app to v0.0.18; manifest adds sandbox install compatibility, distribution type, and connect permissions, and removes the empty post-install URL.
  • Documentation

    • Publish instructions revised to a clearer, authenticated 5-step flow.

Copy link
Contributor

vercel bot commented Aug 28, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Oct 9, 2025 6:11pm

Copy link
Contributor

coderabbitai bot commented Aug 28, 2025

Walkthrough

Adds a Stripe "mode" concept (test/sandbox/live) across webhook routing, client creation, handlers, utilities, scripts, and call sites; introduces a /sandbox re-exported webhook route; updates Stripe app manifest and publish instructions; replaces boolean livemode with mode: StripeMode and propagates the new parameter through affected functions.

Changes

Cohort / File(s) Summary
Stripe app manifest
packages/stripe-app/stripe-app.json
Version bumped 0.0.120.0.18; added connect_permissions: null, sandbox_install_compatible: true, distribution_type: "public"; removed empty post_install_action.url.
Publish instructions
packages/stripe-app/README.md
Rewrote "Publish new version" flow to a 5-step sequence adding explicit stripe login and clarified directory/upload/publish steps.
Sandbox webhook route (re-export)
apps/web/app/(ee)/api/stripe/integration/webhook/sandbox/route.ts
New file re-exporting POST from the shared webhook route to expose /sandbox.
Webhook route
apps/web/app/(ee)/api/stripe/integration/webhook/route.ts
Resolve mode from request pathname (/test, /sandbox, else live); select mode-specific webhook secret; require sig + secret; pass mode: StripeMode to handlers.
Webhook handlers
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts, .../invoice-paid.ts, .../charge-refunded.ts
Updated signatures to (event: Stripe.Event, mode: StripeMode) and replaced event.livemode usage with mode, propagating mode to downstream calls.
Webhook utilities
apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-connected-customer.ts, .../get-promotion-code.ts, .../get-subscription-product-id.ts
Replaced boolean livemode parameters with mode: StripeMode; call stripeAppClient({ mode }); updated imports/signatures and minor return behavior adjustments.
Stripe client & types
apps/web/lib/stripe/index.ts, apps/web/lib/types.ts
Added `export type StripeMode = "test"
Call sites switching to mode
apps/web/lib/*, apps/web/scripts/stripe/*
Multiple scripts and libraries updated to pass `mode: "live"

Sequence Diagram(s)

sequenceDiagram
  participant Stripe as Stripe (external)
  participant Sandbox as /webhook/sandbox
  participant Shared as /webhook (shared POST)
  participant Route as Route (mode resolver)
  participant Handlers as Webhook Handlers
  rect rgb(245,250,255)
    Note right of Sandbox: lightweight re-export → Shared.POST
  end
  Stripe->>Sandbox: POST /webhook/sandbox (payload + sig)
  Sandbox->>Shared: forward POST
  Shared->>Route: parse pathname → determine mode ("test"/"sandbox"/"live")
  Route->>Route: select webhookSecret for mode
  Route->>Route: verify signature with webhookSecret
  alt signature valid
    Route->>Handlers: invoke handler(event, mode)
    Handlers-->>Route: returns success
    Route-->>Stripe: 200 OK
  else invalid
    Route-->>Stripe: 400/401
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • devkiran

Poem

I hop through manifests and flip a mode or two,
A sandbox doorway opens where webhooks once flew.
I pass a mode to handlers, keys tucked in my pack,
Logs trail like carrot crumbs — no webhook left off track. 🥕🐇

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly identifies the main objective of the pull request, which is adding support for the Stripe sandbox environment for app testing. It is concise and specific, providing reviewers an immediate understanding of the core change. There is no extraneous detail or vague wording.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch stripe-app-sandbox-testing

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1350451 and 3581d9f.

📒 Files selected for processing (1)
  • apps/web/scripts/fillout/export-stripe-invoices.ts (1 hunks)
⏰ 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). (1)
  • GitHub Check: build
🔇 Additional comments (1)
apps/web/scripts/fillout/export-stripe-invoices.ts (1)

50-52: LGTM! Correct migration to mode: "live".

The change correctly implements the migration from boolean livemode to string-based mode. Using "live" mode is appropriate for this production data export script.


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.

@devkiran devkiran requested a review from steven-tey October 8, 2025 15:49
@devkiran devkiran marked this pull request as ready for review October 8, 2025 15:49
Copy link
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7dc8c9 and e3bcdd6.

📒 Files selected for processing (2)
  • apps/web/app/(ee)/api/stripe/integration/webhook/sandbox/route.ts (1 hunks)
  • packages/stripe-app/stripe-app.json (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/stripe-app/stripe-app.json
⏰ 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). (1)
  • GitHub Check: build
🔇 Additional comments (1)
apps/web/app/(ee)/api/stripe/integration/webhook/sandbox/route.ts (1)

16-16: LGTM!

The re-export of the POST handler is clean and enables code reuse for the sandbox endpoint.

…g. Updated webhook functions and utility methods to accept mode parameter, ensuring consistent behavior across test, sandbox, and live environments.
Copy link
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: 0

♻️ Duplicate comments (5)
apps/web/lib/firstpromoter/update-stripe-customers.ts (1)

11-13: Same conditional pattern as in create-discount.ts.

See the review comment on apps/web/lib/actions/partners/create-discount.ts (lines 21-23) regarding the VERCEL_ENV conditional pattern.

apps/web/lib/tolt/update-stripe-customers.ts (1)

11-13: Same conditional pattern as in create-discount.ts.

See the review comment on apps/web/lib/actions/partners/create-discount.ts (lines 21-23) regarding the VERCEL_ENV conditional pattern.

apps/web/lib/stripe/create-stripe-discount-code.ts (1)

5-7: Same conditional pattern as in create-discount.ts.

See the review comment on apps/web/lib/actions/partners/create-discount.ts (lines 21-23) regarding the VERCEL_ENV conditional pattern.

apps/web/lib/partnerstack/update-stripe-customers.ts (1)

13-15: Same conditional pattern as in create-discount.ts.

See the review comment on apps/web/lib/actions/partners/create-discount.ts (lines 21-23) regarding the VERCEL_ENV conditional pattern.

apps/web/lib/stripe/disable-stripe-discount-code.ts (1)

3-5: Same conditional pattern as in create-discount.ts.

See the review comment on apps/web/lib/actions/partners/create-discount.ts (lines 21-23) regarding the VERCEL_ENV conditional pattern.

🧹 Nitpick comments (1)
apps/web/lib/actions/partners/create-discount.ts (1)

21-23: Clarify the VERCEL_ENV conditional pattern.

The conditional spread ...(process.env.VERCEL_ENV && { mode: "live" }) means mode is only explicitly set when VERCEL_ENV is present. When VERCEL_ENV is not set (e.g., local development), mode is omitted entirely, relying on stripeAppClient's default behavior.

If stripeAppClient already defaults to "live" when mode is omitted, this conditional adds complexity without changing behavior. If the intent is to use a different mode in local development (e.g., "test"), consider making this explicit.

Verify the intent and consider one of these alternatives:

Option 1: If the default is always "live", remove the conditional:

-const stripe = stripeAppClient({
-  ...(process.env.VERCEL_ENV && { mode: "live" }),
-});
+const stripe = stripeAppClient({ mode: "live" });

Option 2: If local dev should use "test" mode explicitly:

-const stripe = stripeAppClient({
-  ...(process.env.VERCEL_ENV && { mode: "live" }),
-});
+const stripe = stripeAppClient({
+  mode: process.env.VERCEL_ENV ? "live" : "test",
+});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89f93a6 and e94becb.

📒 Files selected for processing (15)
  • apps/web/app/(ee)/api/stripe/integration/webhook/charge-refunded.ts (1 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (7 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (3 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/route.ts (3 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-connected-customer.ts (1 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-promotion-code.ts (1 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-subscription-product-id.ts (1 hunks)
  • apps/web/lib/actions/partners/create-discount.ts (1 hunks)
  • apps/web/lib/firstpromoter/update-stripe-customers.ts (1 hunks)
  • apps/web/lib/partnerstack/update-stripe-customers.ts (1 hunks)
  • apps/web/lib/stripe/create-stripe-discount-code.ts (1 hunks)
  • apps/web/lib/stripe/disable-stripe-discount-code.ts (1 hunks)
  • apps/web/lib/stripe/index.ts (2 hunks)
  • apps/web/lib/tolt/update-stripe-customers.ts (1 hunks)
  • apps/web/lib/types.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
apps/web/lib/stripe/index.ts (1)
apps/web/lib/types.ts (1)
  • StripeMode (652-652)
apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (1)
apps/web/lib/types.ts (1)
  • StripeMode (652-652)
apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-connected-customer.ts (2)
apps/web/lib/types.ts (1)
  • StripeMode (652-652)
apps/web/lib/stripe/index.ts (1)
  • stripeAppClient (13-35)
apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-subscription-product-id.ts (2)
apps/web/lib/types.ts (1)
  • StripeMode (652-652)
apps/web/lib/stripe/index.ts (1)
  • stripeAppClient (13-35)
apps/web/app/(ee)/api/stripe/integration/webhook/charge-refunded.ts (2)
apps/web/lib/types.ts (1)
  • StripeMode (652-652)
apps/web/lib/stripe/index.ts (2)
  • stripe (4-10)
  • stripeAppClient (13-35)
apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-promotion-code.ts (2)
apps/web/lib/types.ts (1)
  • StripeMode (652-652)
apps/web/lib/stripe/index.ts (1)
  • stripeAppClient (13-35)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (3)
packages/ui/src/icons/payout-platforms/stripe.tsx (1)
  • Stripe (3-19)
apps/web/lib/types.ts (1)
  • StripeMode (652-652)
apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-promotion-code.ts (1)
  • getPromotionCode (4-28)
apps/web/app/(ee)/api/stripe/integration/webhook/route.ts (4)
apps/web/lib/types.ts (1)
  • StripeMode (652-652)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (1)
  • checkoutSessionCompleted (38-510)
apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (1)
  • invoicePaid (19-265)
apps/web/app/(ee)/api/stripe/integration/webhook/charge-refunded.ts (1)
  • chargeRefunded (8-117)
⏰ 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). (1)
  • GitHub Check: build
🔇 Additional comments (12)
apps/web/lib/types.ts (1)

651-652: LGTM! Clean type definition for mode-based Stripe configuration.

The StripeMode type clearly defines the three supported Stripe environments. This enables the mode-based configuration pattern introduced across webhook handlers and client initialization.

apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (1)

7-7: All invoicePaid calls updated to pass mode. The breaking-change signature is fully covered by route.ts.

apps/web/app/(ee)/api/stripe/integration/webhook/charge-refunded.ts (1)

3-3: LGTM! Mode parameter correctly threaded through.

The import, function signature update, and propagation to stripeAppClient are consistent with the mode-based refactor.

Also applies to: 8-8, 13-13

apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-subscription-product-id.ts (1)

2-2: LGTM! Mode parameter correctly integrated.

The function signature update and mode propagation to stripeAppClient align with the mode-based approach.

Also applies to: 7-7, 11-11, 19-19

apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-connected-customer.ts (1)

2-2: LGTM! Mode parameter correctly integrated.

The function signature and mode propagation are consistent with the refactor.

Also applies to: 7-7, 11-11, 19-19

apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-promotion-code.ts (1)

2-2: LGTM! Mode parameter correctly integrated.

The function signature update and inline mode propagation are consistent with the refactor.

Also applies to: 7-7, 11-11, 18-23

apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (3)

15-20: LGTM! Function signature correctly updated.

The import and function signature updates are consistent with the mode-based refactor.

Also applies to: 38-41


191-191: LGTM! Mode correctly propagated to helper functions.

The mode parameter is correctly passed to attributeViaPromoCode and getConnectedCustomer calls.

Also applies to: 218-218, 235-235


416-416: LGTM! Mode correctly propagated in program flow.

The mode parameter is correctly passed to getSubscriptionProductId, the attributeViaPromoCode helper function signature is updated, and getPromotionCode receives mode.

Also applies to: 515-520, 527-527

apps/web/app/(ee)/api/stripe/integration/webhook/route.ts (2)

2-2: LGTM! Path-based mode resolution is correctly implemented.

The logic to determine mode from the request pathname and select the appropriate webhook secret is sound and follows a clear pattern.

Ensure that webhook secrets are configured for all modes in your deployment environment:

  • STRIPE_APP_WEBHOOK_SECRET_TEST for test mode
  • STRIPE_APP_WEBHOOK_SECRET_SANDBOX for sandbox mode
  • STRIPE_APP_WEBHOOK_SECRET for live mode

You can verify this with the script provided in the review for apps/web/lib/stripe/index.ts.

Also applies to: 33-45


80-80: LGTM! Mode correctly propagated to webhook handlers.

The mode parameter is correctly passed to the three handlers that require it: checkoutSessionCompleted, invoicePaid, and chargeRefunded. Other handlers that don't use Stripe app client calls correctly omit the parameter.

Also applies to: 83-83, 86-86

apps/web/lib/stripe/index.ts (1)

13-35: Stripe secret keys are not configured – stripeAppClient will always throw

None of STRIPE_APP_SECRET_KEY_TEST, STRIPE_APP_SECRET_KEY_SANDBOX, or STRIPE_APP_SECRET_KEY is set in your environment; define these variables in your test, sandbox, and live deployments to prevent runtime errors.

Copy link
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/scripts/stripe/backfill-stripe-webhook-events.ts (1)

29-38: Make Stripe mode configurable for operational flexibility.

Hardcoding mode: "test" in a backfill script is risky because:

  • Backfill scripts typically operate on production data to fix data issues
  • The script queries the production database (lines 8-20), but searches test Stripe customers
  • This creates a mismatch: production DB records will never find corresponding test Stripe customers
  • If someone replaces the placeholder stripeAccountId with a real production account ID, the script will fail silently or produce incorrect results

Make the mode configurable via an environment variable or command-line argument to match the target environment:

+const stripeMode = (process.env.STRIPE_MODE || "live") as "test" | "sandbox" | "live";
+
 async function main() {
   const customers = await prisma.customer.findMany({
     // ...
   });

   await Promise.allSettled(
     customers.map(async (customer) => {
       if (!customer.email) return;
       if (customer.stripeCustomerId) return;

       const stripeCustomer = await stripeAppClient({
-        mode: "test",
+        mode: stripeMode,
       }).customers.list(
🧹 Nitpick comments (2)
apps/web/scripts/stripe/search-customers.ts (1)

7-9: LGTM! Consider making the mode configurable for flexibility.

The change to mode: "test" correctly aligns with the new mode-based Stripe client configuration. For a development script, hardcoding "test" mode is reasonable.

To improve flexibility when testing across different Stripe environments (test/sandbox/live), consider making the mode configurable via an environment variable:

+const mode = (process.env.STRIPE_MODE as "test" | "sandbox" | "live") || "test";
+
 const stripeCustomers = await stripeAppClient({
-  mode: "test",
+  mode,
 }).customers.search(
apps/web/scripts/stripe/get-connected-customer.ts (1)

5-9: "test" is valid—consider making mode configurable via an environment variable or CLI argument (optional).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e94becb and 7c0a779.

📒 Files selected for processing (6)
  • apps/web/app/(ee)/api/stripe/integration/webhook/sandbox/route.ts (1 hunks)
  • apps/web/scripts/stripe/backfill-stripe-webhook-events.ts (1 hunks)
  • apps/web/scripts/stripe/get-connected-customer.ts (1 hunks)
  • apps/web/scripts/stripe/search-customers.ts (1 hunks)
  • apps/web/scripts/stripe/update-stripe-customers.ts (1 hunks)
  • packages/stripe-app/stripe-app.json (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/(ee)/api/stripe/integration/webhook/sandbox/route.ts
🔇 Additional comments (1)
apps/web/scripts/stripe/update-stripe-customers.ts (1)

34-43: Outdated review – ignore suggestion
The snippet in apps/web/scripts/stripe/update-stripe-customers.ts no longer matches (it uses livemode: true while stripeAppClient accepts mode), so the original recommendation doesn’t apply.

Likely an incorrect or invalid review comment.

Copy link
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c0a779 and 58c7137.

📒 Files selected for processing (1)
  • apps/web/lib/stripe/index.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/lib/stripe/index.ts (1)
apps/web/lib/types.ts (1)
  • StripeMode (652-652)
⏰ 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). (1)
  • GitHub Check: build

@steven-tey
Copy link
Collaborator

@coderabbitai full review

Copy link
Contributor

coderabbitai bot commented Oct 9, 2025

✅ Actions performed

Full review triggered.

Copy link
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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/app/(ee)/api/stripe/integration/webhook/route.ts (1)

33-51: Improve error diagnostics for missing webhook secrets.

When an environment variable like STRIPE_APP_WEBHOOK_SECRET_SANDBOX is not configured, the current error response "Invalid request" doesn't indicate which secret is missing, making production debugging difficult.

Consider logging the specific mode or missing secret:

  if (!sig || !webhookSecret) {
+   console.error(`Missing webhook configuration for mode: ${mode}`);
    return new Response("Invalid request", {
      status: 400,
    });
  }

Additionally, you might extract the mode detection logic into a helper function for better testability:

function detectWebhookMode(pathname: string): { mode: StripeMode; webhookSecret: string | undefined } {
  if (pathname.endsWith("/test")) {
    return { mode: "test", webhookSecret: process.env.STRIPE_APP_WEBHOOK_SECRET_TEST };
  } else if (pathname.endsWith("/sandbox")) {
    return { mode: "sandbox", webhookSecret: process.env.STRIPE_APP_WEBHOOK_SECRET_SANDBOX };
  } else {
    return { mode: "live", webhookSecret: process.env.STRIPE_APP_WEBHOOK_SECRET };
  }
}
♻️ Duplicate comments (2)
packages/stripe-app/stripe-app.json (1)

60-60: connect_permissions should be an array, not null.

Stripe's manifest schema requires connect_permissions to be an array. Using null will cause validation failures when uploading the app configuration.

Apply this diff:

-  "connect_permissions": null,
+  "connect_permissions": [],

Alternatively, remove the field entirely if no permissions are needed.

apps/web/lib/stripe/index.ts (1)

22-22: Add validation for missing environment variables.

The non-null assertion is unsafe. If the environment variable for the selected mode is undefined, the Stripe client will be constructed with undefined, causing runtime errors during API calls rather than failing fast with a clear error message.

Add validation before constructing the Stripe client:

 export const stripeAppClient = ({ mode }: { mode?: StripeMode }) => {
   const appSecretKey = secretMap[mode ?? "test"];
 
+  if (!appSecretKey) {
+    throw new Error(
+      `Missing Stripe app secret key for mode "${mode ?? "test"}". Please set the corresponding environment variable.`
+    );
+  }
+
-  return new Stripe(appSecretKey!, {
+  return new Stripe(appSecretKey, {
     apiVersion: "2025-05-28.basil",
     appInfo: {
       name: "Dub.co",
       version: "0.1.0",
     },
   });
 };
🧹 Nitpick comments (2)
apps/web/lib/stripe/index.ts (1)

20-20: Reconsider the default mode selection.

Defaulting to "test" mode when no mode is specified could lead to unexpected behavior in production if callers forget to pass the mode explicitly. Consider either:

  1. Making mode required (remove the optional ? and default), forcing callers to be explicit
  2. Defaulting to "live" mode for production safety
  3. Using an environment variable to determine the default mode

For example, to make the mode required:

-export const stripeAppClient = ({ mode }: { mode?: StripeMode }) => {
+export const stripeAppClient = ({ mode }: { mode: StripeMode }) => {
-  const appSecretKey = secretMap[mode ?? "test"];
+  const appSecretKey = secretMap[mode];

Or to use an environment-based default:

 export const stripeAppClient = ({ mode }: { mode?: StripeMode }) => {
-  const appSecretKey = secretMap[mode ?? "test"];
+  const defaultMode = process.env.NODE_ENV === "production" ? "live" : "test";
+  const appSecretKey = secretMap[mode ?? defaultMode];
apps/web/app/(ee)/api/stripe/integration/webhook/route.ts (1)

32-32: Update comment to mention sandbox route.

The comment references the test route but should also mention the sandbox route for consistency with the new sandbox support.

-  // @see https://github.com/dubinc/dub/blob/main/apps/web/app/(ee)/api/stripe/integration/webhook/test/route.ts
+  // @see https://github.com/dubinc/dub/blob/main/apps/web/app/(ee)/api/stripe/integration/webhook/test/route.ts
+  // @see https://github.com/dubinc/dub/blob/main/apps/web/app/(ee)/api/stripe/integration/webhook/sandbox/route.ts
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31ce622 and 1350451.

📒 Files selected for processing (22)
  • apps/web/app/(ee)/api/stripe/integration/webhook/charge-refunded.ts (1 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (7 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (3 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/route.ts (3 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/sandbox/route.ts (1 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-connected-customer.ts (1 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-promotion-code.ts (1 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-subscription-product-id.ts (1 hunks)
  • apps/web/lib/actions/partners/create-discount.ts (1 hunks)
  • apps/web/lib/firstpromoter/update-stripe-customers.ts (1 hunks)
  • apps/web/lib/partnerstack/update-stripe-customers.ts (1 hunks)
  • apps/web/lib/stripe/create-stripe-discount-code.ts (1 hunks)
  • apps/web/lib/stripe/disable-stripe-discount-code.ts (1 hunks)
  • apps/web/lib/stripe/index.ts (2 hunks)
  • apps/web/lib/tolt/update-stripe-customers.ts (1 hunks)
  • apps/web/lib/types.ts (1 hunks)
  • apps/web/scripts/stripe/backfill-stripe-webhook-events.ts (1 hunks)
  • apps/web/scripts/stripe/get-connected-customer.ts (1 hunks)
  • apps/web/scripts/stripe/search-customers.ts (1 hunks)
  • apps/web/scripts/stripe/update-stripe-customers.ts (1 hunks)
  • packages/stripe-app/README.md (1 hunks)
  • packages/stripe-app/stripe-app.json (3 hunks)
🔇 Additional comments (17)
apps/web/scripts/stripe/backfill-stripe-webhook-events.ts (1)

29-31: LGTM! Migration to mode-based configuration.

The change from livemode: false to mode: "test" is consistent with the broader migration to the StripeMode type. Using "test" mode is appropriate for this backfill script.

apps/web/app/(ee)/api/stripe/integration/webhook/sandbox/route.ts (1)

1-16: LGTM! Route path comment has been corrected.

The previously flagged issue regarding the incorrect route path in the comment has been addressed. The comment at Line 2 now correctly references /api/stripe/integration/webhook/sandbox, matching the actual route. The re-export pattern is clean and aligns with the mode-based webhook routing architecture.

packages/stripe-app/stripe-app.json (1)

3-3: LGTM! Version and sandbox support additions.

The version bump to 0.0.18 and the addition of sandbox_install_compatible: true and distribution_type: "public" align with the PR's objective to support Stripe sandbox for app testing.

Also applies to: 87-88

apps/web/lib/actions/partners/create-discount.ts (1)

21-23: LGTM! Consistent migration to mode-based configuration.

The change from livemode: true to mode: "live" is consistent with the StripeMode migration. Using "live" mode when VERCEL_ENV is set is appropriate for production deployments.

apps/web/lib/partnerstack/update-stripe-customers.ts (1)

13-15: LGTM! Consistent migration to mode-based configuration.

The change from livemode: true to mode: "live" aligns with the broader StripeMode migration and is appropriate for production customer data handling.

apps/web/lib/tolt/update-stripe-customers.ts (1)

11-13: LGTM! Consistent migration to mode-based configuration.

The change from livemode: true to mode: "live" is consistent with the StripeMode migration and appropriate for production customer operations.

apps/web/lib/stripe/create-stripe-discount-code.ts (1)

5-7: LGTM! Consistent migration to mode-based configuration.

The change from livemode: true to mode: "live" aligns with the StripeMode migration and is appropriate for creating discount codes in production environments.

apps/web/scripts/stripe/search-customers.ts (1)

7-9: LGTM! Appropriate use of test mode for scripts.

The change from livemode: true to mode: "test" is consistent with the StripeMode migration. Using "test" mode is appropriate for ad-hoc customer search scripts, preventing unintended queries against live production data.

apps/web/scripts/stripe/update-stripe-customers.ts (1)

35-35: Verify the mode change from live to test.

The script previously used livemode: true (production mode) but now uses mode: "test". This changes the script to query test Stripe data instead of live production data. Ensure this is intentional, as it will affect which Stripe customers are retrieved and updated.

If this script is meant for production use, consider using mode: "live" or making the mode configurable via an environment variable.

apps/web/lib/types.ts (1)

655-655: LGTM!

The StripeMode type definition is clean and clearly defines the three supported Stripe modes.

apps/web/scripts/stripe/get-connected-customer.ts (1)

6-6: LGTM!

The change from livemode: false to mode: "test" is consistent with the previous behavior and aligns with the script's testing purpose (note the placeholder IDs).

apps/web/lib/firstpromoter/update-stripe-customers.ts (1)

12-12: LGTM!

The change maintains the same production behavior by conditionally using mode: "live" when VERCEL_ENV is set, which is semantically equivalent to the previous livemode: true.

apps/web/lib/stripe/disable-stripe-discount-code.ts (1)

4-4: LGTM!

Consistent with the mode-based migration pattern used across the codebase, maintaining production behavior when VERCEL_ENV is set.

apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (1)

7-7: LGTM!

The refactoring properly introduces the mode parameter, imports the StripeMode type, and propagates the mode through the call chain to getConnectedCustomer. The changes are clean and consistent with the mode-based migration.

Also applies to: 19-19, 37-37

apps/web/app/(ee)/api/stripe/integration/webhook/charge-refunded.ts (1)

3-3: LGTM!

The refactoring follows the same pattern as other webhook handlers, properly introducing the mode parameter and using it in the stripeAppClient initialization.

Also applies to: 8-8, 13-13

apps/web/app/(ee)/api/stripe/integration/webhook/route.ts (2)

2-2: LGTM!

Type import is clean and necessary for the mode-aware webhook routing.


72-97: No additional handlers require mode. Checked all other webhook handlers; none perform Stripe API calls needing the mode context.

@steven-tey
Copy link
Collaborator

/bug0 run

1 similar comment
@panda-sandeep
Copy link

/bug0 run

@steven-tey steven-tey merged commit 2152145 into main Oct 9, 2025
9 checks passed
@steven-tey steven-tey deleted the stripe-app-sandbox-testing branch October 9, 2025 21:49
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.

3 participants