feat(billing): implement upsert for payment methods and enhance webhook handling for idempotency#2534
feat(billing): implement upsert for payment methods and enhance webhook handling for idempotency#2534
Conversation
…ok handling for idempotency
📝 WalkthroughWalkthroughAdds an idempotent Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Webhook Client
participant Service as StripeWebhookService
participant Repo as PaymentMethodRepository
participant DB as Database
participant Stripe as Stripe API
Client->>Service: payment_method.attached event
Service->>Repo: upsert(userId, fingerprint, paymentMethodId)
Repo->>DB: SELECT by fingerprint + paymentMethodId
alt Record exists
DB-->>Repo: existing record
Repo-->>Service: {paymentMethod, isNew: false}
else Record not found
Repo->>DB: SELECT count(*) of user's payment methods
DB-->>Repo: count
Repo->>DB: INSERT new payment method (isDefault based on count) ON CONFLICT DO NOTHING
alt Insert succeeds
DB-->>Repo: new record
Repo-->>Service: {paymentMethod, isNew: true}
else Conflict / unique violation
DB-->>Repo: conflict / error
Repo->>DB: SELECT by fingerprint + paymentMethodId
DB-->>Repo: return record
Repo-->>Service: {paymentMethod, isNew: false}
end
end
alt isNew = true AND paymentMethod.isDefault = true
Service->>Stripe: Set payment method as default on customer
alt Sync succeeds
Stripe-->>Service: OK
else Sync fails
Stripe-->>Service: Error
Service->>Service: Log STRIPE_DEFAULT_PAYMENT_METHOD_SYNC_FAILED
end
end
Service-->>Client: 200 OK
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In
`@apps/api/src/billing/repositories/payment-method/payment-method.repository.ts`:
- Around line 136-178: The upsert() method can hit a partial-unique constraint
on (userId, isDefault) when two requests compute isDefault=true concurrently;
update the insert conflict handling to either include that constraint or
gracefully handle the DB error: modify the
cursor.insert(...).onConflictDoNothing(...) call to also target the userId and
isDefault columns (e.g., add this.table.userId and this.table.isDefault to the
target list) so the second insert is ignored, or wrap the insert/returning block
in a try/catch that detects the specific unique/constraint violation and then
loads the existing record via findOneBy and returns isNew:false. Ensure
references to upsert, countByUserId, onConflictDoNothing, cursor.insert, and
findOneBy are used to locate the change.
In `@apps/api/src/billing/services/stripe-webhook/stripe-webhook.service.ts`:
- Around line 90-99: The idempotency guard in stripe-webhook.service.ts only
treats existingTransaction?.status === "succeeded" as already processed, which
will reprocess refunded or other terminal-success transactions; update the check
in the Stripe webhook handler (where you call
stripeTransactionRepository.findByPaymentIntentId and inspect
existingTransaction.status) to treat "refunded" and any other terminal
success/settled statuses as already-processed (e.g., check against a set/array
like ["succeeded","refunded", ...]) and return early, ensuring you reference
existingTransaction.id and paymentIntent.id in the log message as before.
apps/api/src/billing/repositories/payment-method/payment-method.repository.ts
Show resolved
Hide resolved
Codecov Report❌ Patch coverage is ❌ Your patch status has failed because the patch coverage (45.00%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #2534 +/- ##
==========================================
+ Coverage 50.70% 50.73% +0.03%
==========================================
Files 1063 1063
Lines 29490 29513 +23
Branches 6531 6548 +17
==========================================
+ Hits 14952 14974 +22
+ Misses 14230 14114 -116
- Partials 308 425 +117
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@apps/api/src/billing/repositories/payment-method/payment-method.repository.ts`:
- Around line 198-205: The non-null assertion on paymentMethod after calling
findOneBy can throw at runtime; update the logic around findOneBy (the
paymentMethod variable) to handle a null result defensively: check if
paymentMethod is null and either throw a clear, descriptive error (including
input.fingerprint and input.paymentMethodId) or return an explicit failure
value/object instead of using the ! operator, then return the normal {
paymentMethod, isNew: false } when present.
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.