@voltro/plugin-billing 0.86.0 → 0.87.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.
@@ -0,0 +1,29 @@
1
+ import { BillingError as e } from "./types.js";
2
+ import { stripeCall as t } from "./stripeClient.js";
3
+ import { stripeAccountIdentity as n } from "./stripeIdentity.js";
4
+ import { Effect as r } from "effect";
5
+ import { canonicalJson as i } from "@voltro/protocol";
6
+ //#region src/stripeVerifiedEvent.ts
7
+ var a = (a, o) => r.gen(function* () {
8
+ let s = yield* r.try({
9
+ try: () => a.webhooks.constructEvent(Buffer.from(o.rawBody), o.signatureHeader, o.secret),
10
+ catch: () => new e({
11
+ source: "stripe.webhooks.constructEvent",
12
+ message: "The webhook signature could not be verified.",
13
+ transient: !1
14
+ })
15
+ }), c = yield* t("stripe.events.retrieve", () => a.events.retrieve(s.id)), l = yield* n(a);
16
+ return yield* r.try({
17
+ try: () => {
18
+ if (c.id !== s.id || c.type !== s.type || c.created !== s.created || c.api_version !== s.api_version || c.livemode !== s.livemode || c.livemode !== l.livemode || (c.account ?? null) !== (s.account ?? null) || c.account !== void 0 && c.account !== null && c.account !== l.providerAccountId || i(JSON.parse(JSON.stringify(c.data))) !== i(JSON.parse(JSON.stringify(s.data)))) throw Error("Event context mismatch");
19
+ return c;
20
+ },
21
+ catch: () => new e({
22
+ source: "stripe.webhooks.account",
23
+ message: "The signed event does not match the configured provider account/mode.",
24
+ transient: !1
25
+ })
26
+ });
27
+ });
28
+ //#endregion
29
+ export { a as verifyStripeBillingEvent };
package/dist/table.js CHANGED
@@ -1,54 +1,77 @@
1
- import { id as e, integer as t, table as n, text as r, timestamp as i } from "@voltro/database";
1
+ import { billingCustomerAccountsTable as e, billingCustomerCustodyTable as t } from "./customerStore.js";
2
+ import { billingInvoiceHistoryPagesTable as n, billingInvoiceHistoryReferencesTable as r, billingInvoiceHistoryWalksTable as i } from "./invoiceHistoryJournal.js";
3
+ import { BILLING_INVOICE_OBSERVATIONS_TABLE as a, billingInvoiceObservationsTable as o, billingRetainedInvoiceObservationsTable as s } from "./invoiceStore.js";
4
+ import { billingInvoiceHistoryWorkTable as c } from "./invoiceHistoryWork.js";
5
+ import { billingInvoiceHistoryDispatchTable as l, billingInvoiceHistoryScanAccountsTable as u } from "./invoiceHistoryProgress.js";
6
+ import { billingUsageEventsTable as d } from "./usageEvents.js";
7
+ import { billingCheckoutOperationsTable as f } from "./checkoutStore.js";
8
+ import { billingCloseOperationsTable as p } from "./closeStore.js";
9
+ import { billingDunningNoticesTable as m } from "./dunning.js";
10
+ import { id as h, integer as g, table as _, text as v, timestamp as y } from "@voltro/database";
2
11
  //#region src/table.ts
