Skip to content

fix(stripe): avoid duplicate customer creation on lookup errors#29528

Open
Shreyas2004wagh wants to merge 3 commits into
calcom:mainfrom
Shreyas2004wagh:fix/stripe-customer-lookup-errors
Open

fix(stripe): avoid duplicate customer creation on lookup errors#29528
Shreyas2004wagh wants to merge 3 commits into
calcom:mainfrom
Shreyas2004wagh:fix/stripe-customer-lookup-errors

Conversation

@Shreyas2004wagh

@Shreyas2004wagh Shreyas2004wagh commented Jun 9, 2026

Copy link
Copy Markdown

What does this PR do?

Fixes #29455

createStripeCustomerId previously handled Stripe customer lookup with a broad try/catch. That meant real lookup failures, such as auth errors, network failures, or rate limits, were treated the same as "no customer exists" and could silently create a duplicate Stripe customer.

This PR changes the flow so:

  • existing Stripe customers are reused when customers.list() returns one
  • a new Stripe customer is created only when the lookup succeeds and returns an empty list
  • real Stripe lookup errors propagate to the caller instead of being swallowed

How should this be tested?

yarn workspace @calcom/api-v2 jest src/modules/stripe/stripe.service.spec.ts --runInBand

Local result:

PASS src/modules/stripe/stripe.service.spec.ts
Tests: 3 passed

I also ran:

yarn biome check --write apps/api/v2/src/modules/stripe/stripe.service.ts apps/api/v2/src/modules/stripe/stripe.service.spec.ts

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Welcome to Cal.diy, @Shreyas2004wagh! Thanks for opening this pull request.

A few things to keep in mind:

  • This is Cal.diy, not Cal.com. Cal.diy is a community-driven, fully open-source fork of Cal.com licensed under MIT. Your changes here will be part of Cal.diy — they will not be deployed to the Cal.com production app.
  • Please review our Contributing Guidelines if you haven't already.
  • Make sure your PR title follows the Conventional Commits format.

A maintainer will review your PR soon. Thanks for contributing!

@github-actions github-actions Bot added the 🐛 bug Something isn't working label Jun 9, 2026
@Shreyas2004wagh Shreyas2004wagh marked this pull request as ready for review June 9, 2026 13:57
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a1214c64-3ab4-45d8-a510-53ccba39b865

📥 Commits

Reviewing files that changed from the base of the PR and between 8e6cc64 and 07e6474.

📒 Files selected for processing (2)
  • apps/api/v2/src/modules/stripe/stripe.service.spec.ts
  • apps/api/v2/src/modules/stripe/stripe.service.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/api/v2/src/modules/stripe/stripe.service.ts
  • apps/api/v2/src/modules/stripe/stripe.service.spec.ts

📝 Walkthrough

Walkthrough

This PR refactors the createStripeCustomerId method to replace its try/catch fallback pattern with a direct list-based lookup. Previously, if stripe.customers.list threw an error, the method would create a new customer. The updated implementation calls list directly, reuses the first customer from the response if present, and creates a new customer only when the list response is empty. Any list error now propagates instead of triggering creation. The PR adds tests verifying customer reuse, customer creation, and error propagation.

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(stripe): avoid duplicate customer creation on lookup errors' directly summarizes the main change—preventing broad error catching that triggers duplicate customer creation.
Description check ✅ Passed The description explains what was fixed (broad try/catch), why it was a problem (duplicate customer creation), and the three key changes made in this PR.
Linked Issues check ✅ Passed The PR implementation addresses all coding objectives from #29455: removes broad error catching, adds explicit empty-list check, only creates customers on empty-list success case, and allows real errors to propagate.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the Stripe customer lookup error handling issue. The spec file adds tests for the fixed behavior, and stripe.service.ts implements the fix.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/api/v2/src/modules/stripe/stripe.service.spec.ts (1)

64-101: ⚡ Quick win

Assert customers.list call args to lock in the bugfix contract.

Add expectations that lookup is called with { email: user.email, limit: 1 } in all scenarios, so the regression boundary is explicit in tests.

