-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Improve checkout.session.completed webhook to match customer by email
#3163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughMoved workspace lookup to the webhook entry, broadened customer lookup criteria, threaded the discovered workspace through promotion-code attribution and sale attribution, removed redundant workspace fetches, and exposed Project type in function signatures. Changes
Sequence DiagramsequenceDiagram
participant Webhook as Stripe Checkout Webhook
participant DB as Database (Prisma)
participant Promo as attributeViaPromoCode
participant Sale as Sale Attribution
Webhook->>DB: find workspace by stripeConnectId (select id, defaultProgramId, stripeConnectId, webhookEnabled)
alt workspace found
DB-->>Webhook: workspace
Webhook->>DB: find customer by clientReferenceId / externalId / email / stripeCustomerId
DB-->>Webhook: customer?
Webhook->>Promo: attributeViaPromoCode(workspace, promotionCodeId, stripeAccountId, mode, charge)
Promo-->>Webhook: promo attribution result
Webhook->>Sale: attribute sale/lead using workspace + attribution result
Sale-->>Webhook: attribution completed
else workspace missing
DB-->>Webhook: no workspace -> early return / log
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20–30 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 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 |
There was a problem hiding this 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/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (1)
94-107: Comment-code mismatch: MissingstripeCustomerIdfield checks.The comment states it should check "or email or stripeCustomerId", but the OR conditions only check the
externalIdfield. Compare with lines 208-221 which properly search bystripeCustomerIdandShould this be:
existingCustomer = await prisma.customer.findFirst({ where: { projectId: workspace.id, - // check for existing customer with the same externalId (via clickId or email) or email or stripeCustomerId + // check for existing customer with the same externalId, email, or stripeCustomerId OR: [ { externalId: clickEvent.click_id, }, { externalId: stripeCustomerEmail, }, + ...(stripeCustomerEmail ? [{ email: stripeCustomerEmail }] : []), + ...(stripeCustomerId ? [{ stripeCustomerId }] : []), ], }, });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts(8 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-10-28T19:17:28.085Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2985
File: apps/web/app/(ee)/api/network/programs/[programSlug]/route.ts:20-27
Timestamp: 2025-10-28T19:17:28.085Z
Learning: In this codebase, Prisma's extendedWhereUnique feature is enabled (or Prisma 5.x is used), allowing findUniqueOrThrow to accept additional non-unique filters alongside unique fields like slug. This pattern is valid and acceptable.
Applied to files:
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
📚 Learning: 2025-08-25T17:42:13.600Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2736
File: apps/web/lib/api/get-workspace-users.ts:76-83
Timestamp: 2025-08-25T17:42:13.600Z
Learning: Business rule confirmed: Each workspace has exactly one program. The code should always return workspace.programs[0] since there's only one program per workspace.
Applied to files:
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
📚 Learning: 2025-06-18T20:26:25.177Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2538
File: apps/web/ui/partners/overview/blocks/commissions-block.tsx:16-27
Timestamp: 2025-06-18T20:26:25.177Z
Learning: In the Dub codebase, components that use workspace data (workspaceId, defaultProgramId) are wrapped in `WorkspaceAuth` which ensures these values are always available, making non-null assertions safe. This is acknowledged as a common pattern in their codebase, though not ideal.
Applied to files:
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
⏰ 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 (4)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (4)
30-30: LGTM!The
Projecttype import is needed for the updatedattributeViaPromoCodefunction signature.
63-77: Good refactor: Workspace lookup at entry point.Moving the workspace fetch to the function entry enables early validation and eliminates redundant database queries downstream.
366-366: LGTM!The
_saleunderscore prefix correctly indicates the unused return value, and workspace no longer needs to be extracted here since it's fetched at the entry point.
508-523: Good refactor: Workspace threading eliminates redundant DB fetch.Passing the workspace as a parameter with a properly narrowed type (
Pick<Project, ...>) is cleaner than fetching it again internally.
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
Show resolved
Hide resolved
There was a problem hiding this 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
🧹 Nitpick comments (1)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (1)
212-229: Consider addingprojectIdto thestripeCustomerIdbranch for explicit data isolation.The conditional spread for
stripeCustomerEmailaddresses the undefined-value issue flagged previously. However, thestripeCustomerIdbranch still lacks aprojectIdconstraint. While Stripe customer IDs are unique per connected account (and the webhook context ensures the correct workspace), scoping byprojectIdprovides an explicit safeguard against accidental cross-workspace matching.existingCustomer = await prisma.customer.findFirst({ where: { OR: [ { + projectId: workspace.id, stripeCustomerId, }, ...(stripeCustomerEmail ? [ { projectId: workspace.id, email: stripeCustomerEmail, }, ] : []), ], }, });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts(8 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-10-28T19:17:28.085Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2985
File: apps/web/app/(ee)/api/network/programs/[programSlug]/route.ts:20-27
Timestamp: 2025-10-28T19:17:28.085Z
Learning: In this codebase, Prisma's extendedWhereUnique feature is enabled (or Prisma 5.x is used), allowing findUniqueOrThrow to accept additional non-unique filters alongside unique fields like slug. This pattern is valid and acceptable.
Applied to files:
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
📚 Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.
Applied to files:
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
📚 Learning: 2025-08-25T17:42:13.600Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2736
File: apps/web/lib/api/get-workspace-users.ts:76-83
Timestamp: 2025-08-25T17:42:13.600Z
Learning: Business rule confirmed: Each workspace has exactly one program. The code should always return workspace.programs[0] since there's only one program per workspace.
Applied to files:
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
📚 Learning: 2025-06-18T20:26:25.177Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2538
File: apps/web/ui/partners/overview/blocks/commissions-block.tsx:16-27
Timestamp: 2025-06-18T20:26:25.177Z
Learning: In the Dub codebase, components that use workspace data (workspaceId, defaultProgramId) are wrapped in `WorkspaceAuth` which ensures these values are always available, making non-null assertions safe. This is acknowledged as a common pattern in their codebase, though not ideal.
Applied to files:
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
🧬 Code graph analysis (1)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (1)
packages/prisma/index.ts (1)
prisma(3-9)
⏰ 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 (3)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (3)
63-77: Good refactor: centralized workspace lookup.Moving the workspace discovery to the webhook entry point with a minimal field selection is clean. This eliminates redundant fetches and provides clear early-exit behavior.
94-111: LGTM!The customer lookup is correctly scoped with
projectIdat the top level, and the conditional spread forstripeCustomerEmailproperly avoids the undefined-value pitfall in Prisma'sORclauses.
516-528: LGTM!Using
Pick<Project, ...>for the workspace parameter is a clean approach—it documents exactly which fields the function depends on while eliminating the redundant database lookup that was previously inside this function.
Summary by CodeRabbit
Refactor
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.