3
- var a = "_voltro_billing_customers", o = "_voltro_billing_subscriptions", s = "_voltro_billing_invoices", c = "_voltro_billing_usage", l = n(a, {
4
- id: e({ prefix: "bcust" }),
5
- tenantId: r(),
6
- provider: r(),
7
- providerCustomerId: r(),
8
- createdAt: i(),
9
- updatedAt: i()
10
- }).index("byBillingCustomerTenant", ["tenantId"]).index("byBillingProviderCustomer", ["providerCustomerId"]), u = n(o, {
11
- id: e({ prefix: "bsub" }),
12
- tenantId: r(),
13
- provider: r(),
14
- providerSubscriptionId: r(),
15
- plan: r(),
16
- status: r(),
17
- quantity: t().default(1),
18
- currentPeriodStart: i().nullable(),
19
- currentPeriodEnd: i().nullable(),
20
- cancelAt: i().nullable(),
21
- pastDueSince: i().nullable(),
22
- statusEventAt: i().nullable(),
23
- createdAt: i(),
24
- updatedAt: i()
25
- }).index("byBillingSubscriptionTenant", ["tenantId"]).index("byBillingProviderSubscription", ["providerSubscriptionId"]).index("byBillingSubscriptionStatus", ["status"]), d = n(s, {
26
- id: e({ prefix: "binv" }),
27
- tenantId: r(),
28
- provider: r(),
29
- providerInvoiceId: r(),
30
- amountMinor: t(),
31
- currency: r(),
32
- status: r(),
33
- statusEventAt: i().nullable(),
34
- createdAt: i()
35
- }).index("byBillingInvoiceTenant", ["tenantId"]).index("byBillingProviderInvoice", ["providerInvoiceId"]), f = n(c, {
36
- id: e({ prefix: "buse" }),
37
- tenantId: r().maxLength(191),
38
- entitlementKey: r().maxLength(191),
39
- period: r().maxLength(16),
40
- used: t().default(0),
41
- reportedToProvider: t().default(0),
42
- updatedAt: i()
12
+ var b = "_voltro_billing_customers", x = "_voltro_billing_subscriptions", S = "_voltro_billing_invoices", C = "_voltro_billing_usage", w = _(b, {
13
+ id: h({ prefix: "bcust" }),
14
+ tenantId: v(),
15
+ provider: v(),
16
+ providerCustomerId: v(),
17
+ createdAt: y(),
18
+ updatedAt: y()
19
+ }).index("byBillingCustomerTenant", ["tenantId"]).index("byBillingProviderCustomer", ["providerCustomerId"]), T = _(x, {
20
+ id: h({ prefix: "bsub" }),
21
+ tenantId: v(),
22
+ provider: v(),
23
+ providerSubscriptionId: v(),
24
+ plan: v(),
25
+ status: v(),
26
+ quantity: g().default(1),
27
+ currentPeriodStart: y().nullable(),
28
+ currentPeriodEnd: y().nullable(),
29
+ cancelAt: y().nullable(),
30
+ pastDueSince: y().nullable(),
31
+ statusEventAt: y().nullable(),
32
+ createdAt: y(),
33
+ updatedAt: y()
34
+ }).index("byBillingSubscriptionTenant", ["tenantId"]).index("byBillingProviderSubscription", ["providerSubscriptionId"]).index("byBillingSubscriptionStatus", ["status"]), E = _(S, {
35
+ id: h({ prefix: "binv" }),
36
+ tenantId: v(),
37
+ provider: v(),
38
+ providerInvoiceId: v(),
39
+ amountMinor: g(),
40
+ currency: v(),
41
+ status: v(),
42
+ statusEventAt: y().nullable(),
43
+ createdAt: y()
44
+ }).index("byBillingInvoiceTenant", ["tenantId"]).index("byBillingProviderInvoice", ["providerInvoiceId"]), D = _(C, {
45
+ id: h({ prefix: "buse" }),
46
+ tenantId: v().maxLength(191),
47
+ entitlementKey: v().maxLength(191),
48
+ period: v().maxLength(16),
49
+ used: g().default(0),
50
+ reportedToProvider: g().default(0),
51
+ updatedAt: y()
43
52
  }).unique("byBillingUsageWindow", [
44
53
  "tenantId",
45
54
  "entitlementKey",
46
55
  "period"
47
- ]), p = () => [
48
- l,
49
- u,
56
+ ]), O = () => [
57
+ m,
50
58
  d,
51
- f
59
+ p,
60
+ f,
61
+ w,
62
+ e,
63
+ t,
64
+ i,
65
+ n,
66
+ r,
67
+ c,
68
+ u,
69
+ l,
70
+ T,
71
+ E,
72
+ o,
73
+ s,
74
+ D
52
75
  ];
53
76
  //#endregion
54
- export { a as BILLING_CUSTOMERS_TABLE, s as BILLING_INVOICES_TABLE, o as BILLING_SUBSCRIPTIONS_TABLE, c as BILLING_USAGE_TABLE, l as billingCustomersTable, d as billingInvoicesTable, u as billingSubscriptionsTable, p as billingTables, f as billingUsageTable };
77
+ export { b as BILLING_CUSTOMERS_TABLE, S as BILLING_INVOICES_TABLE, a as BILLING_INVOICE_OBSERVATIONS_TABLE, x as BILLING_SUBSCRIPTIONS_TABLE, C as BILLING_USAGE_TABLE, w as billingCustomersTable, o as billingInvoiceObservationsTable, E as billingInvoicesTable, s as billingRetainedInvoiceObservationsTable, T as billingSubscriptionsTable, O as billingTables, D as billingUsageTable };
package/dist/types.d.ts CHANGED
@@ -30,7 +30,7 @@ declare const BillingError_base: Schema.Class<BillingError, Schema.TaggedStruct<
30
30
  }>, YieldableError>;