Suggested diff
     it("reuses an existing Stripe customer when lookup finds one", async () => {
       mockCustomersList.mockResolvedValue(createCustomerSearchResult([createCustomer("cus_existing")]));
 
       await expect(service.createStripeCustomerId(user)).resolves.toBe("cus_existing");
+      expect(mockCustomersList).toHaveBeenCalledWith({ email: user.email, limit: 1 });
 
       expect(mockCustomersCreate).not.toHaveBeenCalled();
       expect(mockUsersRepository.updateByEmail).toHaveBeenCalledWith(user.email, {
         metadata: {
           existing: "value",
@@
     it("creates a Stripe customer when lookup returns no customers", async () => {
       mockCustomersList.mockResolvedValue(createCustomerSearchResult([]));
       mockCustomersCreate.mockResolvedValue(createCustomer("cus_new"));
 
       await expect(service.createStripeCustomerId(user)).resolves.toBe("cus_new");
+      expect(mockCustomersList).toHaveBeenCalledWith({ email: user.email, limit: 1 });
 
       expect(mockCustomersCreate).toHaveBeenCalledWith({ email: user.email });
       expect(mockUsersRepository.updateByEmail).toHaveBeenCalledWith(user.email, {
         metadata: {
           existing: "value",
@@
     it("propagates Stripe lookup errors without creating duplicate customers", async () => {
       const stripeError = new Error("Stripe lookup failed");
       mockCustomersList.mockRejectedValue(stripeError);
 
       await expect(service.createStripeCustomerId(user)).rejects.toThrow(stripeError);
+      expect(mockCustomersList).toHaveBeenCalledWith({ email: user.email, limit: 1 });
 
       expect(mockCustomersCreate).not.toHaveBeenCalled();
       expect(mockUsersRepository.updateByEmail).not.toHaveBeenCalled();
     });
🤖 Prompt for 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.

In `@apps/api/v2/src/modules/stripe/stripe.service.spec.ts` around lines 64 - 101,
Add an assertion in each test that the Stripe customers.list mock
(mockCustomersList) is invoked with the exact lookup contract { email:
user.email, limit: 1 }; update the three tests ("reuses an existing Stripe
customer...", "creates a Stripe customer...", "propagates Stripe lookup
errors...") to include expect(mockCustomersList).toHaveBeenCalledWith({ email:
user.email, limit: 1 }) so the lookup call signature is locked in for
createStripeCustomerId and prevents regressions.
🤖 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 `@apps/api/v2/src/modules/stripe/stripe.service.ts`:
- Around line 14-20: The file imports type-only symbols AppConfig and StripeData
as regular imports; update their import statements to use TypeScript's type-only
form (import type) for AppConfig and StripeData in
apps/api/v2/src/modules/stripe/stripe.service.ts so those identifiers are
treated as types only and do not produce runtime imports—locate the existing
imports of AppConfig and StripeData and change them to use import type
accordingly.

---

Nitpick comments:
In `@apps/api/v2/src/modules/stripe/stripe.service.spec.ts`:
- Around line 64-101: Add an assertion in each test that the Stripe
customers.list mock (mockCustomersList) is invoked with the exact lookup
contract { email: user.email, limit: 1 }; update the three tests ("reuses an
existing Stripe customer...", "creates a Stripe customer...", "propagates Stripe
lookup errors...") to include expect(mockCustomersList).toHaveBeenCalledWith({
email: user.email, limit: 1 }) so the lookup call signature is locked in for
createStripeCustomerId and prevents regressions.
🪄 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: 9b35df4e-d941-40ed-a449-61198c088925

📥 Commits

Reviewing files that changed from the base of the PR and between ecfb05b and 8e6cc64.

📒 Files selected for processing (2)
  • apps/api/v2/src/modules/stripe/stripe.service.spec.ts
  • apps/api/v2/src/modules/stripe/stripe.service.ts

Comment thread apps/api/v2/src/modules/stripe/stripe.service.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐛 bug Something isn't working size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MEDIUM: createStripeCustomerId catches too broadly, silently creating duplicate customers on real Stripe errors

1 participant