Skip to content

0.1.8: Stop reusing deleted or other users' Stripe customers - #79

Open
michaelshimeles wants to merge 1 commit into
fix/webhook-correctnessfrom
fix/customer-lookups
Open

michaelshimeles wants to merge 1 commit into
fix/webhook-correctnessfrom
fix/customer-lookups

Conversation

@michaelshimeles

Copy link
Copy Markdown
Collaborator

Second of five stacked PRs, part of the 0.1.8 patch. Based on the webhook correctness PR.

What changed

  • getOrCreateCustomer could hand one user another user's Stripe customer. When nothing matched the userId, it fell back to any customer with the same email, so a second account with that email got the first account's customer, and its Customer Portal link showed the first user's cards and invoices. It now only reuses an email match when that customer has no userId, and then links it to the user in Stripe and in Convex.
  • Customers deleted in Stripe are marked deleted: true instead of only having their PII scrubbed. getOrCreateCustomer, getCustomerByUserId, and getCustomerByEmail skip them, so a user whose customer was deleted gets a new one instead of a "No such customer" error at checkout. The subscription and payment fallbacks also skip deleted customers.
  • getCustomerByUserId and getCustomerByEmail return the newest match instead of the oldest.
  • customer.updated creates the customer when it isn't stored yet and links userId from metadata. Customers created before the webhook was set up never synced before. It also leaves deleted customers alone, so a late update can't restore their details.
  • getSubscriptionByOrgId used .first(), which returned the oldest subscription, often a canceled one after the org resubscribed. It now prefers active or trialing, then past due, unpaid, or paused, then incomplete, with ties going to the newest.

deleted is a new optional field on customer documents. Apps with hand-written returns validators for customer data will need to add it. The next PR exports validators so that doesn't keep happening.

One gap I left alone: createCustomer still uses create_customer_${userId} as the idempotency key. If a customer is deleted within 24 hours of being created, Stripe replays the deleted customer for that key.

Testing

  • npm run build, npm test (63 passing), npm run typecheck, npm run lint.
  • Component tests for deleted and newest-first lookups, customer.updated creating and linking customers, late updates after deletion, and the org subscription preference. Client tests for skipping a deleted linked customer, refusing another user's email match, and claiming an unclaimed one.

- Customers deleted in Stripe are marked deleted. getOrCreateCustomer,
  getCustomerByUserId, and getCustomerByEmail skip them, so a user whose
  customer was deleted gets a new one instead of a "No such customer"
  checkout error.
- getOrCreateCustomer only reuses an email match when that customer has
  no userId, then links it to the user in Stripe and Convex. Before, any
  user sharing an email could be handed another user's customer and its
  billing portal.
- customer.updated creates missing customers, links userId from
  metadata, and leaves deleted customers alone.
- User and email lookups return the newest customer, and
  getSubscriptionByOrgId prefers a live subscription over old canceled
  ones.

Co-authored-by: Cursor <cursoragent@cursor.com>
@pkg-pr-new

pkg-pr-new Bot commented Nov 3, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@convex-dev/stripe@79

commit: bf87b32

@michaelshimeles
michaelshimeles marked this pull request as ready for review November 3, 2026 22:01
@michaelshimeles
michaelshimeles added this pull request to stack #83 November 3, 2026 22:02

This branch has not been deployed

No deployments
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