31
31
 
32
32
  /**
33
- * The normalized event union a `BillingProvider.normalizeEvent` produces
33
+ * The tenant-resolved event union dispatched after `BillingService.applyEvent` accepts an event
34
34
  * from a verified, decoded provider webhook payload. `BillingService`
35
35
  * applies these to the DB rows — switching provider swaps the adapter;
36
36
  * the service + rows + entitlement engine are unchanged.
@@ -86,17 +86,29 @@ export declare type BillingEvent = {
86
86
  /** The event tag union — handy for `onEvent` keys. */
87
87
  export declare type BillingEventTag = BillingEvent['_tag'];
88
88
 
89
- /**
90
- * Dumb adapter over a billing provider. Holds NO policy — checkout/portal
91
- * URL minting, usage push, and pure payload→event mapping only. The
92
- * `BillingService` wraps it with the entitlement engine, the DB rows, and
93
- * transient retry.
94
- *
95
- * `normalizeEvent` receives a payload whose signature was ALREADY verified
96
- * by @voltro/plugin-webhooks' mounter — this is pure mapping, never a
97
- * second signature check.
98
- */
89
+ export declare type BillingInvoiceTrigger = BillingInvoiceTriggerFields & ({
90
+ readonly _tag: 'invoicePaid';
91
+ } | {
92
+ readonly _tag: 'invoicePaymentFailed';
93
+ });
94
+
95
+ /** Verified provider invoice trigger. Tenant metadata is never ownership evidence. */
96
+ declare interface BillingInvoiceTriggerFields {
97
+ readonly providerCustomerId: string;
98
+ readonly providerInvoiceId: string;
99
+ readonly amountMinor: number;
100
+ readonly currency: string;
101
+ readonly occurredAt: Date | null;
102
+ }
103
+
99
104
  export declare interface BillingProvider {
105
+ /** Required by account-qualified custody consumers; absence is unsupported, not fallback authority. */
106
+ readonly fetchAccountIdentity?: () => Effect.Effect<ProviderAccountIdentity, BillingError>;
107
+ /** Must reject a different account/mode before reading customer data. A 404 is not positive deletion evidence. */
108
+ readonly fetchCustomerSnapshot?: (input: {
109
+ readonly account: ProviderAccountIdentity;
110
+ readonly providerCustomerId: string;
111
+ }) => Effect.Effect<ProviderCustomerSnapshot, BillingError>;
100
112
  readonly name: string;
101
113
  /** Whether the provider supports server-pushed metered usage. Stripe
102
114
  * does (subscription-item usage records); a flat-plan provider may not.
@@ -107,9 +119,13 @@ export declare interface BillingProvider {
107
119
  * meters map events to a customer by id, so an unlinked tenant's usage
108
120
  * stays pending instead of being sent unattributable. */
109
121
  readonly usageRequiresCustomer?: boolean;
110
- readonly createCheckoutSession: (input: CheckoutInput) => Effect.Effect<{
111
- url: string;
112
- }, BillingError>;
122
+ readonly createCheckoutSession: (input: CheckoutInput) => Effect.Effect<CheckoutSessionReceipt, BillingError>;
123
+ /** Read positive evidence for one exact persisted checkout intent. */
124
+ readonly findCheckoutSession: (input: {
125
+ readonly tenantId: string;
126
+ readonly providerCustomerId?: string;
127
+ readonly operationId: string;
128
+ }) => Effect.Effect<CheckoutSessionLookup, BillingError>;
113
129
  readonly createPortalSession: (input: PortalInput) => Effect.Effect<{
114
130
  url: string;
115
131
  }, BillingError>;
@@ -141,6 +157,10 @@ export declare interface BillingProvider {
141
157
  * matters — the subscription's status right now does. Returns null when the
142
158
  * provider does not know the id.
143
159
  */
160
+ /** Direct customer-qualified observation, including terminal subscriptions.
161
+ * Null is absence, never proof of cancellation. Required for retired-customer
162
+ * reconciliation and for subscriptions absent from the live listing. */
163
+ readonly fetchSubscriptionSnapshot?: (providerSubscriptionId: string) => Effect.Effect<ProviderSubscriptionSnapshot | null, BillingError>;
144
164
  readonly fetchSubscription: (providerSubscriptionId: string) => Effect.Effect<ProviderSubscriptionState | null, BillingError>;
145
165
  /**
146
166
  * Every live (not yet ended) subscription at the provider, optionally for
@@ -150,12 +170,19 @@ export declare interface BillingProvider {
150
170
  readonly listSubscriptions: (input: {
151
171
  readonly providerCustomerId?: string;
152
172
  }) => Effect.Effect<ReadonlyArray<ProviderSubscriptionSnapshot>, BillingError>;
153
- /** End a subscription — at period end (reversible) or immediately with a
154
- * final invoice. Returns the provider's state afterwards. */
173
+ /** Quote the provider-clock settlement deadline after final usage dispatch.
174
+ * Persist it before requesting account-close cancellation. */
175
+ readonly prepareSubscriptionClose: (providerSubscriptionId: string) => Effect.Effect<{
176
+ readonly cancelAt: Date;
177
+ }, BillingError>;
155
178
  readonly cancelSubscription: (input: {
156
179
  readonly providerSubscriptionId: string;
157
- readonly mode: CancelMode;
158
- }) => Effect.Effect<ProviderSubscriptionState, BillingError>;
180
+ } & ({
181
+ readonly mode: 'periodEnd';
182
+ } | {
183
+ readonly mode: 'close';
184
+ readonly closeOperation: SubscriptionCloseOperation;
185
+ })) => Effect.Effect<ProviderSubscriptionState, BillingError>;
159
186
  /** Undo a period-end cancellation that has not taken effect yet. */
