@crvouga/mockingbird-service-stripe 1.3.0 → 1.4.0

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 CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog — @crvouga/mockingbird-service-stripe
2
2
 
3
+ ## 1.4.0 (2026-09-29)
4
+
5
+ ### Features
6
+
7
+ - retry failed subscription renewals and apply the configured outcome ([c737a68](https://github.com/crvouga/mockingbird/commit/c737a6873a5e9b984b382b9f00c0b01f1712116c))
8
+
9
+ ### Fixes and improvements
10
+
11
+ - validate list elements ahead of a schema issue in order ([5a09c88](https://github.com/crvouga/mockingbird/commit/5a09c880a432b4e13e2ef5b519a8a76962d372da))
12
+ - answer a missing SetupIntent with Stripe's 404 resource_missing ([9e8368f](https://github.com/crvouga/mockingbird/commit/9e8368ff2d01303cf5ff5410afc3387c75163ca6))
13
+ - leave the portal configuration list out of live parity walks ([9adfc89](https://github.com/crvouga/mockingbird/commit/9adfc89ed7dbe88d798724deaf9d9ccddea60800))
14
+ - use --all-snapshot with Stripe CLI 1.52 in the webhook oracle ([d95804c](https://github.com/crvouga/mockingbird/commit/d95804cbdf98bcbd2199865dcdd13157d44b1d12))
15
+ - pass --events to stripe listen so Stripe CLI 1.52 starts ([b31d3da](https://github.com/crvouga/mockingbird/commit/b31d3da44396f79630f21f732fd4fb2cc1d3fcc9))
16
+ - report why stripe listen exited in the webhook oracle ([19461e4](https://github.com/crvouga/mockingbird/commit/19461e478a3d0489aac646820ecaad12b2d6ce2a))
17
+
18
+ ## 1.3.1 (2026-09-29)
19
+
20
+ ### Fixes and improvements
21
+
22
+ - say 'No such configuration' for a missing portal configuration ([6fad72a](https://github.com/crvouga/mockingbird/commit/6fad72a676b359672653508d9dd654e656ac6aa1))
23
+ - upper-case only two-letter country codes like the live API ([4ee5121](https://github.com/crvouga/mockingbird/commit/4ee5121cf2254fd75eef34bbeefee496cd16964a))
24
+ - leave portal configuration listing out of live parity walks ([20d715a](https://github.com/crvouga/mockingbird/commit/20d715a548e60dd933fab52e7ba661870607049e))
25
+ - keep the shipping address country verbatim like the live API ([052e285](https://github.com/crvouga/mockingbird/commit/052e285a0ae9bcc26050f1c50ed635085438c3d1))
26
+ - use --all-snapshot so the Stripe CLI webhook oracle starts ([09809bf](https://github.com/crvouga/mockingbird/commit/09809bfcd106cc4570d74e781ab0619efb191e05))
27
+ - listen for all events so newer Stripe CLIs start the webhook oracle ([278e364](https://github.com/crvouga/mockingbird/commit/278e364c77263d8cff65fb6d03fde9e89a1010ca))
28
+ - surface redacted stripe listen output when the webhook oracle fails to start ([dd179bb](https://github.com/crvouga/mockingbird/commit/dd179bbdf7a0d38ba097f79041ec818eaf69dfc5))
29
+ - sign and retry webhooks on the injected clock ([1091bde](https://github.com/crvouga/mockingbird/commit/1091bde0df69d9cc3eba2062d8523755e6caca14))
30
+
3
31
  ## 1.3.0 (2026-09-29)
4
32
 
5
33
  ### Features
package/README.md CHANGED
@@ -86,7 +86,7 @@ the mock's hosted page.
86
86
 
87
87
  State is partitioned by **account**, and the account is chosen by API key:
88
88
 
89
- - `PUT /__admin/accounts {"accounts": [{id, keys, apiVersion?, webhookSecrets?, corpus?, displayName?}]}`
89
+ - `PUT /__admin/accounts {"accounts": [{id, keys, apiVersion?, webhookSecrets?, corpus?, displayName?, billing?}]}`
90
90
  (also `createRuntime({accounts})` / `serve --accounts <json|file>`). Every key listed on an
91
91
  account acts as it. Any other test key is an account of its own (`accountOfKey(key)`), so an MSO
92
92
  key reading a PC object gets Stripe's exact `404 resource_missing` (`No such payment_intent: 'pi_…'`).
@@ -94,6 +94,8 @@ State is partitioned by **account**, and the account is chosen by API key:
94
94
  its webhook payloads render at (default `2024-06-20`, what every backend receiver of ours pins).
95
95
  - `webhookSecrets: {"<receiver url>": "whsec_…"}` delivers every event of the account there.
96
96
  - `corpus: true` seeds the recorded catalog (below).
97
+ - `billing: {retries: {scheduleDays?, afterAllFail?}}` sets the account's failed-renewal retries
98
+ (see Lifecycles and the clock); unset fields use `createRuntime({lifecycle: {paymentRetries}})`.
97
99
 
98
100
  **Namespaces** isolate parallel workers; each namespace has its own copy of every account. Carriers:
99
101
  the `x-mockingbird-namespace` header, the `/ns/<namespace>/…` path prefix, or **by API key**:
@@ -118,7 +120,7 @@ holds, ancestors included); Stripe's own rules are enforced: a non-expandable fi
118
120
 
119
121
  Every state change records an event in the account's log (`GET /v1/events`, filterable by
120
122
  `types[]` and `created`) and publishes it through the shared webhook hub, signed
121
- `Stripe-Signature: t=<wall-clock unix>,v1=<hex HMAC-SHA256(secret, "t.body")>` over the exact bytes —
123
+ `Stripe-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "t.body")>` over the exact bytes —
122
124
  `stripe.webhooks.constructEvent` verifies them.
123
125
 
124
126
  - Endpoints: `PUT /__admin/webhook-endpoints [{account, url, secret, enabledEvents: ["*"|…]}]`
@@ -126,6 +128,11 @@ Every state change records an event in the account's log (`GET /v1/events`, filt
126
128
  `serve --webhook-url/--webhook-secret`, accounts' `webhookSecrets`, and endpoints created through
127
129
  `POST /v1/webhook_endpoints` (signed with the `whsec_` returned at creation). One event fans out to
128
130
  every matching endpoint, as on Stripe. Retries follow the hub's schedule.
131
+ - `t` is the wall clock, unless the runtime's `clock` is injected (`createRuntime({ clock })`):
132
+ then `t`, retries and the attempt timeout run on that clock, so a virtual-time suite verifies with
133
+ `constructEventAsync(body, sig, secret, tolerance, undefined, clock.now())` and sees a retry when it
134
+ advances the clock (`runtime.clock.advance`, or `runtime.tick()` after moving its own clock).
135
+ `webhooks: { now, schedule, cancel, id }` override the hub's sources directly.
129
136
  - `GET /__admin/webhooks`, `/webhooks/events`, `POST /__admin/webhooks/:id/replay`, `/webhooks/flush`.
130
137
  - Delivery faults: presets `webhook_duplicate` (same event id twice — our receiver's in-flight
131
138
  dedupe answers 500), `webhook_reorder` (the next two swapped), `webhook_drop` (never delivered,
@@ -166,6 +173,18 @@ clock-driven lifecycle, so their webhooks fire at once; the served mock also tic
166
173
  voided (`void`), marked `uncollectible` (`mark_uncollectible`, `invoice.marked_uncollectible`) or
167
174
  left as drafts (`keep_as_draft`), never charged; the status holds. `pause_collection[resumes_at]`
168
175
  lifts the pause on its own before the renewal it precedes; `pause_collection=""` lifts it now.
176
+ - **Payment retries**: a declined renewal (`charge_automatically`) leaves the invoice `open` with
177
+ `attempt_count: 1` and `next_payment_attempt`, and the subscription `past_due`. Each retry
178
+ `scheduleDays` after the first failure (default `[3, 5, 7]`, Stripe's classic "3 retries over
179
+ 1 week") charges again when the clock reaches it: a decline bumps `attempt_count`, emits
180
+ `invoice.payment_failed` and moves `next_payment_attempt`; a working default payment method pays
181
+ the invoice (`invoice.paid`) and the subscription is `active` again
182
+ (`customer.subscription.updated`). After the last retry `next_payment_attempt` is `null` and
183
+ `afterAllFail` applies: `cancel` (the default: `canceled`, `cancellation_details.reason:
184
+ payment_failed`, `customer.subscription.deleted`), `unpaid` (`customer.subscription.updated`) or
185
+ `past_due` (nothing changes). An empty schedule applies it at the first failure. The schedule is
186
+ fixed, not Stripe's ML-timed Smart Retries; set it per account (`billing.retries`, also
187
+ `PUT /__admin/accounts`) or for the runtime (`lifecycle.paymentRetries`). Dunning emails are not sent.
169
188
  - `incomplete` subscriptions become `incomplete_expired` after 23 h (their invoice is voided).
170
189
  - Checkout Sessions expire at `expires_at` (`checkout.session.expired`).
171
190
  - Schedule phases advance; the last one releases or cancels per `end_behavior`.
@@ -362,8 +381,6 @@ plus `port`, `host`; resolves `{url, port, runtime, close}`), `serveTarget` (the
362
381
  PaymentSheet cannot be redirected (member-app native keeps its fake provider).
363
382
  - **Connect** (`Stripe-Account`, application fees, transfers), tax, shipping, Radar, mandates,
364
383
  meters, quotes, credit notes, payouts, and non-card payment methods (bank debits, wallets).
365
- - **Smart retries / dunning**: a failed renewal goes `past_due` once; later automatic retries,
366
- `unpaid` and dunning emails are not run.
367
384
  - **Proration arithmetic** is day-fraction approximate (Stripe prorates to the second);
368
385
  `auto_advance` drafts are not finalized an hour later.
369
386
  - **Customer portal extras**: the login page (`login_page.url` is not served), `schedule_at_period_end`
@@ -1559,7 +1559,7 @@ var restoreNamespace = (sqlite, namespace, snapshot) => {
1559
1559
  };
1560
1560
 
1561
1561
  // ../core/dist/version.js
1562
- var PACKAGE_VERSION = true ? "1.3.0" : UNRELEASED_VERSION;
1562
+ var PACKAGE_VERSION = true ? "1.4.0" : UNRELEASED_VERSION;
1563
1563
 
1564
1564
  // ../core/dist/signing.js
1565
1565
  var encoder = new TextEncoder();
@@ -2583,6 +2583,27 @@ var eraOf = (version) => {
2583
2583
  };
2584
2584
 
2585
2585
  // src/accounts.ts
2586
+ var DEFAULT_PAYMENT_RETRIES = {
2587
+ scheduleDays: [3, 5, 7],
2588
+ afterAllFail: "cancel"
2589
+ };
2590
+ var RETRY_OUTCOMES = ["cancel", "unpaid", "past_due"];
2591
+ var validateRetries = (id, value) => {
2592
+ const raw = value?.retries;
2593
+ if (typeof value !== "object" || value === null || typeof raw !== "object" || raw === null)
2594
+ return `${id}: billing must be {"retries": {"scheduleDays": [3, 5, 7], "afterAllFail": "cancel"}}`;
2595
+ const { scheduleDays, afterAllFail } = raw;
2596
+ if (scheduleDays !== void 0 && (!Array.isArray(scheduleDays) || scheduleDays.some(
2597
+ (day, index) => typeof day !== "number" || !Number.isFinite(day) || day <= 0 || index > 0 && day <= scheduleDays[index - 1]
2598
+ )))
2599
+ return `${id}: billing.retries.scheduleDays must be increasing positive numbers of days`;
2600
+ if (afterAllFail !== void 0 && !RETRY_OUTCOMES.includes(afterAllFail))
2601
+ return `${id}: billing.retries.afterAllFail must be one of ${RETRY_OUTCOMES.join(", ")}`;
2602
+ return {
2603
+ ...scheduleDays !== void 0 ? { scheduleDays } : {},
2604
+ ...afterAllFail !== void 0 ? { afterAllFail } : {}
2605
+ };
2606
+ };
2586
2607
  var DEFAULT_WEBHOOK_API_VERSION = "2024-06-20";
2587
2608
  var KEY = /^(?:sk|rk|pk)_(?:test|live)_[A-Za-z0-9_]+$/;
2588
2609
  var validateAccounts = (value) => {
@@ -2607,8 +2628,11 @@ var validateAccounts = (value) => {
2607
2628
  const secrets = raw.webhookSecrets;
2608
2629
  if (secrets !== void 0 && (typeof secrets !== "object" || secrets === null || Object.values(secrets).some((secret) => typeof secret !== "string")))
2609
2630
  return `${raw.id}: webhookSecrets must map receiver URLs to whsec_ secrets`;
2631
+ const retries = raw.billing === void 0 ? void 0 : validateRetries(raw.id, raw.billing);
2632
+ if (typeof retries === "string") return retries;
2610
2633
  accounts.push({
2611
2634
  id: raw.id,
2635
+ ...retries ? { billing: { retries } } : {},
2612
2636
  keys: raw.keys,
2613
2637
  ...typeof raw.apiVersion === "string" ? { apiVersion: raw.apiVersion } : {},
2614
2638
  ...secrets ? { webhookSecrets: secrets } : {},
@@ -4875,11 +4899,19 @@ var activateAfterPayment = (scope, subscriptionId, intent) => {
4875
4899
  };
4876
4900
  var markPaymentFailed = (scope, invoice) => {
4877
4901
  const now = customerNow(scope, invoice.customer);
4902
+ const attempts = invoice.attempt_count + 1;
4903
+ let next = now + 3 * DAY;
4904
+ if (invoice.subscription !== null) {
4905
+ const days = scope.services.paymentRetries(scope.account.account).scheduleDays;
4906
+ const first = attempts === 1 || invoice.next_payment_attempt === null ? now : invoice.next_payment_attempt - (days[attempts - 2] ?? 0) * DAY;
4907
+ const day = days[attempts - 1];
4908
+ next = day === void 0 ? null : first + day * DAY;
4909
+ }
4878
4910
  const failed = {
4879
4911
  ...invoice,
4880
- attempt_count: invoice.attempt_count + 1,
4912
+ attempt_count: attempts,
4881
4913
  attempted: true,
4882
- next_payment_attempt: now + 3 * DAY
4914
+ next_payment_attempt: next
4883
4915
  };
4884
4916
  scope.account.invoices.update(failed.id, failed);
4885
4917
  scope.emit("invoice.payment_failed", renderInvoice(failed, scope.account));
@@ -5315,12 +5347,15 @@ var cycleSubscription = (scope, current) => {
5315
5347
  });
5316
5348
  claimInvoiceItems(scope, invoice);
5317
5349
  let status = moved.status;
5350
+ let exhausted = false;
5318
5351
  if (!keepDraft) {
5319
5352
  const finalized = finalizeInvoice(scope, invoice);
5320
5353
  const collected = collectCycleInvoice(scope, current, finalized);
5321
5354
  invoice = collected.invoice;
5322
- if (finalized.status === "open" && current.pause_collection === null && finalized.collection_method === "charge_automatically")
5355
+ if (finalized.status === "open" && current.pause_collection === null && finalized.collection_method === "charge_automatically") {
5323
5356
  status = collected.failed ? "past_due" : "active";
5357
+ exhausted = collected.failed && invoice.attempt_count > 0 && invoice.next_payment_attempt === null;
5358
+ }
5324
5359
  }
5325
5360
  const latest = scope.account.subscriptions.get(current.id) ?? moved;
5326
5361
  const next = {
@@ -5334,6 +5369,33 @@ var cycleSubscription = (scope, current) => {
5334
5369
  const after = renderSubscription(next, scope.account);
5335
5370
  const changed = changedFields(before, after);
5336
5371
  scope.emit("customer.subscription.updated", after, changed);
5372
+ if (exhausted) applyFailedPaymentOutcome(scope, next);
5373
+ };
5374
+ var applyFailedPaymentOutcome = (scope, current) => {
5375
+ const outcome = scope.services.paymentRetries(scope.account.account).afterAllFail;
5376
+ if (outcome === "cancel") cancelSubscription(scope, current, "payment_failed");
5377
+ else if (outcome === "unpaid") saveSubscription(scope, current, { ...current, status: "unpaid" });
5378
+ };
5379
+ var retryDueInvoices = (scope, subscription, now) => {
5380
+ for (let guard2 = 0; guard2 < 50; guard2 += 1) {
5381
+ const due = scope.account.invoices.list({
5382
+ order: "oldest",
5383
+ where: (invoice) => invoice.subscription === subscription.id && invoice.status === "open" && invoice.collection_method === "charge_automatically" && invoice.next_payment_attempt !== null && invoice.next_payment_attempt <= now
5384
+ })[0]?.value;
5385
+ if (due === void 0) return;
5386
+ let settled;
5387
+ try {
5388
+ settled = payInvoice(scope, due, { offSession: true });
5389
+ } catch (error) {
5390
+ if (!(error instanceof StripeError)) throw error;
5391
+ settled = scope.account.invoices.get(due.id) ?? due;
5392
+ }
5393
+ const current = scope.account.subscriptions.get(subscription.id);
5394
+ if (settled.status !== "open" || current === void 0) return;
5395
+ if (settled.next_payment_attempt === due.next_payment_attempt) return;
5396
+ if (settled.next_payment_attempt === null) applyFailedPaymentOutcome(scope, current);
5397
+ if (settled.next_payment_attempt === null || current.status !== "past_due") return;
5398
+ }
5337
5399
  };
5338
5400
  var periodAmount = (scope, subscription, at) => {
5339
5401
  const lines = subscriptionItems(scope, subscription).map((item) => {
@@ -5744,7 +5806,8 @@ var releaseSchedule = (scope, schedule, now) => {
5744
5806
  var DEFAULT_LIFECYCLE = {
5745
5807
  upcomingInvoiceDays: 3,
5746
5808
  trialWillEndDays: 3,
5747
- incompleteExpiryHours: 23
5809
+ incompleteExpiryHours: 23,
5810
+ paymentRetries: DEFAULT_PAYMENT_RETRIES
5748
5811
  };
5749
5812
  var runLifecycle = (scope, settings, onlyCustomers) => {
5750
5813
  const inScope = (customer) => onlyCustomers === void 0 ? customer === null || !findCustomer(scope, customer)?.test_clock : customer !== null && onlyCustomers.has(customer);
@@ -5778,6 +5841,10 @@ var runLifecycle = (scope, settings, onlyCustomers) => {
5778
5841
  );
5779
5842
  }
5780
5843
  for (let guard2 = 0; guard2 < 120; guard2 += 1) {
5844
+ if (subscription.status === "past_due") {
5845
+ retryDueInvoices(scope, subscription, now2);
5846
+ subscription = scope.account.subscriptions.get(subscription.id) ?? subscription;
5847
+ }
5781
5848
  if (!["active", "trialing", "past_due"].includes(subscription.status)) break;
5782
5849
  const cancelAt = subscription.cancel_at;
5783
5850
  if (cancelAt !== null && cancelAt <= now2) {
@@ -6461,7 +6528,13 @@ var parseParams = (schema, raw, options = {}) => {
6461
6528
  if (error) throw error;
6462
6529
  }
6463
6530
  const issue = issues[0];
6464
- if (issue) throw issueToError(issue);
6531
+ if (issue) {
6532
+ const failing = /^[^[]+\[(\d+)\]/.exec(issue.path)?.[1];
6533
+ const list = params[key];
6534
+ if (failing !== void 0 && Number(failing) > 0 && Array.isArray(list))
6535
+ options.validate?.[key]?.({ ...params, [key]: list.slice(0, Number(failing)) });
6536
+ throw issueToError(issue);
6537
+ }
6465
6538
  options.validate?.[key]?.(params);
6466
6539
  }
6467
6540
  const stray = parsed.issues[0];
@@ -7184,7 +7257,7 @@ var loginPageOf = (scope, raw, base2, id) => {
7184
7257
  var requireConfiguration = (scope, id, param = "configuration") => {
7185
7258
  if (!scope.account.portalConfigurations.get(id)) ensureDefaultConfiguration(scope);
7186
7259
  const config = scope.account.portalConfigurations.get(id);
7187
- if (!config) throw resourceMissing("billing portal configuration", id, param);
7260
+ if (!config) throw resourceMissing("configuration", id, param);
7188
7261
  return config;
7189
7262
  };
7190
7263
  var flowSubscription = (scope, customer, id, param) => {
@@ -7314,14 +7387,13 @@ var billingPortalHandlers = (services) => ({
7314
7387
  GetBillingPortalConfigurations: async (context) => {
7315
7388
  const scope = requestScope(services, context);
7316
7389
  const params = queryParams(context);
7317
- ensureDefaultConfiguration(scope);
7318
7390
  const active = booleanOf(params.active);
7319
7391
  const isDefault = booleanOf(params.is_default);
7320
7392
  return jsonRes(
7321
7393
  200,
7322
7394
  await paginate(scope.account.portalConfigurations, params, {
7323
7395
  url: "/v1/billing_portal/configurations",
7324
- kind: "billing portal configuration",
7396
+ kind: "configuration",
7325
7397
  where: (config) => (active === void 0 || config.active === active) && (isDefault === void 0 || config.is_default === isDefault),
7326
7398
  render: (config) => renderConfiguration(scope, config)
7327
7399
  })
@@ -9852,7 +9924,7 @@ var paymentIntentHandlers = (services) => {
9852
9924
  // src/setup-intents.ts
9853
9925
  var requireSetupIntent = (scope, id) => {
9854
9926
  const intent = scope.account.setupIntents.get(id);
9855
- if (!intent) throw invalidRequest(`No such setup_intent: '${id}'`, "intent", "resource_missing");
9927
+ if (!intent) throw resourceMissing("setupintent", id, "intent");
9856
9928
  return intent;
9857
9929
  };
9858
9930
  var confirmSetup = (scope, intent, method, autoAuthenticate = false) => {
@@ -10891,7 +10963,7 @@ var toAddress = (raw) => {
10891
10963
  const out = {};
10892
10964
  for (const key of ADDRESS_KEYS) {
10893
10965
  const value = input[key];
10894
- out[key] = value === void 0 ? null : key === "country" ? value.replace(/[a-z]/g, (letter) => letter.toUpperCase()) : value;
10966
+ out[key] = value === void 0 ? null : key === "country" && /^[A-Za-z]{2}$/.test(value) ? value.toUpperCase() : value;
10895
10967
  }
10896
10968
  return out;
10897
10969
  };
@@ -16428,6 +16500,29 @@ var reshapeQaCommand = (command, _state, rng) => {
16428
16500
  return withBody(command, next);
16429
16501
  };
16430
16502
 
16503
+ // src/webhook-timers.ts
16504
+ var createClockTimers = (clock) => {
16505
+ const timers = /* @__PURE__ */ new Set();
16506
+ return {
16507
+ schedule(callback, delayMs) {
16508
+ const timer = { due: clock.now() + delayMs, callback };
16509
+ timers.add(timer);
16510
+ return timer;
16511
+ },
16512
+ cancel(handle) {
16513
+ timers.delete(handle);
16514
+ },
16515
+ run() {
16516
+ const now = clock.now();
16517
+ const due = [...timers].filter((timer) => timer.due <= now).sort((a, b) => a.due - b.due);
16518
+ for (const timer of due) {
16519
+ if (!timers.delete(timer)) continue;
16520
+ timer.callback();
16521
+ }
16522
+ }
16523
+ };
16524
+ };
16525
+
16431
16526
  // src/runtime.ts
16432
16527
  var RATE_LIMITED = {
16433
16528
  error: {
@@ -16542,10 +16637,18 @@ var guard = (run) => {
16542
16637
  };
16543
16638
  var createRuntime2 = (options = {}) => {
16544
16639
  const accounts = new AccountDirectory(options.accounts ?? []);
16640
+ const baseClock = options.clock ?? createClock();
16641
+ const timers = options.clock ? createClockTimers(options.clock) : void 0;
16545
16642
  const hub = createWebhookHub({
16546
16643
  signer: signers.timestamped("Stripe-Signature"),
16547
16644
  ...options.webhooks?.retryDelaysMs ? { retryDelaysMs: options.webhooks.retryDelaysMs } : {},
16548
16645
  ...options.webhooks?.fetch ? { fetch: options.webhooks.fetch } : {},
16646
+ ...options.clock ? { now: () => baseClock.now() } : {},
16647
+ ...options.webhooks?.now ? { now: options.webhooks.now } : {},
16648
+ ...timers ? { schedule: timers.schedule, cancel: timers.cancel } : {},
16649
+ ...options.webhooks?.schedule ? { schedule: options.webhooks.schedule } : {},
16650
+ ...options.webhooks?.cancel ? { cancel: options.webhooks.cancel } : {},
16651
+ ...options.webhooks?.id ? { id: options.webhooks.id } : {},
16549
16652
  endpoints: options.webhooks?.endpoints ?? []
16550
16653
  });
16551
16654
  let runtimeRef;
@@ -16607,8 +16710,8 @@ var createRuntime2 = (options = {}) => {
16607
16710
  else syncEndpoints(namespace);
16608
16711
  }
16609
16712
  };
16610
- const baseClock = options.clock ?? createClock();
16611
16713
  const tickAll = () => {
16714
+ timers?.run();
16612
16715
  if (!runtimeRef) return;
16613
16716
  for (const name of runtimeRef.namespaces()) runtimeRef.instance(name).tick(true);
16614
16717
  };
@@ -16912,6 +17015,10 @@ var StripeAPI = class {
16912
17015
  publish: options.onWebhook,
16913
17016
  accounts,
16914
17017
  deliveryVersion: (account) => accounts.config(account)?.apiVersion ?? webhookVersion,
17018
+ paymentRetries: (account) => ({
17019
+ ...this.settings.paymentRetries,
17020
+ ...accounts.config(account)?.billing?.retries
17021
+ }),
16915
17022
  pendingWebhooks: options.pendingWebhooks ?? (() => 0),
16916
17023
  namespacePrefix: options.publicNamespace === void 0 || options.publicNamespace === "default" ? "" : `/ns/${encodeURIComponent(options.publicNamespace)}`,
16917
17024
  publicUrl: options.publicUrl,
@@ -17214,4 +17321,4 @@ export {
17214
17321
  createRuntime2 as createRuntime,
17215
17322
  StripeAPI
17216
17323
  };
17217
- //# sourceMappingURL=chunk-2WP3EV2W.js.map
17324
+ //# sourceMappingURL=chunk-P77GCNSW.js.map