@ambushsoftworks/nestjs-payments-graphql 0.6.0 → 0.7.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +135 -0
- package/README.md +57 -2
- package/dist/dto/create-recurring-invoice.input.d.ts +2 -0
- package/dist/dto/create-recurring-invoice.input.js +16 -0
- package/dist/dto/create-recurring-invoice.input.js.map +1 -1
- package/dist/dto/update-recurring-template.input.d.ts +2 -0
- package/dist/dto/update-recurring-template.input.js +13 -0
- package/dist/dto/update-recurring-template.input.js.map +1 -1
- package/dist/exceptions/index.js.map +1 -1
- package/dist/gateways/gateway-registry.service.js +4 -3
- package/dist/gateways/gateway-registry.service.js.map +1 -1
- package/dist/gateways/payment-gateway.interface.d.ts +2 -1
- package/dist/gateways/stripe/stripe-webhook.controller.js +5 -1
- package/dist/gateways/stripe/stripe-webhook.controller.js.map +1 -1
- package/dist/gateways/stripe/stripe.gateway.d.ts +2 -0
- package/dist/gateways/stripe/stripe.gateway.js +31 -3
- package/dist/gateways/stripe/stripe.gateway.js.map +1 -1
- package/dist/interfaces/recurring-invoice-repository.interface.d.ts +2 -0
- package/dist/models/recurring-invoice.model.d.ts +1 -0
- package/dist/models/recurring-invoice.model.js +9 -0
- package/dist/models/recurring-invoice.model.js.map +1 -1
- package/dist/payments.module.js.map +1 -1
- package/dist/prisma/assert-payments-schema.js +4 -1
- package/dist/prisma/assert-payments-schema.js.map +1 -1
- package/dist/prisma/columns.d.ts +3 -1
- package/dist/prisma/columns.js +9 -2
- package/dist/prisma/columns.js.map +1 -1
- package/dist/prisma/prisma-payment-config.repository.js +1 -0
- package/dist/prisma/prisma-payment-config.repository.js.map +1 -1
- package/dist/prisma/prisma-recurring-invoice.repository.d.ts +2 -0
- package/dist/prisma/prisma-recurring-invoice.repository.js +18 -0
- package/dist/prisma/prisma-recurring-invoice.repository.js.map +1 -1
- package/dist/prisma/prisma-webhook-idempotency.repository.d.ts +1 -0
- package/dist/prisma/prisma-webhook-idempotency.repository.js +6 -3
- package/dist/prisma/prisma-webhook-idempotency.repository.js.map +1 -1
- package/dist/services/e-transfer.service.js +1 -1
- package/dist/services/e-transfer.service.js.map +1 -1
- package/dist/services/invoice-number.service.d.ts +0 -1
- package/dist/services/invoice-number.service.js +2 -8
- package/dist/services/invoice-number.service.js.map +1 -1
- package/dist/services/invoice.service.js +12 -17
- package/dist/services/invoice.service.js.map +1 -1
- package/dist/services/payment-config.service.js +3 -13
- package/dist/services/payment-config.service.js.map +1 -1
- package/dist/services/payment-email.service.d.ts +1 -1
- package/dist/services/payment-email.service.js +5 -3
- package/dist/services/payment-email.service.js.map +1 -1
- package/dist/services/payment-jobs.service.js +6 -1
- package/dist/services/payment-jobs.service.js.map +1 -1
- package/dist/services/payment-method.service.d.ts +5 -1
- package/dist/services/payment-method.service.js +20 -3
- package/dist/services/payment-method.service.js.map +1 -1
- package/dist/services/payment.service.js +33 -4
- package/dist/services/payment.service.js.map +1 -1
- package/dist/services/recurring-invoice.service.d.ts +11 -2
- package/dist/services/recurring-invoice.service.js +91 -37
- package/dist/services/recurring-invoice.service.js.map +1 -1
- package/dist/services/refund.service.js +15 -6
- package/dist/services/refund.service.js.map +1 -1
- package/dist/services/simple-decimal.d.ts +7 -0
- package/dist/services/simple-decimal.js +16 -0
- package/dist/services/simple-decimal.js.map +1 -0
- package/dist/services/webhook-provider.util.d.ts +3 -0
- package/dist/services/webhook-provider.util.js +9 -0
- package/dist/services/webhook-provider.util.js.map +1 -0
- package/dist/types/recurring-invoice.types.d.ts +3 -0
- package/package.json +1 -1
- package/prisma/payment-models.prisma +13 -1
- package/dist/models/index.d.ts +0 -14
- package/dist/models/index.js +0 -38
- package/dist/models/index.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -28,6 +28,141 @@ prose for buried obligations. The marker was introduced in v0.4.0 and
|
|
|
28
28
|
|
|
29
29
|
## [Unreleased]
|
|
30
30
|
|
|
31
|
+
## [0.7.0] - 2026-09-19
|
|
32
|
+
|
|
33
|
+
> **Upgrade notes.** One optional database column, required only if you want
|
|
34
|
+
> the feature.
|
|
35
|
+
>
|
|
36
|
+
> 1. **To pin a recurring schedule to a card**, add
|
|
37
|
+
> `paymentMethodId String?` to `RecurringInvoice` and enable
|
|
38
|
+
> `features.cardManagement`. Leave the flag off and nothing changes —
|
|
39
|
+
> `assertPaymentsSchema` does not ask for the column, so an existing
|
|
40
|
+
> recurring consumer boots unchanged.
|
|
41
|
+
> 2. **`RecurringInvoiceService` takes a new last constructor argument**,
|
|
42
|
+
> `PaymentMethodService`. This only matters if you construct it by hand, in
|
|
43
|
+
> tests for example.
|
|
44
|
+
> 3. **`removePaymentMethod` returns two more fields.** Nothing breaks: the
|
|
45
|
+
> return is an intersection, so every existing call site still compiles and
|
|
46
|
+
> a `PaymentCustomer` assignment still type-checks. Read
|
|
47
|
+
> `wasDefault`/`remainingCount` when you want to prompt.
|
|
48
|
+
> 4. **`failureReason` can now say `payment_method_unavailable`.** If you
|
|
49
|
+
> switch on that string, add a branch: it means the card a schedule was
|
|
50
|
+
> pinned to was refused, usually because it was detached. Previously this
|
|
51
|
+
> case surfaced as a raw exception message.
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
- **A recurring schedule can name the card it charges.**
|
|
55
|
+
`RecurringInvoice.paymentMethodId` pins a schedule to one saved card instead
|
|
56
|
+
of the client's provider-level default — departmental billing, or a card that
|
|
57
|
+
is not the client's default. `attemptAutoCharge` threads it to the gateway,
|
|
58
|
+
which has accepted a `paymentMethodId` since 0.5.0 with nothing able to supply
|
|
59
|
+
one. Raised by the jobsites team, who had shipped the feature and switched it
|
|
60
|
+
off: they refuse the enrolment rather than charge the wrong card.
|
|
61
|
+
- **A pinned card that has been detached fails the renewal.** It does not
|
|
62
|
+
fall back to the client default, because falling back charges a card the
|
|
63
|
+
client deliberately did not nominate. The failure carries
|
|
64
|
+
`payment_method_unavailable`, distinct from `no_payment_method`, so a
|
|
65
|
+
consumer can tell "the card you chose is gone" from "you have no card".
|
|
66
|
+
- **The pin is checked when the template is written**, not when it is
|
|
67
|
+
charged: a bad pin discovered at charge time is discovered as a failed
|
|
68
|
+
renewal, which is the failure the feature exists to prevent.
|
|
69
|
+
- **`autoCharge` is settable through the package.** It is a column, it gates
|
|
70
|
+
the whole auto-charge path, and until now neither `create` nor
|
|
71
|
+
`updateTemplate` carried it — a consumer had to write it through their
|
|
72
|
+
repository directly. Found while mapping the above; a pinned card on a
|
|
73
|
+
template whose `autoCharge` nobody can turn on is half a feature.
|
|
74
|
+
- **`removePaymentMethod` reports what it broke.** It now returns
|
|
75
|
+
`remainingCount` and `wasDefault` alongside the customer. `wasDefault: true`
|
|
76
|
+
with `remainingCount > 0` is the state worth acting on — the client has cards
|
|
77
|
+
and nothing chargeable, and the first sign otherwise is a renewal-failure
|
|
78
|
+
email, then a paused schedule. **The package still promotes nothing**:
|
|
79
|
+
choosing a card nobody nominated is the one thing it refuses to do, and the
|
|
80
|
+
consumer knows what their customers mean by removing a default. Costs no
|
|
81
|
+
extra provider calls — the count comes from the listing the ownership check
|
|
82
|
+
already fetched.
|
|
83
|
+
- **`IRecurringInvoiceRepository.findByPaymentMethodId?()`** and
|
|
84
|
+
`RecurringInvoiceService.findByPaymentMethodId` — the schedules pinned to a
|
|
85
|
+
card, so "removing this card stops 3 renewals" can be said before it happens
|
|
86
|
+
rather than discovered after. Optional, so a v0.6.x adapter is unaffected;
|
|
87
|
+
the service throws a named error rather than reporting "nothing breaks"
|
|
88
|
+
without having checked. Closes the `remove-payment-method-cascade` thread
|
|
89
|
+
deferred in May 2026.
|
|
90
|
+
- **`PaymentMethodService.ownsPaymentMethod`** — the ownership check without
|
|
91
|
+
the throw, for a caller validating input rather than acting on it.
|
|
92
|
+
|
|
93
|
+
### Fixed — the September 2026 audit's remaining findings
|
|
94
|
+
|
|
95
|
+
The audit run before 0.6.0 fixed CRITICAL and HIGH and filed the rest. This
|
|
96
|
+
closes that list. Five of them could move or lose something real:
|
|
97
|
+
|
|
98
|
+
- **A PROCESSING refund could never be marked FAILED.** `handleRefundFailed`
|
|
99
|
+
gated on `PENDING`, but the Stripe mapping returns PROCESSING for everything
|
|
100
|
+
that is not `succeeded`/`pending`/`failed` — including `canceled`, which is
|
|
101
|
+
itself mapped to a `refund.failed` event. So the commonest route to this path
|
|
102
|
+
produced a row the path then refused to touch, and the refund sat in
|
|
103
|
+
PROCESSING for ever with the log calling it terminal. The gate is now the
|
|
104
|
+
terminal set. The success path never had the bug, which is the asymmetry that
|
|
105
|
+
hid it.
|
|
106
|
+
- **Webhook handlers looked up rows under a hard-coded `'stripe'`** — six
|
|
107
|
+
sites. Rows are keyed by `(provider, providerPaymentId)`, so a second
|
|
108
|
+
registered gateway would have resolved its events against Stripe's rows.
|
|
109
|
+
`PaymentWebhookEvent.provider` (optional; absent still means Stripe) now
|
|
110
|
+
carries the answer and `StripeGateway` stamps it.
|
|
111
|
+
- **`reconcilePayment` could never succeed for a Checkout row the webhook had
|
|
112
|
+
already recorded.** Swapping `providerPaymentId` to the PaymentIntent id hit
|
|
113
|
+
`@@unique([provider, providerPaymentId])`, nothing caught it, the transaction
|
|
114
|
+
rolled back and `needsReconciliation` stayed true — so every later sweep
|
|
115
|
+
retried the same doomed write. It now cancels the session alias and credits
|
|
116
|
+
the invoice nothing, which is what the webhook path already did.
|
|
117
|
+
- **`handleChargeFailure` overwrote a concurrent `resume()`.** It incremented a
|
|
118
|
+
count read before the provider call, so a resume landing in that window had
|
|
119
|
+
its reset written back over — and at three the template was re-paused
|
|
120
|
+
immediately. The count is re-read at the point of use, under a row lock where
|
|
121
|
+
the adapter offers `findByIdForUpdate`.
|
|
122
|
+
- **A failed overdue reminder vanished.** The reminder is one-shot (the invoice
|
|
123
|
+
is already OVERDUE, so no later run selects it) and the email service never
|
|
124
|
+
throws, so a transient SMTP failure lost it silently. The ordering stays —
|
|
125
|
+
the status must not wait on a mail server — but the loss is now reported in
|
|
126
|
+
`failures`.
|
|
127
|
+
|
|
128
|
+
Also: a webhook could create a payment for zero or a fraction of a cent; a
|
|
129
|
+
discount larger than the invoice stored a negative total; `recalculateTotals`
|
|
130
|
+
was a third writer of `amountDue` still computing it independently; the
|
|
131
|
+
division's "default provider" was whichever row the database happened to return
|
|
132
|
+
first; `createPaymentSession` could not be made idempotent; a declined card
|
|
133
|
+
with no PaymentIntent reported `providerPaymentId: 'unknown'`, a fake id that
|
|
134
|
+
two such failures would collide on; `isRecurringCapable` used `in`, which is
|
|
135
|
+
true for a capability an adapter withdrew by setting it to `undefined`; the
|
|
136
|
+
e-transfer charset contained `L` while its comment said it did not, in codes
|
|
137
|
+
people transcribe by hand; `pause(reason)` dropped the reason and announced
|
|
138
|
+
nothing, so consumers heard auto-pauses and never manual ones; and
|
|
139
|
+
`InvoiceNumberService` had a retry loop that ran once, a constant that meant
|
|
140
|
+
nothing, and an unreachable exception below it.
|
|
141
|
+
|
|
142
|
+
### Changed
|
|
143
|
+
- **`strict` is on.** The audit filed it as a release-sized job; it was one
|
|
144
|
+
line. Every error came from `strictPropertyInitialization`, which GraphQL
|
|
145
|
+
`@ObjectType` classes trip because mappers populate them — 168 of them, none
|
|
146
|
+
a defect. With that member off, full `strict` reports zero across `src/` and
|
|
147
|
+
`test/`, so everything else in it was already satisfied and merely switched
|
|
148
|
+
off. `noFallthroughCasesInSwitch` and `forceConsistentCasingInFileNames` too.
|
|
149
|
+
The compiler and the `no-unsafe-*` lint rules now pull the same way.
|
|
150
|
+
- **The `failureReason` vocabulary is documented.** The README listed three of
|
|
151
|
+
the package's tokens and omitted two; there was no enumeration anywhere else,
|
|
152
|
+
and `RecurringPaymentFailedEvent.failureReason` is a bare `string`. The full
|
|
153
|
+
set now has a table, separating the package's own tokens (configuration
|
|
154
|
+
problems, worth alerting on) from provider codes (ordinary declines).
|
|
155
|
+
- **The docs warn against subclassing `StripeGateway`.** A subclass declares
|
|
156
|
+
the signatures it was written against, so an optional parameter added later
|
|
157
|
+
is silently dropped. A consumer carried a 0.2.0-era subclass across an
|
|
158
|
+
upgrade and it discarded the `idempotencyKey` the package computes — two
|
|
159
|
+
PaymentIntents for one renewal, and a $5.00 charge against a $2.50 invoice,
|
|
160
|
+
verified in Stripe test mode. Implement `RecurringPaymentGateway` instead.
|
|
161
|
+
- **`CONTRIBUTING`'s semver table gains a row** for widening a service return
|
|
162
|
+
type. An intersection is assignable to its left side, so existing call sites
|
|
163
|
+
compile and it is a minor with an upgrade note — which is why 0.7.0 has no
|
|
164
|
+
breaking change despite `removePaymentMethod` returning more.
|
|
165
|
+
|
|
31
166
|
## [0.6.0] - 2026-09-19
|
|
32
167
|
|
|
33
168
|
> **Upgrade notes.** One optional database column; nothing else to migrate.
|
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ Production-grade payments module for NestJS with GraphQL support. Invoicing (inc
|
|
|
22
22
|
- [Recurring Invoices](#recurring-invoices)
|
|
23
23
|
- [Saved Cards](#saved-cards)
|
|
24
24
|
- [Managing saved cards](#managing-saved-cards)
|
|
25
|
+
- [Pinning a schedule to one card](#pinning-a-schedule-to-one-card)
|
|
25
26
|
- [E-Transfer](#e-transfer)
|
|
26
27
|
- [Email Notifications](#email-notifications)
|
|
27
28
|
- [Event Listeners](#event-listeners)
|
|
@@ -151,6 +152,8 @@ Some interfaces also carry **optional methods** that unlock a capability without
|
|
|
151
152
|
| `findReusablePendingByInvoiceId` | `IPaymentRepository` | Durable PaymentIntent reuse for inline flows (v0.3.0) |
|
|
152
153
|
| `findTaxComponentsByInvoice` + `replaceTaxComponents` | `IInvoiceRepository` | Invoices carrying more than one tax (v0.4.0) |
|
|
153
154
|
| `findTaxComponentsByRecurringInvoice` + `replaceTaxComponents` | `IRecurringInvoiceRepository` | The same, on recurring templates (v0.4.0) |
|
|
155
|
+
| `findByPaymentMethodId` | `IRecurringInvoiceRepository` | Lists the schedules pinned to a card, so you can warn before removing it (v0.7.0) |
|
|
156
|
+
| `findByIdForUpdate` | `IRecurringInvoiceRepository` | Locks the template so the auto-charge failure counter is a safe read-modify-write — without it a `resume()` landing mid-charge can be overwritten (v0.7.0) |
|
|
154
157
|
| `setDefaultPaymentMethod` | `IPaymentCustomerRepository` | Mirrors the provider's default card so it can be shown without a provider call; required by `features.cardManagement` (v0.6.0) |
|
|
155
158
|
| `findInstallmentByIdForUpdate` | `IPaymentPlanRepository` | Two concurrent `invoiceInstallment` calls for one installment cannot both link an invoice (v0.5.0) |
|
|
156
159
|
|
|
@@ -393,6 +396,7 @@ When `stripe` config is provided the module registers `StripeGateway`. **The web
|
|
|
393
396
|
- **Idempotency** — two levels, both required. *Event level*: each event ID is recorded via `IWebhookIdempotencyRepository` (inside the handler's transaction, so a failed attempt rolls the record back and Stripe's retry runs it again); replays no-op. Two deliveries of one event at the same moment are safe as well: the second one's `record` hits the unique constraint on the event ID, and the handler treats that as already processed and returns normally. For that, your adapter's `record` must translate the ORM's unique error into `UniqueConstraintViolationException`, as every other adapter method does, and never swallow it. *Payment level*: one payment emits more than one success event — a Checkout payment fires both `checkout.session.completed` and `payment_intent.succeeded`, under two different event IDs and two different provider payment IDs. `handlePaymentSucceeded` resolves both to a single `Payment` row by the PaymentIntent ID and applies the amount to the invoice only once, whichever event arrives first. Subscribing to both events is safe and expected. (Before v0.3.1 it was not: the second event re-applied the amount, inflating `amountPaid` on partially-paid invoices.)
|
|
394
397
|
- **Retries** — `payment_intent.succeeded` events retry up to 3 times with a 2-second delay if the corresponding `Payment` row has not yet been created.
|
|
395
398
|
- **Unrecognized event types** — Stripe fires many events per checkout (`payment_intent.created`, `charge.updated`, `charge.succeeded`, …) that the package does not route. These return 200 with a `debug`-level log and Stripe does not retry. The gateway surfaces them as an `IgnoredWebhookEvent` (`{ type: 'ignored', eventId, eventType }`) — if you implement a custom `PaymentGateway`, your `handleWebhook` must return this sentinel for unrouted events instead of throwing.
|
|
399
|
+
- **Which provider's rows an event addresses** — `PaymentWebhookEvent.provider` (v0.7.0). Payment and refund rows are keyed by `(provider, providerPaymentId)`, and before v0.7.0 the handlers wrote `'stripe'` into that lookup literally, so a second registered gateway would have had its events resolved against Stripe's rows. `StripeGateway` stamps the field on every event it emits; an event without it still means Stripe, so nothing changes for anyone who does not act. **If you write your own gateway, set it.**
|
|
396
400
|
- **Error reporting** — configure `webhook.errorReporter` to pipe exceptions into Sentry/GlitchTip/etc.
|
|
397
401
|
|
|
398
402
|
**Stripe Dashboard webhook subscriptions.** Your Stripe Dashboard webhook endpoint MUST subscribe to (at minimum):
|
|
@@ -449,6 +453,10 @@ A spec asserts this constant against the gateway's routing switch, so the export
|
|
|
449
453
|
|
|
450
454
|
`GatewayRegistryService` keeps a runtime lookup of registered gateways so the package can work without `@Optional() @Inject()`.
|
|
451
455
|
|
|
456
|
+
**Making a Checkout session idempotent.** `createPaymentSession` accepts an `idempotencyKey` (v0.7.0), which it passes to Stripe. Without one, a double-submitted "Pay now" creates two sessions for a single invoice and either can be paid.
|
|
457
|
+
|
|
458
|
+
The package does **not** invent a key here, though it does for `createRefund` and `createPaymentIntent`. Replaying either of those gives back the same refund or the same intent; replaying a Checkout Session hands the customer a URL that may already be dead or already paid. Only you know where your retry boundary is — a request id, an attempt number — so pass one.
|
|
459
|
+
|
|
452
460
|
#### Stripe-specific session options (`gatewayOptions`)
|
|
453
461
|
|
|
454
462
|
`PaymentGateway.createPaymentSession` accepts a gateway-agnostic `gatewayOptions?: Record<string, unknown>` for passthrough config. The Stripe gateway interprets it as follows:
|
|
@@ -855,6 +863,38 @@ Auto-charge: when `autoCharge: true`, the scheduler pulls the stored `PaymentCus
|
|
|
855
863
|
|
|
856
864
|
Auto-charge requires a saved card — see [Saved Cards](#saved-cards).
|
|
857
865
|
|
|
866
|
+
#### Pinning a schedule to one card
|
|
867
|
+
|
|
868
|
+
By default a renewal charges whatever the provider holds as the client's default. Set `paymentMethodId` on the template to charge a specific saved card instead — departmental billing, or a card that is not the client's default:
|
|
869
|
+
|
|
870
|
+
```typescript
|
|
871
|
+
await recurringInvoices.create(divisionId, {
|
|
872
|
+
...template,
|
|
873
|
+
autoCharge: true,
|
|
874
|
+
paymentMethodId: 'pm_1UHPtD…', // must already belong to this client
|
|
875
|
+
});
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
The card is checked against that client when the template is written, not when it is charged — a bad pin discovered at charge time is discovered as a failed renewal, which is the failure this exists to avoid. Requires `features.cardManagement`. Pass `null` on update to unpin and return the schedule to the client default.
|
|
879
|
+
|
|
880
|
+
**A pinned card that has been detached fails the renewal.** It does not fall back to the client default, because falling back charges a card the client deliberately did not nominate. The failure carries `payment_method_unavailable`, distinct from `no_payment_method`, so you can prompt for the right thing.
|
|
881
|
+
|
|
882
|
+
Before removing a card, `recurringInvoices.findByPaymentMethodId(pm)` lists the schedules pinned to it — "removing this card stops 3 renewals" is a warning a person can act on. Schedules riding the client default are not returned: they are not tied to any one card.
|
|
883
|
+
|
|
884
|
+
#### What `failureReason` can say
|
|
885
|
+
|
|
886
|
+
`onRecurringPaymentFailed` and `RecurringInvoice.lastFailureReason` carry one of these. The package's own tokens are configuration problems; anything else is the provider's own code for a decline.
|
|
887
|
+
|
|
888
|
+
| Token | Means |
|
|
889
|
+
|---|---|
|
|
890
|
+
| `invoice_not_payable` | The invoice was not in a payable status when the charge was attempted — usually `autoSend: false`, or sending failed |
|
|
891
|
+
| `no_payment_customer` | The client has no provider customer, so no card to charge |
|
|
892
|
+
| `no_payment_method` | The client has a provider customer but no card the package can resolve |
|
|
893
|
+
| `payment_method_unavailable` | The card this schedule is pinned to was refused by the provider — usually detached (v0.7.0) |
|
|
894
|
+
| `sca_required` | The charge needs customer authentication, which an off-session charge cannot collect |
|
|
895
|
+
| `unknown_error` | An error with no message reached the failure handler. Worth investigating rather than retrying |
|
|
896
|
+
| anything else | A provider code — `card_declined`, `expired_card`, `insufficient_funds`. These are ordinary declines |
|
|
897
|
+
|
|
858
898
|
Utility helpers exported:
|
|
859
899
|
|
|
860
900
|
```typescript
|
|
@@ -917,15 +957,30 @@ paymentCustomerRepositoryInstance: paymentCustomerRepo,
|
|
|
917
957
|
|---|---|
|
|
918
958
|
| `listPaymentMethods(divisionId, clientDetailsId, provider?)` | The client's saved cards, read from the provider, with `isDefault` flagged. Empty for a client who never saved one. |
|
|
919
959
|
| `setDefaultPaymentMethod(divisionId, clientDetailsId, paymentMethodId, provider?)` | Makes that card the one auto-charge uses. |
|
|
920
|
-
| `removePaymentMethod(divisionId, clientDetailsId, paymentMethodId, provider?)` | Detaches it. Removing the default clears it rather than leaving a card the client no longer has. |
|
|
960
|
+
| `removePaymentMethod(divisionId, clientDetailsId, paymentMethodId, provider?)` | Detaches it. Removing the default clears it rather than leaving a card the client no longer has. Returns the customer plus `{ remainingCount, wasDefault }` — see below (v0.7.0). |
|
|
921
961
|
| `confirmSetup(divisionId, clientDetailsId, setupIntentId, provider?)` | Records a completed setup session on demand, idempotent with the webhook. |
|
|
922
962
|
| `resolveChargeablePaymentMethod(divisionId, clientDetailsId, provider?)` | What an auto-charge would use, or `null`. |
|
|
923
963
|
| `requireChargeablePaymentMethod(…)` | As above but throws `NoChargeablePaymentMethodException`. |
|
|
924
964
|
|
|
925
965
|
**Ownership is checked in the package, not left to you.** Every method taking a `paymentMethodId` verifies it belongs to that client first and throws `PaymentMethodNotOwnedException` otherwise. Getting this wrong is silent and crosses tenants: a consumer who implemented card management themselves checked ownership in one of two adjacent methods and not the other, and the one that skipped it detached whatever id it was handed. If you drive a provider SDK directly anywhere, that check is your obligation.
|
|
926
966
|
|
|
967
|
+
**Removal reports what it broke, and promotes nothing.** `wasDefault: true` with `remainingCount > 0` is the state worth acting on: the client has cards and nothing chargeable, and the first sign otherwise is a renewal-failure email. The package will not promote a survivor — choosing a card nobody nominated is the one thing it refuses to do — so prompt the client, or promote from your side with `setDefaultPaymentMethod`. Pair it with `RecurringInvoiceService.findByPaymentMethodId` to say what stops renewing.
|
|
968
|
+
|
|
969
|
+
```typescript
|
|
970
|
+
const { remainingCount, wasDefault } = await paymentMethods.removePaymentMethod(
|
|
971
|
+
divisionId, clientDetailsId, 'pm_1UHPtD…',
|
|
972
|
+
);
|
|
973
|
+
if (wasDefault && remainingCount > 0) {
|
|
974
|
+
// They have cards and nothing chargeable. Ask which one takes over.
|
|
975
|
+
}
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
`remainingCount` is counted from the listing the ownership check already fetched, minus the card just removed — a fresh read would cost two more provider calls to report something this call already knew.
|
|
979
|
+
|
|
927
980
|
**The provider is written before the mirror.** If the mirror write fails, the call still succeeds and logs — the provider is what decides the charge, so a stale cache shows the wrong card while a stale provider would bill the wrong one.
|
|
928
981
|
|
|
982
|
+
> **Implement `RecurringPaymentGateway`; do not subclass `StripeGateway`.** A subclass declares the signatures it was written against, so every optional parameter added later is silently dropped on the way through. A consumer carried a 0.2.0-era subclass across an upgrade and it discarded the `idempotencyKey` the package computes — two PaymentIntents for one renewal, and a $5.00 charge against a $2.50 invoice. The failure is silent and monetary, and nothing in the type system catches it.
|
|
983
|
+
|
|
929
984
|
A custom gateway needs `listPaymentMethods`, `detachPaymentMethod`, `setDefaultPaymentMethod` and `getDefaultPaymentMethod` on `RecurringPaymentGateway`. All four are optional, so a v0.5.x gateway still compiles; `GatewayRegistryService.getPaymentMethodCapable()` throws `PaymentMethodOperationNotSupportedException` if any is missing.
|
|
930
985
|
|
|
931
986
|
**If no card resolves, the charge is refused.** `createOffSessionPayment` returns `failureReason: 'no_payment_method'` without calling the provider. It never falls back to the most recently added card — billing a card the client did not choose for this is worse than not billing.
|
|
@@ -1230,7 +1285,7 @@ What to watch once this is live. The package emits these; none of them page you
|
|
|
1230
1285
|
| `failures[]` | Return of all three `PaymentJobsService` jobs | Individual invoices failed their sweep. Non-empty for two consecutive runs is worth a look. |
|
|
1231
1286
|
| `updated` | `runReconciliation` | Payments the sweep had to fix because no webhook arrived. **Persistently non-zero means your webhook endpoint is broken**, and invoices are closing up to 15 minutes late. |
|
|
1232
1287
|
| Webhook 500 rate | Your HTTP logs, or `webhook.errorReporter` | Since v0.5.0 a failed handler answers 500 so Stripe retries. A sustained rate means handlers are failing and you have ~3 days before Stripe disables the endpoint. |
|
|
1233
|
-
| `onRecurringPaymentFailed` | Event listener |
|
|
1288
|
+
| `onRecurringPaymentFailed` | Event listener | See the `failureReason` table under [Recurring Invoices](#recurring-invoices). The package's own tokens are configuration problems, not declines, and are worth alerting on separately from card failures. |
|
|
1234
1289
|
| `onRecurringInvoicePaused` | Event listener | Three consecutive failures auto-paused a subscription. It stays paused until someone resumes it. |
|
|
1235
1290
|
| `Payment.needsReconciliation` | Your database | Set when a provider session could not be cancelled during a void. The session may still be payable. |
|
|
1236
1291
|
| `onPaymentDisputed` | Event listener | A chargeback. The funds are already gone; the package records it and leaves the invoice alone. |
|
|
@@ -119,6 +119,22 @@ __decorate([
|
|
|
119
119
|
(0, class_transformer_1.Type)(() => create_recurring_line_item_input_1.CreateRecurringLineItemInput),
|
|
120
120
|
__metadata("design:type", Array)
|
|
121
121
|
], CreateRecurringInvoiceInput.prototype, "lineItems", void 0);
|
|
122
|
+
__decorate([
|
|
123
|
+
(0, graphql_1.Field)({ nullable: true, description: 'Charge the saved card on renewal' }),
|
|
124
|
+
(0, class_validator_1.IsOptional)(),
|
|
125
|
+
(0, class_validator_1.IsBoolean)(),
|
|
126
|
+
__metadata("design:type", Boolean)
|
|
127
|
+
], CreateRecurringInvoiceInput.prototype, "autoCharge", void 0);
|
|
128
|
+
__decorate([
|
|
129
|
+
(0, graphql_1.Field)({
|
|
130
|
+
nullable: true,
|
|
131
|
+
description: 'Pin this schedule to one saved card instead of the client default',
|
|
132
|
+
}),
|
|
133
|
+
(0, class_validator_1.IsOptional)(),
|
|
134
|
+
(0, class_validator_1.IsString)(),
|
|
135
|
+
(0, class_validator_1.MaxLength)(255),
|
|
136
|
+
__metadata("design:type", String)
|
|
137
|
+
], CreateRecurringInvoiceInput.prototype, "paymentMethodId", void 0);
|
|
122
138
|
exports.CreateRecurringInvoiceInput = CreateRecurringInvoiceInput = __decorate([
|
|
123
139
|
(0, graphql_1.InputType)()
|
|
124
140
|
], CreateRecurringInvoiceInput);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-recurring-invoice.input.js","sourceRoot":"","sources":["../../src/dto/create-recurring-invoice.input.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,6CAA+D;AAC/D,yDAAyC;AACzC,qDAayB;AACzB,0EAA4C;AAC5C,yFAAkF;AAClF,6EAAuE;AACvE,0DAAsD;AACtD,+EAA8E;AAGvE,IAAM,2BAA2B,GAAjC,MAAM,2BAA2B;
|
|
1
|
+
{"version":3,"file":"create-recurring-invoice.input.js","sourceRoot":"","sources":["../../src/dto/create-recurring-invoice.input.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,6CAA+D;AAC/D,yDAAyC;AACzC,qDAayB;AACzB,0EAA4C;AAC5C,yFAAkF;AAClF,6EAAuE;AACvE,0DAAsD;AACtD,+EAA8E;AAGvE,IAAM,2BAA2B,GAAjC,MAAM,2BAA2B;CA0GvC,CAAA;AA1GY,kEAA2B;AAItC;IAHC,IAAA,eAAK,GAAE;IACP,IAAA,wBAAM,GAAE;IACR,IAAA,4BAAU,GAAE;;oEACW;AAGxB;IADC,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,mDAAyB,CAAC;;iEACC;AAMxC;IAJC,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,aAAG,CAAC;IAChB,IAAA,uBAAK,GAAE;IACP,IAAA,qBAAG,EAAC,CAAC,CAAC;IACN,IAAA,qBAAG,EAAC,GAAG,CAAC;;kEACa;AAItB;IAFC,IAAA,eAAK,GAAE;IACP,IAAA,wBAAM,GAAE;8BACG,IAAI;+DAAC;AAKjB;IAHC,IAAA,eAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACzB,IAAA,4BAAU,GAAE;IACZ,IAAA,wBAAM,GAAE;8BACO,IAAI;kEAAC;AAKrB;IAHC,IAAA,eAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;IAC7C,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;6DACO;AAOnB;IALC,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,eAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACtC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,CAAC;IACN,IAAA,qBAAG,EAAC,CAAC,CAAC;;4DACU;AAejB;IAJC,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,CAAC,oDAAuB,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1D,IAAA,4BAAU,GAAE;IACZ,IAAA,gCAAc,EAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC9B,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,oDAAuB,CAAC;;kEACM;AAM1C;IAJC,IAAA,eAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;IAC9C,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,2BAAS,EAAC,CAAC,CAAC;;6DACK;AAMlB;IAJC,IAAA,eAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACzB,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,2BAAS,EAAC,GAAG,CAAC;;kEACQ;AAKvB;IAHC,IAAA,eAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACzB,IAAA,4BAAU,GAAE;IACZ,IAAA,wBAAM,GAAE;;gEACY;AAMrB;IAJC,IAAA,eAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACzB,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,2BAAS,EAAC,IAAI,CAAC;;0DACD;AAMf;IAJC,IAAA,eAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACzB,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,2BAAS,EAAC,IAAI,CAAC;;gEACK;AAKrB;IAHC,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,2BAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC5C,IAAA,4BAAU,GAAE;IACZ,IAAA,wBAAU,GAAE;;6DACqB;AAKlC;IAHC,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,CAAC,+DAA4B,CAAC,CAAC;IAC3C,IAAA,gCAAc,EAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC9B,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,+DAA4B,CAAC;;8DACC;AAK1C;IAHC,IAAA,eAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,kCAAkC,EAAE,CAAC;IAC1E,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;+DACS;AAYrB;IAVC,IAAA,eAAK,EAAC;QACL,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,mEAAmE;KACtE,CAAC;IACD,IAAA,4BAAU,GAAE;IAGZ,IAAA,0BAAQ,GAAE;IACV,IAAA,2BAAS,EAAC,GAAG,CAAC;;oEACU;sCAzGd,2BAA2B;IADvC,IAAA,mBAAS,GAAE;GACC,2BAA2B,CA0GvC"}
|
|
@@ -91,6 +91,19 @@ __decorate([
|
|
|
91
91
|
(0, class_transformer_1.Type)(() => create_recurring_line_item_input_1.CreateRecurringLineItemInput),
|
|
92
92
|
__metadata("design:type", Array)
|
|
93
93
|
], UpdateRecurringTemplateInput.prototype, "lineItems", void 0);
|
|
94
|
+
__decorate([
|
|
95
|
+
(0, graphql_1.Field)({ nullable: true }),
|
|
96
|
+
(0, class_validator_1.IsOptional)(),
|
|
97
|
+
(0, class_validator_1.IsBoolean)(),
|
|
98
|
+
__metadata("design:type", Boolean)
|
|
99
|
+
], UpdateRecurringTemplateInput.prototype, "autoCharge", void 0);
|
|
100
|
+
__decorate([
|
|
101
|
+
(0, graphql_1.Field)(() => String, { nullable: true }),
|
|
102
|
+
(0, class_validator_1.IsOptional)(),
|
|
103
|
+
(0, class_validator_1.IsString)(),
|
|
104
|
+
(0, class_validator_1.MaxLength)(255),
|
|
105
|
+
__metadata("design:type", Object)
|
|
106
|
+
], UpdateRecurringTemplateInput.prototype, "paymentMethodId", void 0);
|
|
94
107
|
exports.UpdateRecurringTemplateInput = UpdateRecurringTemplateInput = __decorate([
|
|
95
108
|
(0, graphql_1.InputType)()
|
|
96
109
|
], UpdateRecurringTemplateInput);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-recurring-template.input.js","sourceRoot":"","sources":["../../src/dto/update-recurring-template.input.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,6CAA0D;AAC1D,yDAAyC;AACzC,qDAUyB;AACzB,0EAA4C;AAC5C,yFAAkF;AAClF,6EAAuE;AACvE,0DAAsD;AAG/C,IAAM,4BAA4B,GAAlC,MAAM,4BAA4B;
|
|
1
|
+
{"version":3,"file":"update-recurring-template.input.js","sourceRoot":"","sources":["../../src/dto/update-recurring-template.input.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/exceptions/index.ts"],"names":[],"mappings":";;;AAQA,MAAa,gBAAiB,SAAQ,KAAK;IAGzC,YAAY,OAAe,EAAE,IAAY;QACvC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;QAElC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF;AAVD,4CAUC;AAMD,MAAa,4BAA6B,SAAQ,gBAAgB;IAChE,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC;IAC1C,CAAC;CACF;AAJD,oEAIC;AAKD,MAAa,8BAA+B,SAAQ,gBAAgB;IAClE,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,yBAAyB,CAAC,CAAC;IAC5C,CAAC;CACF;AAJD,wEAIC;AAKD,MAAa,uBAAwB,SAAQ,gBAAgB;IAC3D,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC;IAC1C,CAAC;CACF;AAJD,0DAIC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/exceptions/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -56,9 +56,10 @@ let GatewayRegistryService = class GatewayRegistryService {
|
|
|
56
56
|
return Array.from(this.gateways.keys());
|
|
57
57
|
}
|
|
58
58
|
isRecurringCapable(gateway) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
'
|
|
59
|
+
const g = gateway;
|
|
60
|
+
return (typeof g.createOrRetrieveCustomer === 'function' &&
|
|
61
|
+
typeof g.createOffSessionPayment === 'function' &&
|
|
62
|
+
typeof g.createSetupSession === 'function');
|
|
62
63
|
}
|
|
63
64
|
};
|
|
64
65
|
exports.GatewayRegistryService = GatewayRegistryService;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway-registry.service.js","sourceRoot":"","sources":["../../src/gateways/gateway-registry.service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAG5C,8CAGuB;AA6ChB,IAAM,sBAAsB,GAA5B,MAAM,sBAAsB;IAA5B;QACY,aAAQ,GAAG,IAAI,GAAG,EAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"gateway-registry.service.js","sourceRoot":"","sources":["../../src/gateways/gateway-registry.service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAG5C,8CAGuB;AA6ChB,IAAM,sBAAsB,GAA5B,MAAM,sBAAsB;IAA5B;QACY,aAAQ,GAAG,IAAI,GAAG,EAA0B,CAAC;IAkHhE,CAAC;IA/GC,QAAQ,CAAC,OAAuB;QAC9B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IACnD,CAAC;IAMD,GAAG,CAAC,YAAoB;QACtB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAChD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,oDAAoD,YAAY,GAAG,CACpE,CAAC;QACJ,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAMD,YAAY,CAAC,YAAoB;QAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,KAAK,CACb,YAAY,YAAY,uCAAuC,CAChE,CAAC;QACJ,CAAC;QACD,OAAO,OAAmD,CAAC;IAC7D,CAAC;IAaD,gBAAgB,CAAC,YAAoB;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACvC,IACE,OAAO,OAAO,CAAC,mBAAmB,KAAK,UAAU;YACjD,OAAO,OAAO,CAAC,mBAAmB,KAAK,UAAU,EACjD,CAAC;YACD,MAAM,IAAI,wDAA2C,CACnD,YAAY,YAAY,oDAAoD,CAC7E,CAAC;QACJ,CAAC;QACD,OAAO,OAA+B,CAAC;IACzC,CAAC;IAcD,uBAAuB,CAAC,YAAoB;QAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CACrB,CAAC;QACjB,MAAM,OAAO,GACX;YACE,oBAAoB;YACpB,qBAAqB;YACrB,yBAAyB;YACzB,yBAAyB;SAE5B,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,CAAC;QAExD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,wDAA2C,CACnD,YAAY,YAAY,2CAA2C;gBACjE,aAAa,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACrC,CAAC;QACJ,CAAC;QACD,OAAO,OAAsC,CAAC;IAChD,CAAC;IAGD,YAAY;QACV,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1C,CAAC;IAWO,kBAAkB,CAAC,OAAuB;QAChD,MAAM,CAAC,GAAG,OAA2C,CAAC;QACtD,OAAO,CACL,OAAO,CAAC,CAAC,wBAAwB,KAAK,UAAU;YAChD,OAAO,CAAC,CAAC,uBAAuB,KAAK,UAAU;YAC/C,OAAO,CAAC,CAAC,kBAAkB,KAAK,UAAU,CAC3C,CAAC;IACJ,CAAC;CACF,CAAA;AAnHY,wDAAsB;iCAAtB,sBAAsB;IADlC,IAAA,mBAAU,GAAE;GACA,sBAAsB,CAmHlC"}
|
|
@@ -10,7 +10,7 @@ export interface PaymentGateway {
|
|
|
10
10
|
cancelUrl: string;
|
|
11
11
|
metadata?: Record<string, string>;
|
|
12
12
|
expiresAt?: Date;
|
|
13
|
-
|
|
13
|
+
idempotencyKey?: string;
|
|
14
14
|
}): Promise<{
|
|
15
15
|
checkoutUrl: string;
|
|
16
16
|
providerPaymentId: string;
|
|
@@ -67,6 +67,7 @@ export type PaymentWebhookEventType = 'payment.processing' | 'payment.succeeded'
|
|
|
67
67
|
export interface PaymentWebhookEvent {
|
|
68
68
|
type: PaymentWebhookEventType;
|
|
69
69
|
eventId: string;
|
|
70
|
+
provider?: string;
|
|
70
71
|
providerPaymentId: string;
|
|
71
72
|
providerRefundId?: string;
|
|
72
73
|
amount: number;
|
|
@@ -117,8 +117,12 @@ let StripeWebhookController = StripeWebhookController_1 = class StripeWebhookCon
|
|
|
117
117
|
case 'setup.succeeded':
|
|
118
118
|
await this.adoptSetupPaymentMethod(event);
|
|
119
119
|
break;
|
|
120
|
+
case 'payment.cancelled':
|
|
121
|
+
this.logger.log(`Payment cancelled at the provider (${event.providerPaymentId}); ` +
|
|
122
|
+
`no action taken.`);
|
|
123
|
+
break;
|
|
120
124
|
default:
|
|
121
|
-
this.logger.warn(`Unhandled webhook event type: ${event.type}`);
|
|
125
|
+
this.logger.warn(`Unhandled webhook event type: ${String(event.type)}`);
|
|
122
126
|
}
|
|
123
127
|
}
|
|
124
128
|
async adoptSetupPaymentMethod(event) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stripe-webhook.controller.js","sourceRoot":"","sources":["../../../src/gateways/stripe/stripe-webhook.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAWwB;AACxB,uCAAyC;AAIzC,0EAAqE;AACrE,oEAAgE;AAChE,kEAA8D;AAC9D,+CAAyD;AAOzD,MAAM,WAAW,GAAG,CAAC,CAAC;AAGtB,MAAM,cAAc,GAAG,IAAI,CAAC;AAGf,QAAA,gBAAgB,GAAG,kBAAkB,CAAC;AAY5C,MAAM,eAAe,GAAG,GAAG,EAAE,CAClC,IAAA,wBAAe,EACb,IAAA,oBAAW,EAAC,wBAAgB,EAAE,IAAI,CAAC,EACnC,IAAA,oBAAW,EAAC,UAAU,EAAE,IAAI,CAAC,EAC7B,IAAA,oBAAW,EAAC,YAAY,EAAE,IAAI,CAAC,CAChC,CAAC;AALS,QAAA,eAAe,mBAKxB;AAcG,IAAM,uBAAuB,+BAA7B,MAAM,uBAAuB;IAMlC,YACmB,SAAoB,EACpB,eAAuC,EACvC,cAA8B,EAC9B,aAA4B;QAH5B,cAAS,GAAT,SAAS,CAAW;QACpB,oBAAe,GAAf,eAAe,CAAwB;QACvC,mBAAc,GAAd,cAAc,CAAgB;QAC9B,kBAAa,GAAb,aAAa,CAAe;QAT9B,WAAM,GAAG,IAAI,eAAM,CAAC,yBAAuB,CAAC,IAAI,CAAC,CAAC;QAC3D,kBAAa,GAEV,IAAI,CAAC;IAOb,CAAC;IAEJ,YAAY;QACV,IAAI,CAAC;YACH,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,kCAAsB,EAAE;gBAC9D,MAAM,EAAE,KAAK;aACd,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC5B,CAAC;IACH,CAAC;IAIK,AAAN,KAAK,CAAC,mBAAmB,CAChB,GAA4B;QAEnC,IAAI,aAA4B,CAAC;QACjC,IAAI,CAAC;YACH,aAAa,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAkB,CAAC;QACtE,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,4BAAmB,CAAC,kCAAkC,CAAC,CAAC;QACpE,CAAC;QAED,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,4BAAmB,CAC3B,2CAA2C,CAC5C,CAAC;QACJ,CAAC;QAGD,MAAM,OAAO,GAA2B,EAAE,CAAC;QAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACvD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC9B,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACvB,CAAC;QACH,CAAC;QAED,IAAI,KAA6B,CAAC;QAClC,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,aAAa,CAAC,aAAa,CAAC;gBACxC,OAAO;gBACP,IAAI,EAAE,OAAO;aACd,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,iDAAiD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAC5G,CAAC;YACF,MAAM,IAAI,4BAAmB,CAAC,uCAAuC,CAAC,CAAC;QACzE,CAAC;QAKD,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,iCAAiC,KAAK,CAAC,OAAO,UAAU,KAAK,CAAC,SAAS,GAAG,CAC3E,CAAC;YACF,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC5B,CAAC;QAGD,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,yCAAyC,KAAK,CAAC,OAAO,KAAK,KAAK,CAAC,IAAI,MAAM,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CACtI,CAAC;YACF,IAAI,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE;gBAC1B,IAAI,EAAE;oBACJ,gBAAgB,EAAE,KAAK,CAAC,IAAI;oBAC5B,cAAc,EAAE,KAAK,CAAC,OAAO;oBAC7B,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;iBAC3C;aACF,CAAC,CAAC;YAQH,MAAM,IAAI,qCAA4B,CACpC,kEAAkE,CACnE,CAAC;QACJ,CAAC;QAED,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC5B,CAAC;IASO,KAAK,CAAC,UAAU,CAAC,KAA0B;QACjD,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,mBAAmB;gBACtB,MAAM,IAAI,CAAC,eAAe,CACxB,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,sBAAsB,CAAC,KAAK,CAAC,EACvD,KAAK,CACN,CAAC;gBACF,MAAM;YAER,KAAK,gBAAgB;gBACnB,MAAM,IAAI,CAAC,cAAc,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;gBACrD,MAAM;YAER,KAAK,oBAAoB;gBACvB,MAAM,IAAI,CAAC,cAAc,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC;gBACzD,MAAM;YAER,KAAK,kBAAkB;gBACrB,MAAM,IAAI,CAAC,aAAa,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC;gBACtD,MAAM;YAER,KAAK,eAAe;gBAClB,MAAM,IAAI,CAAC,aAAa,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;gBACnD,MAAM;YAER,KAAK,kBAAkB,CAAC;YACxB,KAAK,wBAAwB;gBAC3B,MAAM,IAAI,CAAC,cAAc,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC;gBACvD,MAAM;YAER,KAAK,iBAAiB;gBAKpB,MAAM,IAAI,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC;gBAC1C,MAAM;YAER;
|
|
1
|
+
{"version":3,"file":"stripe-webhook.controller.js","sourceRoot":"","sources":["../../../src/gateways/stripe/stripe-webhook.controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -25,6 +25,7 @@ export declare class StripeGateway implements PaymentGateway, RecurringPaymentGa
|
|
|
25
25
|
metadata?: Record<string, string>;
|
|
26
26
|
expiresAt?: Date;
|
|
27
27
|
gatewayOptions?: Record<string, unknown>;
|
|
28
|
+
idempotencyKey?: string;
|
|
28
29
|
}): Promise<{
|
|
29
30
|
checkoutUrl: string;
|
|
30
31
|
providerPaymentId: string;
|
|
@@ -106,6 +107,7 @@ export declare class StripeGateway implements PaymentGateway, RecurringPaymentGa
|
|
|
106
107
|
madeDefault: boolean;
|
|
107
108
|
}>;
|
|
108
109
|
private wrapStripeError;
|
|
110
|
+
private isMissingPaymentMethod;
|
|
109
111
|
private resolveDefaultPaymentMethod;
|
|
110
112
|
createSetupSession(params: {
|
|
111
113
|
providerCustomerId: string;
|
|
@@ -86,7 +86,9 @@ class StripeGateway {
|
|
|
86
86
|
}),
|
|
87
87
|
metadata: mergedMetadata,
|
|
88
88
|
};
|
|
89
|
-
const session = await this.stripe.checkout.sessions.create(sdkParams
|
|
89
|
+
const session = await this.stripe.checkout.sessions.create(sdkParams, params.idempotencyKey
|
|
90
|
+
? { idempotencyKey: params.idempotencyKey }
|
|
91
|
+
: undefined);
|
|
90
92
|
if (!session.url) {
|
|
91
93
|
throw new exceptions_1.PaymentGatewayException('Stripe Checkout session created but no URL returned');
|
|
92
94
|
}
|
|
@@ -494,17 +496,28 @@ class StripeGateway {
|
|
|
494
496
|
return {
|
|
495
497
|
providerPaymentId: paymentIntent.id,
|
|
496
498
|
status: 'failed',
|
|
497
|
-
failureReason: paymentIntent.last_payment_error?.code ?? '
|
|
499
|
+
failureReason: paymentIntent.last_payment_error?.code ?? 'unknown_error',
|
|
498
500
|
};
|
|
499
501
|
}
|
|
500
502
|
catch (error) {
|
|
501
503
|
if (error instanceof stripe_1.default.errors.StripeCardError) {
|
|
502
504
|
return {
|
|
503
|
-
providerPaymentId: error.payment_intent?.id ?? '
|
|
505
|
+
providerPaymentId: error.payment_intent?.id ?? '',
|
|
504
506
|
status: 'failed',
|
|
505
507
|
failureReason: error.code ?? 'card_declined',
|
|
506
508
|
};
|
|
507
509
|
}
|
|
510
|
+
if (error instanceof stripe_1.default.errors.StripeInvalidRequestError &&
|
|
511
|
+
this.isMissingPaymentMethod(error)) {
|
|
512
|
+
this.logger.warn(`Off-session charge for customer ${params.providerCustomerId} named ` +
|
|
513
|
+
`payment method ${params.paymentMethodId ?? '(resolved default)'}, ` +
|
|
514
|
+
`which the provider will not accept: ${error.message}`);
|
|
515
|
+
return {
|
|
516
|
+
providerPaymentId: error.payment_intent?.id ?? '',
|
|
517
|
+
status: 'failed',
|
|
518
|
+
failureReason: 'payment_method_unavailable',
|
|
519
|
+
};
|
|
520
|
+
}
|
|
508
521
|
throw new exceptions_1.PaymentGatewayException(`Off-session payment failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
509
522
|
}
|
|
510
523
|
}
|
|
@@ -614,6 +627,14 @@ class StripeGateway {
|
|
|
614
627
|
}
|
|
615
628
|
return error;
|
|
616
629
|
}
|
|
630
|
+
isMissingPaymentMethod(error) {
|
|
631
|
+
const param = error.param ?? '';
|
|
632
|
+
if (param === 'payment_method' || param.startsWith('payment_method')) {
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
635
|
+
return (error.code === 'resource_missing' &&
|
|
636
|
+
/payment ?method/i.test(error.message ?? ''));
|
|
637
|
+
}
|
|
617
638
|
async resolveDefaultPaymentMethod(providerCustomerId) {
|
|
618
639
|
const customer = await this.stripe.customers.retrieve(providerCustomerId);
|
|
619
640
|
if (customer.deleted)
|
|
@@ -716,6 +737,7 @@ class StripeGateway {
|
|
|
716
737
|
? 'payment.succeeded'
|
|
717
738
|
: 'payment.processing'),
|
|
718
739
|
eventId: event.id,
|
|
740
|
+
provider: this.providerType,
|
|
719
741
|
providerPaymentId: session.id,
|
|
720
742
|
amount,
|
|
721
743
|
currency,
|
|
@@ -742,6 +764,7 @@ class StripeGateway {
|
|
|
742
764
|
? 'payment.dispute_closed'
|
|
743
765
|
: 'payment.disputed'),
|
|
744
766
|
eventId: event.id,
|
|
767
|
+
provider: this.providerType,
|
|
745
768
|
providerPaymentId: paymentIntentId ?? chargeId,
|
|
746
769
|
amount: dispute.amount,
|
|
747
770
|
currency: dispute.currency.toUpperCase(),
|
|
@@ -762,6 +785,7 @@ class StripeGateway {
|
|
|
762
785
|
return {
|
|
763
786
|
type: 'setup.succeeded',
|
|
764
787
|
eventId: event.id,
|
|
788
|
+
provider: this.providerType,
|
|
765
789
|
providerPaymentId: session.setup_intent ?? session.id,
|
|
766
790
|
amount: 0,
|
|
767
791
|
currency: (session.currency ?? 'usd').toUpperCase(),
|
|
@@ -778,6 +802,7 @@ class StripeGateway {
|
|
|
778
802
|
return {
|
|
779
803
|
type: 'payment.processing',
|
|
780
804
|
eventId: event.id,
|
|
805
|
+
provider: this.providerType,
|
|
781
806
|
providerPaymentId: intent.id,
|
|
782
807
|
amount: intent.amount,
|
|
783
808
|
currency: intent.currency.toUpperCase(),
|
|
@@ -806,6 +831,7 @@ class StripeGateway {
|
|
|
806
831
|
return {
|
|
807
832
|
type: 'payment.succeeded',
|
|
808
833
|
eventId: event.id,
|
|
834
|
+
provider: this.providerType,
|
|
809
835
|
providerPaymentId: intent.id,
|
|
810
836
|
amount: intent.amount,
|
|
811
837
|
currency: intent.currency.toUpperCase(),
|
|
@@ -822,6 +848,7 @@ class StripeGateway {
|
|
|
822
848
|
return {
|
|
823
849
|
type: 'payment.failed',
|
|
824
850
|
eventId: event.id,
|
|
851
|
+
provider: this.providerType,
|
|
825
852
|
providerPaymentId: intent.id,
|
|
826
853
|
amount: intent.amount,
|
|
827
854
|
currency: intent.currency.toUpperCase(),
|
|
@@ -849,6 +876,7 @@ class StripeGateway {
|
|
|
849
876
|
return {
|
|
850
877
|
type,
|
|
851
878
|
eventId: event.id,
|
|
879
|
+
provider: this.providerType,
|
|
852
880
|
providerPaymentId: paymentIntentId,
|
|
853
881
|
providerRefundId: refund.id,
|
|
854
882
|
amount: refund.amount,
|