160
187
  readonly resumeSubscription: (providerSubscriptionId: string) => Effect.Effect<ProviderSubscriptionState, BillingError>;
161
188
  /**
@@ -172,6 +199,7 @@ export declare interface BillingProvider {
172
199
  * can be paid. Returns how many were expired. */
173
200
  readonly expireOpenCheckouts: (input: {
174
201
  readonly providerCustomerId: string;
202
+ readonly exceptOperationId?: string;
175
203
  }) => Effect.Effect<number, BillingError>;
176
204
  /** Add prices the subscription is missing (metered items), without
177
205
  * proration. Prices already present are left alone. */
@@ -185,12 +213,22 @@ export declare interface BillingProvider {
185
213
  * the app keeps its own. Optional: a provider may model no customer email.
186
214
  */
187
215
  readonly customerEmail?: (providerCustomerId: string) => Effect.Effect<string | null, BillingError>;
188
- /** The provider's invoice history. We mirror it for listing but never
189
- * render an invoice ourselves — `hostedUrl` / `pdfUrl` are theirs. */
216
+ /** Bounded recent invoice preview, not complete history. Provider-hosted
217
+ * links remain theirs; use listInvoiceHistory for explicit cursor traversal. */
190
218
  readonly listInvoices: (input: {
191
219
  readonly providerCustomerId: string;
192
220
  readonly limit?: number;
193
221
  }) => Effect.Effect<ReadonlyArray<InvoiceRecord>, BillingError>;
222
+ /** Explicit bounded history pages, including draft and terminal statuses.
223
+ * Cursor exhaustion describes this traversal, not a stable global snapshot.
224
+ * Unsupported adapters must fail; the latest-preview list is no fallback. */
225
+ readonly listInvoiceHistory?: (input: ProviderInvoiceHistoryInput) => Effect.Effect<ProviderInvoiceHistoryPage, BillingError>;
226
+ /** Read one finalized invoice with every line, after confirming its customer.
227
+ * Absence means this provider cannot supply detailed invoice evidence. */
228
+ readonly fetchInvoiceSnapshot?: (input: {
229
+ readonly providerCustomerId: string;
230
+ readonly providerInvoiceId: string;
231
+ }) => Effect.Effect<ProviderInvoiceSnapshot, BillingError>;
194
232
  /**
195
233
  * Verify a webhook signature and decode the payload, using the provider's
196
234
  * OWN verification. Returns `null` when this provider does not do its own
@@ -201,10 +239,10 @@ export declare interface BillingProvider {
201
239
  readonly signatureHeader: string;
202
240
  readonly secret: string;
203
241
  }) => Effect.Effect<unknown, BillingError>;
204
- /** Map a verified, decoded webhook payload to a `BillingEvent`, or
242
+ /** Map a verified, decoded webhook payload to a `BillingProviderEvent`, or
205
243
  * `null` for an event type this provider doesn't model (handler
206
244
  * no-ops, still 200). */
207
- readonly normalizeEvent: (raw: unknown) => Effect.Effect<BillingEvent | null, BillingError>;
245
+ readonly normalizeEvent: (raw: unknown) => Effect.Effect<BillingProviderEvent | null, BillingError>;
208
246
  /**
209
247
  * Who and when a verified payload is about: the provider customer it names
210
248
  * and when the provider created it. The webhook syncs that customer from a
@@ -221,9 +259,17 @@ export declare interface BillingProvider {
221
259
  * it keeps them (Stripe: 30 days) — what an outage of the endpoint left
222
260
  * behind. Optional: a provider without it has no catch-up.
223
261
  */
224
- readonly listUndeliveredEvents?: () => Effect.Effect<ReadonlyArray<UndeliveredEvent>, BillingError>;
262
+ readonly listUndeliveredEvents?: (input: {
263
+ readonly cursor: string | null;
264
+ readonly limit: number;
265
+ }) => Effect.Effect<UndeliveredEventPage, BillingError>;
225
266
  }
