@crvouga/mockingbird-service-stripe 1.2.0 → 1.3.1
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 +31 -1
- package/README.md +84 -9
- package/dist/chunk-E54EAWQ7.js +17247 -0
- package/dist/chunk-E54EAWQ7.js.map +7 -0
- package/dist/{chunk-NCLUPCM4.js → chunk-VNST6CJX.js} +2 -2
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +168 -6
- package/dist/index.js +1 -1
- package/dist/server.d.ts +165 -3
- package/dist/server.js +2 -2
- package/package.json +3 -2
- package/dist/chunk-GOUPQLPK.js +0 -15220
- package/dist/chunk-GOUPQLPK.js.map +0 -7
- /package/dist/{chunk-NCLUPCM4.js.map → chunk-VNST6CJX.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-stripe
|
|
2
2
|
|
|
3
|
-
## 1.
|
|
3
|
+
## 1.3.1 (2026-09-29)
|
|
4
|
+
|
|
5
|
+
### Fixes and improvements
|
|
6
|
+
|
|
7
|
+
- say 'No such configuration' for a missing portal configuration ([6fad72a](https://github.com/crvouga/mockingbird/commit/6fad72a676b359672653508d9dd654e656ac6aa1))
|
|
8
|
+
- upper-case only two-letter country codes like the live API ([4ee5121](https://github.com/crvouga/mockingbird/commit/4ee5121cf2254fd75eef34bbeefee496cd16964a))
|
|
9
|
+
- leave portal configuration listing out of live parity walks ([20d715a](https://github.com/crvouga/mockingbird/commit/20d715a548e60dd933fab52e7ba661870607049e))
|
|
10
|
+
- keep the shipping address country verbatim like the live API ([052e285](https://github.com/crvouga/mockingbird/commit/052e285a0ae9bcc26050f1c50ed635085438c3d1))
|
|
11
|
+
- use --all-snapshot so the Stripe CLI webhook oracle starts ([09809bf](https://github.com/crvouga/mockingbird/commit/09809bfcd106cc4570d74e781ab0619efb191e05))
|
|
12
|
+
- listen for all events so newer Stripe CLIs start the webhook oracle ([278e364](https://github.com/crvouga/mockingbird/commit/278e364c77263d8cff65fb6d03fde9e89a1010ca))
|
|
13
|
+
- surface redacted stripe listen output when the webhook oracle fails to start ([dd179bb](https://github.com/crvouga/mockingbird/commit/dd179bbdf7a0d38ba097f79041ec818eaf69dfc5))
|
|
14
|
+
- sign and retry webhooks on the injected clock ([1091bde](https://github.com/crvouga/mockingbird/commit/1091bde0df69d9cc3eba2062d8523755e6caca14))
|
|
15
|
+
|
|
16
|
+
## 1.3.0 (2026-09-29)
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
- hosted customer portal, trial_end=now and subscription lifecycle refinements ([f8d0567](https://github.com/crvouga/mockingbird/commit/f8d0567928a7d5fdc0463cc691f5350f9cb4d1c6))
|
|
21
|
+
- let a scalar naming a later branch's enum value pick that branch ([30ade3a](https://github.com/crvouga/mockingbird/commit/30ade3a28a5b6b273ffbf261216b821acac8e961))
|
|
22
|
+
- promotion codes, inline prices and trials in Checkout ([f60923c](https://github.com/crvouga/mockingbird/commit/f60923c915009ad0d264237f36338a13fb546874))
|
|
23
|
+
- bill prorations, pause collection and uncollectible invoices ([089fc7e](https://github.com/crvouga/mockingbird/commit/089fc7eb86700ef678b1e2a509674565b48f410f))
|
|
24
|
+
- vendor billing portal, subscription resume and trial settings ([09f7280](https://github.com/crvouga/mockingbird/commit/09f7280db744117fce16a6e0606b41f90d67fa54))
|
|
25
|
+
|
|
26
|
+
### Fixes and improvements
|
|
27
|
+
|
|
28
|
+
- stop cleared webhook deliveries from rescheduling forever ([424b78f](https://github.com/crvouga/mockingbird/commit/424b78f29317b162e3bf7d0049cc19be5b977026))
|
|
29
|
+
- serialize immediate webhook attempts to fix reorder flake ([625c82b](https://github.com/crvouga/mockingbird/commit/625c82b5a8a7992c1cf93e9df68e77af08b353e3))
|
|
30
|
+
- omit null advice and network decline codes from card errors ([34f074a](https://github.com/crvouga/mockingbird/commit/34f074a143c3d4d15f6fd472c51f6f4b341d1baf))
|
|
31
|
+
- flush drains webhook retries scheduled while it runs ([c58cede](https://github.com/crvouga/mockingbird/commit/c58cede1b6ba7ae5ed3f8721ad9b7cd7bc0b1ca7))
|
|
32
|
+
|
|
33
|
+
## 1.2.0 (2026-09-25)
|
|
4
34
|
|
|
5
35
|
### Features
|
|
6
36
|
|
package/README.md
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Stateful, in-process mock of the [Stripe API](https://docs.stripe.com/api) for test suites: accounts
|
|
4
4
|
chosen by API key, customers and balances, payment methods, payment and setup intents, charges,
|
|
5
|
-
refunds, disputes, checkout (with a hosted page and a Stripe.js stand-in),
|
|
6
|
-
|
|
5
|
+
refunds, disputes, checkout (with a hosted page and a Stripe.js stand-in), the customer portal
|
|
6
|
+
(configurations, sessions and the hosted portal page), invoices, subscriptions that renew, pause,
|
|
7
|
+
resume and cancel as the clock moves, subscription schedules, coupons, promotion codes, products,
|
|
7
8
|
prices, test clocks, webhook endpoints, the balance ledger and the event log — with signed webhooks
|
|
8
9
|
fanned out to every matching endpoint. Responses are rendered at the caller's `Stripe-Version`
|
|
9
10
|
(`2024-06-20`, `2025-02-24.acacia`, or the vendored latest), and the whole surface is verified by
|
|
10
11
|
differential property tests against Stripe test mode.
|
|
11
12
|
|
|
12
|
-
- Operation coverage (
|
|
13
|
+
- Operation coverage (119 of 123 operations in the vendored spec, with reasons for each gap):
|
|
13
14
|
[SUPPORT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/stripe/SUPPORT.md)
|
|
14
15
|
- Stripe API reference: https://docs.stripe.com/api · Upstream OpenAPI: https://github.com/stripe/openapi
|
|
15
16
|
|
|
@@ -117,7 +118,7 @@ holds, ancestors included); Stripe's own rules are enforced: a non-expandable fi
|
|
|
117
118
|
|
|
118
119
|
Every state change records an event in the account's log (`GET /v1/events`, filterable by
|
|
119
120
|
`types[]` and `created`) and publishes it through the shared webhook hub, signed
|
|
120
|
-
`Stripe-Signature: t=<
|
|
121
|
+
`Stripe-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "t.body")>` over the exact bytes —
|
|
121
122
|
`stripe.webhooks.constructEvent` verifies them.
|
|
122
123
|
|
|
123
124
|
- Endpoints: `PUT /__admin/webhook-endpoints [{account, url, secret, enabledEvents: ["*"|…]}]`
|
|
@@ -125,6 +126,11 @@ Every state change records an event in the account's log (`GET /v1/events`, filt
|
|
|
125
126
|
`serve --webhook-url/--webhook-secret`, accounts' `webhookSecrets`, and endpoints created through
|
|
126
127
|
`POST /v1/webhook_endpoints` (signed with the `whsec_` returned at creation). One event fans out to
|
|
127
128
|
every matching endpoint, as on Stripe. Retries follow the hub's schedule.
|
|
129
|
+
- `t` is the wall clock, unless the runtime's `clock` is injected (`createRuntime({ clock })`):
|
|
130
|
+
then `t`, retries and the attempt timeout run on that clock, so a virtual-time suite verifies with
|
|
131
|
+
`constructEventAsync(body, sig, secret, tolerance, undefined, clock.now())` and sees a retry when it
|
|
132
|
+
advances the clock (`runtime.clock.advance`, or `runtime.tick()` after moving its own clock).
|
|
133
|
+
`webhooks: { now, schedule, cancel, id }` override the hub's sources directly.
|
|
128
134
|
- `GET /__admin/webhooks`, `/webhooks/events`, `POST /__admin/webhooks/:id/replay`, `/webhooks/flush`.
|
|
129
135
|
- Delivery faults: presets `webhook_duplicate` (same event id twice — our receiver's in-flight
|
|
130
136
|
dedupe answers 500), `webhook_reorder` (the next two swapped), `webhook_drop` (never delivered,
|
|
@@ -152,7 +158,19 @@ clock-driven lifecycle, so their webhooks fire at once; the served mock also tic
|
|
|
152
158
|
finalized and charged off-session to the default payment method, then `invoice.paid` +
|
|
153
159
|
`customer.subscription.updated` (`previous_attributes.current_period_end`), or
|
|
154
160
|
`invoice.payment_failed` and `past_due`. Trials end into a cycle; `cancel_at_period_end` cancels
|
|
155
|
-
(`customer.subscription.deleted`). `invoice.upcoming` fires 3 days before renewal
|
|
161
|
+
(`customer.subscription.deleted`). `invoice.upcoming` fires 3 days before renewal and
|
|
162
|
+
`customer.subscription.trial_will_end` 3 days before a trial ends (once per trial).
|
|
163
|
+
- **Trials without a card**: `trial_settings[end_behavior][missing_payment_method]` decides what a
|
|
164
|
+
trial ending with no default payment method does — `create_invoice` (the default) invoices and
|
|
165
|
+
goes `past_due`, `cancel` cancels at the trial's end, `pause` sets status `paused`
|
|
166
|
+
(`customer.subscription.paused`) until `POST /v1/subscriptions/:id/resume`:
|
|
167
|
+
`billing_cycle_anchor=now` (default) starts a new period with a `subscription_update` invoice
|
|
168
|
+
(paid → `active`, failed → `past_due`); `unchanged` keeps the anchor and prorates the rest of the
|
|
169
|
+
period per `proration_behavior` (and `proration_date`). Both emit `customer.subscription.resumed`.
|
|
170
|
+
- **Paused collection**: `pause_collection[behavior]` keeps periods moving while renewal invoices are
|
|
171
|
+
voided (`void`), marked `uncollectible` (`mark_uncollectible`, `invoice.marked_uncollectible`) or
|
|
172
|
+
left as drafts (`keep_as_draft`), never charged; the status holds. `pause_collection[resumes_at]`
|
|
173
|
+
lifts the pause on its own before the renewal it precedes; `pause_collection=""` lifts it now.
|
|
156
174
|
- `incomplete` subscriptions become `incomplete_expired` after 23 h (their invoice is voided).
|
|
157
175
|
- Checkout Sessions expire at `expires_at` (`checkout.session.expired`).
|
|
158
176
|
- Schedule phases advance; the last one releases or cancels per `end_behavior`.
|
|
@@ -167,7 +185,11 @@ now and returns `incomplete` on a decline; `error_if_incomplete` fails the call
|
|
|
167
185
|
customer, and paying it through Stripe.js activates the subscription. `trial_end`,
|
|
168
186
|
`backdate_start_date` + `billing_cycle_anchor` + `proration_behavior=none` (a $0 first invoice),
|
|
169
187
|
item updates with `always_invoice` (billed now) or `create_prorations` (next invoice), and
|
|
170
|
-
discounts with stable `di_` ids (`discounts=""` clears) are modelled.
|
|
188
|
+
discounts with stable `di_` ids (`discounts=""` clears) are modelled. Items must be active recurring
|
|
189
|
+
prices sharing one currency and interval. `DELETE /v1/subscriptions/:id` takes
|
|
190
|
+
`cancellation_details[comment|feedback]`, `prorate` (a credit for the unused time as a pending
|
|
191
|
+
proration) and `invoice_now` (a final invoice; a net credit lands on the customer balance), in the
|
|
192
|
+
query string (as stripe-node sends them) or the body. A $0 invoice is `paid` on
|
|
171
193
|
finalize; a customer credit balance is applied at finalize; `void` works only on open invoices
|
|
172
194
|
(`You can only pass in open invoices. This invoice isn't open.`).
|
|
173
195
|
|
|
@@ -182,9 +204,27 @@ finalize; a customer credit balance is applied at finalize; `void` works only on
|
|
|
182
204
|
succeeding brands, declines (generic, insufficient funds, expired, attach-then-fail), 3D Secure
|
|
183
205
|
and dispute. Each button fills every field, and with “Pay immediately after filling” ticked it
|
|
184
206
|
submits the form too. Pay completes the session (creating the customer, the PaymentIntent with
|
|
185
|
-
`payment_intent_data.metadata`, the Subscription with `subscription_data
|
|
186
|
-
SetupIntent), emits `checkout.session.completed`
|
|
187
|
-
`{CHECKOUT_SESSION_ID}` substituted raw and `%7B…%7D`-encoded;
|
|
207
|
+
`payment_intent_data.metadata`, the Subscription with `subscription_data` — metadata,
|
|
208
|
+
description, trial and `trial_settings` — or the SetupIntent), emits `checkout.session.completed`
|
|
209
|
+
and 302s to `success_url` with `{CHECKOUT_SESSION_ID}` substituted raw and `%7B…%7D`-encoded;
|
|
210
|
+
Cancel 302s to `cancel_url`. The page authenticates 3-D Secure cards in place in every mode, and
|
|
211
|
+
checks what was typed the way the Payment Element does (expiry in the past, incomplete CVC,
|
|
212
|
+
invalid email); a decline leaves the session open (in subscription and setup mode it creates no
|
|
213
|
+
customer; in payment mode it records the failed PaymentIntent and charge, as Stripe does). The email and name
|
|
214
|
+
typed become the new customer's and `customer_details`; `customer_email` pre-fills the email.
|
|
215
|
+
- Subscription mode bills one-time lines once, on the first invoice, and `price_data` lines (with
|
|
216
|
+
or without `recurring`) create inline prices (and `product_data` products), so every line — and
|
|
217
|
+
the subscription — references a real price id. With `payment_method_collection=if_required` a
|
|
218
|
+
free trial shows no card fields (`stripe-mock-no-card`) and starts without a payment method.
|
|
219
|
+
- `allow_promotion_codes=true` adds a promotion code field to the summary
|
|
220
|
+
(`stripe-mock-promotion-code`, `-apply`, `-remove`; a refusal shows
|
|
221
|
+
`stripe-mock-promotion-error` with Checkout's "This code is invalid."). Codes are
|
|
222
|
+
case-insensitive and checked for being active, unexpired, under their limits, for this customer,
|
|
223
|
+
first-time and minimum-amount restrictions, and applicable products; the session re-prices.
|
|
224
|
+
- Creation is validated like Stripe: `customer` with `customer_email`, a recurring price in
|
|
225
|
+
`payment` mode, `allow_promotion_codes` with `discounts`, `subscription_data` outside
|
|
226
|
+
subscription mode, `payment_intent_data` outside payment mode, `customer_creation` outside
|
|
227
|
+
payment mode, inactive prices, mixed currencies, quantities below 1, `trial_end` under 48 h.
|
|
188
228
|
- `POST /__admin/checkout/sessions/:id/complete {"card": "4242…"}` does the same without a browser;
|
|
189
229
|
`…/expire` and `…/async_payment_succeeded` too.
|
|
190
230
|
- `GET /v3` — the Stripe.js stand-in: `Stripe(pk)`, `elements()` → `create("payment"|"card")`,
|
|
@@ -196,6 +236,38 @@ finalize; a customer credit balance is applied at finalize; `void` works only on
|
|
|
196
236
|
A publishable key may only confirm or read an intent whose `client_secret` it presents, and create
|
|
197
237
|
payment methods; anything else is Stripe's 401.
|
|
198
238
|
|
|
239
|
+
### Customer portal
|
|
240
|
+
|
|
241
|
+
`POST /v1/billing_portal/sessions` returns a `url` on the mock (`/p/session/:id`, in place of
|
|
242
|
+
billing.stripe.com) where the customer manages their billing, as the session's configuration allows:
|
|
243
|
+
|
|
244
|
+
- **Configurations**: `POST|GET /v1/billing_portal/configurations[/:id]`
|
|
245
|
+
(`billing_portal.configuration.created|updated`). Each account has a default configuration, as if
|
|
246
|
+
saved in the dashboard: every feature on, cancellation at period end with a reason, plan and
|
|
247
|
+
quantity changes with `create_prorations` across every active product with active recurring
|
|
248
|
+
prices. API-created configurations start with every feature off; `products[].prices` must be
|
|
249
|
+
recurring prices of that product; the default cannot be deactivated.
|
|
250
|
+
- **Sessions** check the customer, an active configuration, and `flow_data`: the subscription must
|
|
251
|
+
be the customer's and manageable, the feature enabled, `subscription_update_confirm` prices
|
|
252
|
+
offered, a retention coupon real (`billing_portal.session.created`). `return_url` falls back to
|
|
253
|
+
`default_return_url`.
|
|
254
|
+
- **The page** (`data-testid="stripe-mock-portal"`, `data-view`): current subscriptions with their
|
|
255
|
+
status (trial end, cancels on, past due, paused, payments paused); **Cancel plan** (reason and
|
|
256
|
+
comment → `cancellation_details`; at period end, or immediately with the configured proration);
|
|
257
|
+
**Renew plan** for a subscription set to cancel; **Update plan** (price and quantity within the
|
|
258
|
+
configured bounds, a confirmation step showing the proration, then the configured
|
|
259
|
+
`proration_behavior`; `trial_update_behavior=end_trial` ends a trial); payment methods (**Add**
|
|
260
|
+
through a SetupIntent, which becomes the customer's and each subscription's default; **Make
|
|
261
|
+
default**; **Delete** a non-default card); billing information (the configured `allowed_updates`,
|
|
262
|
+
`customer.updated` with `previous_attributes`); invoice history with **Pay** for open invoices.
|
|
263
|
+
- **Deep links** open on the flow's page — cancel (with a retention offer to accept), update, update
|
|
264
|
+
confirmation, payment method, billing details — and on completion honour `after_completion`:
|
|
265
|
+
`redirect` (302), `hosted_confirmation` (its `custom_message`) or the homepage. Every action
|
|
266
|
+
runs the same code as the API call it stands for, so the webhooks match.
|
|
267
|
+
|
|
268
|
+
Like Stripe's portal, it has no pause control: pausing is `pause_collection` or
|
|
269
|
+
`trial_settings` + `/resume` through the API, and the portal shows a paused subscription as such.
|
|
270
|
+
|
|
199
271
|
### Test values
|
|
200
272
|
|
|
201
273
|
- Payment methods: `pm_card_visa`, `pm_card_mastercard`, `pm_card_amex`, `pm_card_discover`,
|
|
@@ -299,6 +371,9 @@ plus `port`, `host`; resolves `{url, port, runtime, close}`), `serveTarget` (the
|
|
|
299
371
|
`unpaid` and dunning emails are not run.
|
|
300
372
|
- **Proration arithmetic** is day-fraction approximate (Stripe prorates to the second);
|
|
301
373
|
`auto_advance` drafts are not finalized an hour later.
|
|
374
|
+
- **Customer portal extras**: the login page (`login_page.url` is not served), `schedule_at_period_end`
|
|
375
|
+
downgrades, `billing_cycle_anchor` resets on plan changes, multi-item subscription updates, locales,
|
|
376
|
+
and payment method configurations. Portal sessions do not expire.
|
|
302
377
|
- **Webhook endpoint `api_version`**: payloads render at the account's version, not per endpoint.
|
|
303
378
|
- **Live keys** (`sk_live_…`) are refused with Stripe's 401: the mock is test mode only.
|
|
304
379
|
- Operations marked unsupported in SUPPORT.md (charge create/update, checkout session update,
|