@volter/twin-stripe 2.0.21 → 2.0.23

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.
@@ -1,4 +1,4 @@
1
- import type { SemanticsContext } from '@volter/world-core';
1
+ import { type SemanticsContext } from '@volter/world-core';
2
2
  import { type Row } from './shared.js';
3
3
  /** A customer's now: its test clock's frozen time when it is on one (docs.stripe.com/billing/testing/test-clocks), the
4
4
  * World's otherwise. */
@@ -1,3 +1,29 @@
1
+ // A subscription's invoices over time: the first one Checkout pays, and each renewal. Time's moves are made
2
+ // as the twin's trial end already made them: caught up to the customer's clock (the World's, or its test
3
+ // clock) whenever the twin is asked anything (../stripe-server.ts), each checked against the machines in
4
+ // ../manifest.ts.
5
+ //
6
+ // - Checkout's subscription session pays the first invoice with the card the customer entered, which becomes the
7
+ // subscription's default payment method (docs.stripe.com/payments/checkout/how-checkout-works); nothing is due
8
+ // on a trial's.
9
+ // - At each period's end Stripe drafts a `subscription_cycle` invoice for the next period and advances the
10
+ // period; a trial's end is the first such renewal, and the subscription is active from then
11
+ // (docs.stripe.com/billing/subscriptions/overview#how-payments-work-subscriptions,
12
+ // docs.stripe.com/billing/subscriptions/trials).
13
+ // - About an hour after, Stripe finalizes the draft and charges the subscription's default payment method,
14
+ // else the customer's (docs.stripe.com/invoicing/integration/workflow-transitions#finalized,
15
+ // docs.stripe.com/api/subscriptions/object#subscription_object-default_payment_method).
16
+ // - A declined renewal leaves the invoice open with its attempt counted and the subscription past_due
17
+ // (docs.stripe.com/billing/subscriptions/overview#payment-status); paying the invoice later, through the API
18
+ // (docs.stripe.com/api/invoices/pay) or on the customer portal (docs.stripe.com/customer-management), makes it
19
+ // active again.
20
+ //
21
+ // Where the documentation stops and the twin decides: Stripe retries a failed renewal on its Smart Retries
22
+ // schedule and may then cancel or mark the subscription unpaid; the twin makes no retry (next_payment_attempt
23
+ // is null) and leaves it past_due until the invoice is paid. A paused subscription (pause_collection) is not renewed.
24
+ // - One set to cancel at its period's end is canceled when the period ends, with no renewal
25
+ // (docs.stripe.com/billing/subscriptions/cancel#cancel-at-end-of-cycle).
26
+ import { runAsVendorMove } from '@volter/world-core';
1
27
  import { addBillingInterval, applyCouponDiscount, declineFor, mintClientSecret, priceAmount, resolveSubscriptionBillingInterval } from "../stripe-twin.js";
2
28
  import { finalizedFields } from "./invoices.js";
3
29
  import { mintCharge } from "./payment-intents.js";
@@ -195,6 +221,10 @@ export async function payOpenInvoice(ctx, invoiceId, paymentMethod) {
195
221
  * moment it was due: a period that ended renews (a trial's end among them) or, set to cancel, cancels; a renewal
196
222
  * drafted an hour or more ago is finalized and charged. */
197
223
  export async function advanceBilling(ctx, only) {
224
+ // the billing clock is the vendor's own move, wherever a handler reaches it: never the request's caller's
225
+ return runAsVendorMove(() => advanceBillingDue(ctx, only));
226
+ }
227
+ async function advanceBillingDue(ctx, only) {
198
228
  const at = at_(ctx);
199
229
  for (let guard = 0; guard < 10_000 && (await billingStep(ctx, at, only)); guard++)
200
230
  ;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/twin-stripe",
3
- "version": "2.0.21",
3
+ "version": "2.0.23",
4
4
  "description": "Local Stripe twin — a faithful, stateful local Stripe API your real `stripe` SDK talks to unmodified. Mirror, simulate, and fork. Built on @volter/world-core.",
5
5
  "keywords": [
6
6
  "twin",
@@ -56,15 +56,15 @@
56
56
  "postpack": "node ../../../scripts/publish/prepare-publish.mjs postpack"
57
57
  },
58
58
  "dependencies": {
59
- "@volter/world-ui": "0.1.20",
59
+ "@volter/world-ui": "0.1.22",
60
60
  "react": "^19.2.7",
61
61
  "react-dom": "^19.2.7"
62
62
  },
63
63
  "peerDependencies": {
64
- "@volter/world-core": "2.0.20"
64
+ "@volter/world-core": "2.0.22"
65
65
  },
66
66
  "devDependencies": {
67
- "@volter/world-core": "2.0.20",
67
+ "@volter/world-core": "2.0.22",
68
68
  "@volter/world-tooling": "0.1.0",
69
69
  "@types/bun": "^1.2.20",
70
70
  "@types/node": "^24.0.0",
@@ -23,7 +23,7 @@
23
23
  // is null) and leaves it past_due until the invoice is paid. A paused subscription (pause_collection) is not renewed.
24
24
  // - One set to cancel at its period's end is canceled when the period ends, with no renewal
25
25
  // (docs.stripe.com/billing/subscriptions/cancel#cancel-at-end-of-cycle).
26
- import type { SemanticsContext } from '@volter/world-core';
26
+ import { runAsVendorMove, type SemanticsContext } from '@volter/world-core';
27
27
  import { addBillingInterval, applyCouponDiscount, declineFor, mintClientSecret, priceAmount, resolveSubscriptionBillingInterval } from '../stripe-twin.ts';
28
28
  import { finalizedFields } from './invoices.ts';
29
29
  import { mintCharge } from './payment-intents.ts';
@@ -227,6 +227,11 @@ export async function payOpenInvoice(ctx: SemanticsContext, invoiceId: string, p
227
227
  * moment it was due: a period that ended renews (a trial's end among them) or, set to cancel, cancels; a renewal
228
228
  * drafted an hour or more ago is finalized and charged. */
229
229
  export async function advanceBilling(ctx: SemanticsContext, only?: (sub: Row) => boolean): Promise<void> {
230
+ // the billing clock is the vendor's own move, wherever a handler reaches it: never the request's caller's
231
+ return runAsVendorMove(() => advanceBillingDue(ctx, only));
232
+ }
233
+
234
+ async function advanceBillingDue(ctx: SemanticsContext, only?: (sub: Row) => boolean): Promise<void> {
230
235
  const at = at_(ctx);
231
236
  for (let guard = 0; guard < 10_000 && (await billingStep(ctx, at, only)); guard++);
232
237
  }