226
267
 
268
+ /** Invoice ownership is resolved from native customer links before dispatch. */
269
+ export declare type BillingProviderEvent = Exclude<BillingEvent, {
270
+ readonly _tag: 'invoicePaid' | 'invoicePaymentFailed';
271
+ }> | BillingInvoiceTrigger;
272
+
227
273
  /** The billing service Tag — `const billing = yield* BillingService`. */
228
274
  export declare class BillingService extends BillingService_base {
229
275
  }
@@ -266,6 +312,8 @@ export declare interface BillingServiceShape {
266
312
  readonly consumeEntitlement: (tenantId: string, key: string, cost: number) => Effect.Effect<void, BillingError | EntitlementExceeded>;
267
313
  /** Record metered usage locally; flushed to the provider in batches. */
268
314
  readonly reportUsage: (tenantId: string, key: string, qty: number) => Effect.Effect<void, BillingError>;
315
+ /** Atomically retain an occurrence receipt and increment its original UTC month. */
316
+ readonly reportUsageEvent: (event: MeteredUsageEvent) => Effect.Effect<void, BillingError>;
269
317
  /** Flush all pending local usage counters to the provider. */
270
318
  readonly flushUsage: () => Effect.Effect<void, BillingError>;
271
319
  /** What a plan/seat change WOULD cost, from the provider's own invoice
@@ -275,8 +323,27 @@ export declare interface BillingServiceShape {
275
323
  readonly plan?: PlanId;
276
324
  readonly quantity?: number;
277
325
  }) => Effect.Effect<SubscriptionChange, BillingError>;
278
- /** The provider's invoice history for this tenant. */
326
+ /** Bounded recent provider invoice preview for this tenant, not history coverage. */
279
327
  readonly invoices: (tenantId: string) => Effect.Effect<ReadonlyArray<InvoiceRecord>, BillingError>;
328
+ /** Read history for an exact current or retained customer owned by the tenant.
329
+ * Does not create custody or persist observations. The caller authorizes access. */
330
+ readonly invoiceHistory: (tenantId: string, providerCustomerId: string, options?: {
331
+ readonly cursor?: ProviderInvoiceHistoryCursor;
332
+ readonly limit?: number;
333
+ }) => Effect.Effect<ProviderInvoiceHistoryPage, BillingError>;
334
+ /** Fresh detailed provider evidence for this tenant's current linked customer.
335
+ * This read neither grants entitlements nor replaces native reconciliation. */
336
+ readonly invoiceSnapshot: (tenantId: string, providerInvoiceId: string) => Effect.Effect<ProviderInvoiceSnapshot, BillingError>;
337
+ /** Persist and read back a complete owned observation. A lost revision race
338
+ * repeats the provider read; event payloads never replace provider evidence. */
339
+ readonly reconcileInvoice: (tenantId: string, providerInvoiceId: string, expectedCustomerId?: string) => Effect.Effect<InvoiceObservation, BillingError>;
340
+ /** One bounded durable account/customer pass. Counts are work observations,
341
+ * not global, payment or historical financial completion. */
342
+ readonly reconcileInvoiceHistory: () => Effect.Effect<InvoiceHistoryPassResult, BillingError>;
343
+ /** Process one retained owned history reference with fresh provider/native
344
+ * reads and a fenced durable result. Open/draft/error work stays pending;
345
+ * an observed result is not settlement or historical entitlement coverage. */
346
+ readonly processInvoiceHistoryReference: (tenantId: string, providerCustomerId: string, referenceId: string) => Effect.Effect<InvoiceHistoryReferenceResult, BillingError>;
280
347
  /** Mint a provider-hosted checkout URL for `plan`. */
281
348
  readonly startCheckout: (input: {
282
349
  readonly tenantId: string;
@@ -285,6 +352,9 @@ export declare interface BillingServiceShape {
285
352
  readonly cancelUrl: string;
286
353
  /** Seats to buy up front. Omit for 1. */
287
354
  readonly quantity?: number;
355
+ /** Server-call authority check repeated after waits, before provider work.
356
+ * Native RPC routes supply their current declared-guard check. */
357
+ readonly authorize?: () => Effect.Effect<void, BillingError>;
288
358
  }) => Effect.Effect<{
289
359
  url: string;
290
360
  }, BillingError>;
@@ -334,8 +404,8 @@ export declare interface BillingServiceShape {
334
404
  * load. Every call that changes what a tenant pays already runs it first.
335
405
  */
336
406
  readonly syncTenant: (tenantId: string) => Effect.Effect<Subscription | null, BillingError>;
337
- /** Apply a normalized provider event to the DB rows (idempotent upsert). */
338
- readonly applyEvent: (event: BillingEvent) => Effect.Effect<void, BillingError>;
407
+ /** Apply a verified provider trigger and return its native tenant-resolved event. */
408
+ readonly applyEvent: (event: BillingProviderEvent) => Effect.Effect<BillingEvent, BillingError>;
339
409
  /**
340
410
  * Change the tenant's plan mid-cycle. Computes a TIME-BASED prorated
341
411
  * settlement on the amount difference for the unused remainder of the
@@ -373,6 +443,12 @@ export declare type CancelMode =
373
443
 
374
444
  export declare interface CheckoutInput {
375
445
  readonly tenantId: string;
446
+ /** Identity of one logical checkout request. Providers that support request
447
+ * idempotency must forward it unchanged across transport retries. The
448
+ * caller owns persistence and must not reuse it for different parameters. */
449
+ readonly idempotencyKey?: string;
450
+ /** Fixed deadline of a persisted intent; providers enforce it on creation. */
451
+ readonly expiresAt?: Date;
376
452
  /** Provider price id to subscribe to. */
377
453
  readonly priceId: string;
378
454
  /** Usage-based prices added next to `priceId` (no quantity). */
@@ -393,13 +469,50 @@ export declare interface CheckoutInput {
393
469
  readonly trialDays?: number;
394
470
  }
395
471
 
472
+ export declare interface CheckoutSessionLookup {
473
+ readonly receipt: CheckoutSessionReceipt | null;
474
+ /** Provider-clock evidence, never the application host's clock. */
475
+ readonly observedAt: Date | null;
476
+ }
477
+
478
+ /**
479
+ * Dumb adapter over a billing provider. Holds NO policy — checkout/portal
480
+ * URL minting, usage push, and pure payload→event mapping only. The
481
+ * `BillingService` wraps it with the entitlement engine, the DB rows, and
482
+ * transient retry.
483
+ *
484
+ * `normalizeEvent` receives a payload whose signature was ALREADY verified
485
+ * by @voltro/plugin-webhooks' mounter — this is pure mapping, never a
486
+ * second signature check.
487
+ */
488
+ export declare interface CheckoutSessionReceipt {
489
+ readonly id: string;
490
+ readonly operationId: string | null;
491
+ readonly tenantId: string | null;
492
+ readonly providerCustomerId: string | null;
493
+ readonly providerSubscriptionId: string | null;
494
+ readonly status: 'open' | 'complete' | 'expired';
495
+ readonly url: string | null;
496
+ readonly expiresAt: Date;
497
+ }
498
+
396
499
  /** What `closeAccount` did at the provider. */
397
500
  export declare interface CloseAccountResult {
398
501
  readonly tenantId: string;
399
- /** Provider subscriptions ended (immediately, with a final invoice). */
502
+ /** Provider subscriptions positively confirmed canceled. A scheduled close remains pending. */
400
503
  readonly canceled: ReadonlyArray<string>;
401
504
  }
402
505
 
506
+ declare interface CustomerBinding extends CustomerScope {
507
+ readonly tenantId: string;
508
+ readonly providerCustomerId: string;
509
+ }
510
+
511
+ declare interface CustomerScope {
512
+ readonly provider: string;
513
+ readonly account: ProviderAccountIdentity;
514
+ }
515
+
403
516
  /** The free/fallback plan id every app implicitly has. */
404
517
  export declare const DEFAULT_PLAN: PlanId;
405
518
 
@@ -466,6 +579,59 @@ export declare interface EntitlementStatus {
466
579
  readonly lockout: LockoutMode;
467
580
  }
468
581
 
582
+ /** Immutable native ownership of one provider invoice, not payment authority. */
583
+ export declare interface InvoiceBinding extends ProviderAccountIdentity {
584
+ readonly tenantId: string;
585
+ readonly provider: string;
586
+ readonly providerCustomerId: string;
587
+ readonly providerInvoiceId: string;
588
+ }
589
+
590
+ declare interface InvoiceHistoryJournalOwner extends CustomerBinding {
591
+ readonly custodyId: string;
592
+ }
593
+
594
+ declare interface InvoiceHistoryPassResult {
595
+ readonly kind: 'idle' | 'advanced' | 'contended';
596
+ readonly customerCustodyId: string | null;
597
+ readonly admittedPages: number;
598
+ readonly observed: number;
599
+ readonly deferred: number;
600
+ readonly busy: number;
601
+ readonly unconfirmed: number;
602
+ }
603
+
604
+ declare interface InvoiceHistoryReferenceResult {
605
+ readonly kind: 'observed' | 'deferred' | 'busy';
606
+ readonly work: InvoiceHistoryWork;
607
+ }
608
+
609
+ declare interface InvoiceHistoryWork extends InvoiceHistoryJournalOwner {
610
+ readonly id: string;
611
+ readonly walkId: string;
612
+ readonly referenceId: string;
613
+ readonly providerInvoiceId: string;
614
+ readonly revision: number;
615
+ /** Logical attempts and acquired execution identities survive restarts. */
616
+ readonly attempt: number;
617
+ readonly executions: number;
618
+ readonly state: 'queued' | 'working' | 'retry' | 'deferred' | 'observed';
619
+ readonly activeClaimId: string | null;
620
+ readonly nextAttemptAt: Date;
621
+ readonly lastOutcome: 'unconfirmed' | 'open' | 'observed';
622
+ /** An observed native version, never a payment or entitlement receipt. */
623
+ readonly observationId: string | null;
624
+ readonly observationRevision: number | null;
625
+ readonly updatedAt: Date;
626
+ }
627
+
628
+ export declare interface InvoiceObservation extends InvoiceBinding {
629
+ readonly id: string;
630
+ readonly identityKey: string;
631
+ readonly revision: number;
632
+ readonly snapshot: ProviderInvoiceSnapshot;
633
+ }
634
+
469
635
  export declare interface InvoiceRecord {
470
636
  readonly providerInvoiceId: string;
471
637
  readonly amountMinor: number;
@@ -488,6 +654,15 @@ export declare interface InvoiceRecord {
488
654
  */
489
655
  export declare type LockoutMode = 'hard' | 'soft';
490
656
 
657
+ export declare interface MeteredUsageEvent {
658
+ readonly tenantId: string;
659
+ readonly entitlementKey: string;
660
+ readonly quantity: number;
661
+ readonly occurredAt: Date;
662
+ /** Stable tenant-scoped occurrence identity, reused unchanged on every retry. */
663
+ readonly idempotencyKey: string;
664
+ }
665
+
491
666
  /** A monetary amount: integer minor units + ISO-4217 currency. */
492
667
  export declare interface Money {
493
668
  /** Integer minor units (cents). Never a float. */
@@ -529,6 +704,88 @@ export declare interface PortalInput {
529
704
  readonly returnUrl: string;
530
705
  }
531
706
 
707
+ /** Provider-observed account/mode, not a configured key prefix or tenant hint. */
708
+ export declare interface ProviderAccountIdentity {
709
+ readonly providerAccountId: string;
710
+ readonly livemode: boolean;
711
+ }
712
+
713
+ /** A positive customer response in an explicitly expected provider namespace.
714
+ * observedAt is local observation time, never provider-clock settlement evidence. */
715
+ export declare interface ProviderCustomerSnapshot {
716
+ readonly account: ProviderAccountIdentity;
717
+ readonly providerCustomerId: string;
718
+ readonly deleted: boolean;
719
+ readonly observedAt: Date;
720
+ }
721
+
722
+ /** Explicit provider namespace/customer binding; not a bearer authorization. */
723
+ export declare interface ProviderInvoiceHistoryCursor extends ProviderAccountIdentity {
724
+ readonly providerCustomerId: string;
725
+ readonly afterInvoiceId: string;
726
+ }
727
+
728
+ /** One history reference, not a complete invoice observation or payment proof. */
729
+ export declare interface ProviderInvoiceHistoryEntry extends InvoiceRecord {
730
+ readonly providerCustomerId: string;
731
+ readonly livemode: boolean;
732
+ readonly status: 'draft' | 'open' | 'paid' | 'uncollectible' | 'void';
733
+ readonly createdAt: Date;
734
+ }
735
+
736
+ export declare interface ProviderInvoiceHistoryInput {
737
+ readonly account: ProviderAccountIdentity;
738
+ readonly providerCustomerId: string;
739
+ readonly cursor?: ProviderInvoiceHistoryCursor;
740
+ /** 1..100; defaults to 100. Invalid values fail rather than clamp. */
741
+ readonly limit?: number;
742
+ }
743
+
744
+ export declare interface ProviderInvoiceHistoryPage {
745
+ readonly account: ProviderAccountIdentity;
746
+ readonly providerCustomerId: string;
747
+ readonly invoices: ReadonlyArray<ProviderInvoiceHistoryEntry>;
748
+ readonly nextCursor: ProviderInvoiceHistoryCursor | null;
749
+ /** Local observation time, not provider-clock or financial coverage evidence. */
750
+ readonly observedAt: Date;
751
+ }
752
+
753
+ export declare interface ProviderInvoiceLineSnapshot {
754
+ readonly providerLineId: string;
755
+ readonly providerSubscriptionId: string | null;
756
+ readonly providerSubscriptionItemId: string | null;
757
+ readonly providerPriceId: string | null;
758
+ readonly amountMinor: number;
759
+ readonly currency: string;
760
+ /** Exact provider decimal; absent precision must never be replaced by a truncated integer. */
761
+ readonly quantityDecimal: string | null;
762
+ /** Provider period bounds are inclusive; they are not paid-through authority. */
763
+ readonly periodStart: Date;
764
+ readonly periodEnd: Date;
765
+ /** Null means the provider did not identify the line's origin. */
766
+ readonly proration: boolean | null;
767
+ }
768
+
769
+ /** A complete provider observation, not an entitlement or a payment receipt. */
770
+ export declare interface ProviderInvoiceSnapshot {
771
+ readonly providerInvoiceId: string;
772
+ readonly providerCustomerId: string;
773
+ readonly providerSubscriptionId: string | null;
774
+ readonly providerRevisionId: string | null;
775
+ readonly status: 'open' | 'paid' | 'uncollectible' | 'void';
776
+ readonly livemode: boolean;
777
+ readonly currency: string;
778
+ readonly amountDueMinor: number;
779
+ readonly amountPaidMinor: number;
780
+ readonly amountRemainingMinor: number;
781
+ readonly finalizedAt: Date;
782
+ readonly paidAt: Date | null;
783
+ /** Local read completion time, never a provider clock or entitlement deadline. */
784
+ readonly observedAt: Date;
785
+ /** All pages. A bounded or failed read must fail instead of returning a prefix. */
786
+ readonly lines: ReadonlyArray<ProviderInvoiceLineSnapshot>;
787
+ }
788
+
532
789
  /** What `reconcileFromProvider` found and corrected. */
533
790
  export declare interface ProviderReconcileResult {
534
791
  /** Subscriptions compared (provider-listed plus local rows it did not list). */
@@ -643,6 +900,12 @@ export declare interface SubscriptionChangeResult {
643
900
  readonly currentPeriodEnd: Date | null;
644
901
  }
645
902
 
903
+ export declare interface SubscriptionCloseOperation {
904
+ readonly id: string;
905
+ /** Provider-clock deadline captured after final usage dispatch. */
906
+ readonly cancelAt: Date;
907
+ }
908
+
646
909
  /**
647
910
  * Raised when dunning has locked a tenant out — the grace period after a
648
911
  * provider-confirmed past-due has expired. Distinct from
@@ -692,10 +955,18 @@ export declare interface SubscriptionUpdateInput {
692
955
  /** An event the provider failed to deliver, reduced to what a catch-up needs. */
693
956
  export declare interface UndeliveredEvent {
694
957
  readonly id: string;
958
+ readonly event: BillingProviderEvent | null;
959
+ /** A recognized provider event could not be normalized; retry/report it, never fall back to discovery. */
960
+ readonly rejected?: true;
695
961
  readonly providerCustomerId: string | null;
696
962
  readonly createdAt: Date | null;
697
963
  }
698
964
 
965
+ export declare interface UndeliveredEventPage {
966
+ readonly events: ReadonlyArray<UndeliveredEvent>;
967
+ readonly nextCursor: string | null;
968
+ }
969
+
699
970
  export declare interface UsagePush {
700
971
  readonly tenantId: string;
701
972
  readonly entitlementKey: string;