@voltro/plugin-billing 0.86.0 → 0.88.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +5484 -0
  2. package/README.md +428 -0
  3. package/THIRD-PARTY-NOTICES.md +1511 -276
  4. package/dist/catalog.js +1 -2
  5. package/dist/checkoutStore.js +164 -0
  6. package/dist/closeStore.js +124 -0
  7. package/dist/custodyInventory.js +154 -0
  8. package/dist/customerStore.js +258 -0
  9. package/dist/dunning.js +1 -2
  10. package/dist/economy.d.ts +30 -0
  11. package/dist/economy.js +265 -0
  12. package/dist/entitlement.js +1 -2
  13. package/dist/eventDelivery.js +71 -0
  14. package/dist/flushClaim.js +1 -2
  15. package/dist/index.d.ts +669 -65
  16. package/dist/index.js +230 -173
  17. package/dist/invoiceHistory.js +90 -0
  18. package/dist/invoiceHistoryJournal.js +475 -0
  19. package/dist/invoiceHistoryProgress.js +234 -0
  20. package/dist/invoiceHistoryWork.js +362 -0
  21. package/dist/invoiceSnapshot.js +65 -0
  22. package/dist/invoiceStore.js +132 -0
  23. package/dist/metering.js +1 -2
  24. package/dist/plans.js +1 -2
  25. package/dist/providerIdentity.js +23 -0
  26. package/dist/providers.js +271 -67
  27. package/dist/provision.js +1 -2
  28. package/dist/rpc.js +1 -2
  29. package/dist/service.js +892 -392
  30. package/dist/stores.js +125 -291
  31. package/dist/stripeClient.js +1 -2
  32. package/dist/stripeIdentity.js +55 -0
  33. package/dist/stripeInvoiceHistory.js +66 -0
  34. package/dist/stripeInvoiceSnapshot.js +85 -0
  35. package/dist/stripeProvider.js +305 -179
  36. package/dist/stripeRest.js +1 -2
  37. package/dist/stripeVerifiedEvent.js +28 -0
  38. package/dist/table.js +70 -48
  39. package/dist/types.d.ts +298 -27
  40. package/dist/types.js +1 -2
  41. package/dist/usageEvents.js +64 -0
  42. package/dist/web/index.js +1 -2
  43. package/dist/webhook.js +32 -40
  44. package/package.json +18 -8
package/dist/index.d.ts CHANGED
@@ -3,6 +3,8 @@ import { Context } from 'effect';
3
3
  import { DataStore } from '@voltro/database';
4
4
  import { default as default_2 } from 'stripe';
5
5
  import { Effect } from 'effect';
6
+ import { IdempotencyClaim } from '@voltro/protocol';
7
+ import { IdempotencyStore } from '@voltro/protocol';
6
8
  import { IncomingRequest } from '@voltro/plugin-webhooks';
7
9
  import { IncomingResponse } from '@voltro/plugin-webhooks';
8
10
  import { IncomingWebhookDescriptor } from '@voltro/plugin-webhooks';
@@ -26,16 +28,45 @@ export declare const assertBillingProductionReady: (input: {
26
28
  readonly redirectOrigins: ReadonlyArray<string> | undefined;
27
29
  }) => void;
28
30
 
31
+ export declare const BILLING_CHECKOUT_OPERATIONS_TABLE = "_voltro_billing_checkout_operations";
32
+
33
+ export declare const BILLING_CLOSE_OPERATIONS_TABLE = "_voltro_billing_close_operations";
34
+
35
+ /** Tenant-addressable native inputs, including retained tables. Global delivery
36
+ * receipts, provider history and the application's own records need separate
37
+ * inventory; this roster does not assert their absence or financial completion. */
38
+ export declare const BILLING_CUSTODY_INVENTORY_TABLES: ReadonlyArray<string>;
39
+
40
+ export declare const BILLING_CUSTOMER_ACCOUNTS_TABLE = "_voltro_billing_customer_accounts";
41
+
42
+ export declare const BILLING_CUSTOMER_CUSTODY_TABLE = "_voltro_billing_customer_custody";
43
+
29
44
  export declare const BILLING_CUSTOMERS_TABLE = "_voltro_billing_customers";
30
45
 
31
46
  export declare const BILLING_DUNNING_NOTICES_TABLE = "_voltro_billing_dunning_notices";
32
47
 
33
48
  export declare const BILLING_FLUSH_CLAIMS_TABLE = "_voltro_billing_flush_claims";
34
49
 
50
+ export declare const BILLING_INVOICE_HISTORY_DISPATCH_TABLE = "_voltro_billing_invoice_history_dispatch";
51
+
52
+ export declare const BILLING_INVOICE_HISTORY_PAGES_TABLE = "_voltro_billing_invoice_history_pages";
53
+
54
+ export declare const BILLING_INVOICE_HISTORY_REFERENCES_TABLE = "_voltro_billing_invoice_history_references";
55
+
56
+ export declare const BILLING_INVOICE_HISTORY_SCAN_ACCOUNTS_TABLE = "_voltro_billing_invoice_history_scan_accounts";
57
+
58
+ export declare const BILLING_INVOICE_HISTORY_WALKS_TABLE = "_voltro_billing_invoice_history_walks";
59
+
60
+ export declare const BILLING_INVOICE_HISTORY_WORK_TABLE = "_voltro_billing_invoice_history_work";
61
+
62
+ export declare const BILLING_INVOICE_OBSERVATIONS_TABLE = "_voltro_billing_account_invoices";
63
+
35
64
  export declare const BILLING_INVOICES_TABLE = "_voltro_billing_invoices";
36
65
 
37
66
  export declare const BILLING_SUBSCRIPTIONS_TABLE = "_voltro_billing_subscriptions";
38
67
 
68
+ export declare const BILLING_USAGE_EVENTS_TABLE = "_voltro_billing_usage_events";
69
+
39
70
  export declare const BILLING_USAGE_TABLE = "_voltro_billing_usage";
40
71
 
41
72
  /** The webhook id + the path the doc + plugin mount at. */
@@ -51,6 +82,35 @@ export declare const BILLING_WEBHOOK_PATH = "/billing/webhook";
51
82
  */
52
83
  export declare type BillingAction = 'checkout' | 'change' | 'resume' | 'portal' | 'cancel';
53
84
 
85
+ export declare const billingCheckoutOperationsTable: BillingTable;
86
+
87
+ export declare interface BillingCloseOperation {
88
+ readonly id: string;
89
+ readonly tenantId: string;
90
+ readonly provider: string;
91
+ readonly scope: string;
92
+ readonly providerSubscriptionId: string | null;
93
+ readonly cancelAt: Date | null;
94
+ readonly createdAt: Date;
95
+ readonly usageSealed: number;
96
+ }
97
+
98
+ export declare const billingCloseOperationsTable: BillingTable;
99
+
100
+ export declare interface BillingCloseStore {
101
+ readonly sealUsage: (tenantId: string) => Effect.Effect<BillingCloseOperation, BillingError>;
102
+ readonly getAccount: (tenantId: string) => Effect.Effect<BillingCloseOperation | null, BillingError>;
103
+ readonly reserveAccount: (tenantId: string, provider: string, createdAt: Date) => Effect.Effect<BillingCloseOperation, BillingError>;
104
+ readonly getSubscription: (tenantId: string, providerSubscriptionId: string) => Effect.Effect<BillingCloseOperation | null, BillingError>;
105
+ readonly reserveSubscription: (input: {
106
+ readonly tenantId: string;
107
+ readonly provider: string;
108
+ readonly providerSubscriptionId: string;
109
+ readonly cancelAt: Date;
110
+ readonly createdAt: Date;
111
+ }) => Effect.Effect<BillingCloseOperation, BillingError>;
112
+ }
113
+
54
114
  /**
55
115
  * The shape every handler executor receives carries the resolved subject
56
116
  * under `ctx.request.subject` (mirrors `@voltro/plugin-rbac`'s
@@ -62,9 +122,55 @@ export declare interface BillingContext {
62
122
  };
63
123
  }
64
124
 
125
+ export declare interface BillingCustodyInventory {
126
+ readonly format: 'voltro.billing-custody-inventory.v1';
127
+ readonly tenantId: string;
128
+ readonly digest: string;
129
+ readonly tables: ReadonlyArray<BillingCustodyInventoryTable>;
130
+ /** Diagnostic local time, excluded from the content digest. */
131
+ readonly observedAt: Date;
132
+ /** Two equal native read sweeps are observations, not a writer-stop proof,
133
+ * an atomic provider snapshot, an approval or original account authority. */
134
+ readonly purpose: 'review-only';
135
+ }
136
+
137
+ export declare interface BillingCustodyInventoryOptions {
138
+ readonly tenantId: string;
139
+ /** Defaults to 200; bounded to 1..500. */
140
+ readonly pageSize?: number;
141
+ /** Defaults to 5,000; bounded to 1..50,000 per table. Exceeding this limit
142
+ * fails the whole inventory rather than returning a truncated manifest. */
143
+ readonly maxRowsPerTable?: number;
144
+ }
145
+
146
+ export declare interface BillingCustodyInventoryRow {
147
+ readonly id: string;
148
+ /** Hash of every returned native field, not just the displayed references. */
149
+ readonly digest: string;
150
+ /** Existing native identifiers only; never a proposed customer/tenant mapping. */
151
+ readonly references: Readonly<Record<string, string>>;
152
+ }
153
+
154
+ export declare interface BillingCustodyInventoryTable {
155
+ readonly tableName: string;
156
+ readonly rowCount: number;
157
+ readonly digest: string;
158
+ readonly rows: ReadonlyArray<BillingCustodyInventoryRow>;
159
+ }
160
+
161
+ export declare const billingCustomerAccountsTable: BillingTable;
162
+
163
+ export declare const billingCustomerCustodyTable: BillingTable;
164
+
65
165
  /** tenant ↔ provider customer link. */
66
166
  export declare const billingCustomersTable: BillingTable;
67
167
 
168
+ /** Optional native receipt binding for standalone webhook mounts. */
169
+ export declare interface BillingDeliveryReceipts {
170
+ readonly scope: string;
171
+ readonly store: () => IdempotencyStore;
172
+ }
173
+
68
174
  /**
69
175
  * The sent-notice ledger. One row per `(tenant, episode, step)` — and the
70
176
  * UNIQUE over exactly those three is the send gate, not a report: a notice is
@@ -103,7 +209,7 @@ declare const BillingError_base: Schema.Class<BillingError, Schema.TaggedStruct<
103
209
  }>, YieldableError>;
104
210
 
105
211
  /**
106
- * The normalized event union a `BillingProvider.normalizeEvent` produces
212
+ * The tenant-resolved event union dispatched after `BillingService.applyEvent` accepts an event
107
213
  * from a verified, decoded provider webhook payload. `BillingService`
108
214
  * applies these to the DB rows — switching provider swaps the adapter;
109
215
  * the service + rows + entitlement engine are unchanged.
@@ -156,15 +262,51 @@ export declare type BillingEvent = {
156
262
  readonly occurredAt: Date | null;
157
263
  };
158
264
 
265
+ /** Per-event side effect, run AFTER the row is updated. */
266
+ export declare interface BillingEventDeliveryContext extends ProviderAccountIdentity {
267
+ readonly provider: string;
268
+ readonly providerEventId: string;
269
+ readonly receiptScope: string;
270
+ }
271
+
159
272
  /** The event tag union — handy for `onEvent` keys. */
160
273
  export declare type BillingEventTag = BillingEvent['_tag'];
161
274
 
162
275
  /** One row per flush window — `windowKey` UNIQUE makes the INSERT the gate. */
163
276
  export declare const billingFlushClaimsTable: TableLike;
164
277
 
165
- /** Invoice history (money as integer minor units). */
278
+ export declare const billingInvoiceHistoryDispatchTable: BillingTable;
279
+
280
+ export declare const billingInvoiceHistoryPagesTable: BillingTable;
281
+
282
+ export declare const billingInvoiceHistoryReferencesTable: BillingTable;
283
+
284
+ export declare const billingInvoiceHistoryScanAccountsTable: BillingTable;
285
+
286
+ export declare const billingInvoiceHistoryWalksTable: BillingTable;
287
+
288
+ export declare const billingInvoiceHistoryWorkTable: BillingTable;
289
+
290
+ export declare const billingInvoiceObservationsTable: BillingTable;
291
+
292
+ /** Retained invoice-event history; current writes use billingInvoiceObservationsTable. */
166
293
  export declare const billingInvoicesTable: BillingTable;
167
294
 
295
+ export declare type BillingInvoiceTrigger = BillingInvoiceTriggerFields & ({
296
+ readonly _tag: 'invoicePaid';
297
+ } | {
298
+ readonly _tag: 'invoicePaymentFailed';
299
+ });
300
+
301
+ /** Verified provider invoice trigger. Tenant metadata is never ownership evidence. */
302
+ declare interface BillingInvoiceTriggerFields {
303
+ readonly providerCustomerId: string;
304
+ readonly providerInvoiceId: string;
305
+ readonly amountMinor: number;
306
+ readonly currency: string;
307
+ readonly occurredAt: Date | null;
308
+ }
309
+
168
310
  export declare const billingPlugin: (options?: BillingPluginOptions) => VoltroPlugin;
169
311
 
170
312
  export declare interface BillingPluginOptions {
@@ -275,17 +417,14 @@ export declare interface BillingPluginOptions {
275
417
  readonly name?: string;
276
418
  }
277
419
 
278
- /**
279
- * Dumb adapter over a billing provider. Holds NO policy — checkout/portal
280
- * URL minting, usage push, and pure payload→event mapping only. The
281
- * `BillingService` wraps it with the entitlement engine, the DB rows, and
282
- * transient retry.
283
- *
284
- * `normalizeEvent` receives a payload whose signature was ALREADY verified
285
- * by @voltro/plugin-webhooks' mounter — this is pure mapping, never a
286
- * second signature check.
287
- */
288
420
  export declare interface BillingProvider {
421
+ /** Required by account-qualified custody consumers; absence is unsupported, not fallback authority. */
422
+ readonly fetchAccountIdentity?: () => Effect.Effect<ProviderAccountIdentity, BillingError>;
423
+ /** Must reject a different account/mode before reading customer data. A 404 is not positive deletion evidence. */
424
+ readonly fetchCustomerSnapshot?: (input: {
425
+ readonly account: ProviderAccountIdentity;
426
+ readonly providerCustomerId: string;
427
+ }) => Effect.Effect<ProviderCustomerSnapshot, BillingError>;
289
428
  readonly name: string;
290
429
  /** Whether the provider supports server-pushed metered usage. Stripe
291
430
  * does (subscription-item usage records); a flat-plan provider may not.
@@ -296,9 +435,13 @@ export declare interface BillingProvider {
296
435
  * meters map events to a customer by id, so an unlinked tenant's usage
297
436
  * stays pending instead of being sent unattributable. */
298
437
  readonly usageRequiresCustomer?: boolean;
299
- readonly createCheckoutSession: (input: CheckoutInput) => Effect.Effect<{
300
- url: string;
301
- }, BillingError>;
438
+ readonly createCheckoutSession: (input: CheckoutInput) => Effect.Effect<CheckoutSessionReceipt, BillingError>;
439
+ /** Read positive evidence for one exact persisted checkout intent. */
440
+ readonly findCheckoutSession: (input: {
441
+ readonly tenantId: string;
442
+ readonly providerCustomerId?: string;
443
+ readonly operationId: string;
444
+ }) => Effect.Effect<CheckoutSessionLookup, BillingError>;
302
445
  readonly createPortalSession: (input: PortalInput) => Effect.Effect<{
303
446
  url: string;
304
447
  }, BillingError>;
@@ -330,6 +473,10 @@ export declare interface BillingProvider {
330
473
  * matters — the subscription's status right now does. Returns null when the
331
474
  * provider does not know the id.
332
475
  */
476
+ /** Direct customer-qualified observation, including terminal subscriptions.
477
+ * Null is absence, never proof of cancellation. Required for retired-customer
478
+ * reconciliation and for subscriptions absent from the live listing. */
479
+ readonly fetchSubscriptionSnapshot?: (providerSubscriptionId: string) => Effect.Effect<ProviderSubscriptionSnapshot | null, BillingError>;
333
480
  readonly fetchSubscription: (providerSubscriptionId: string) => Effect.Effect<ProviderSubscriptionState | null, BillingError>;
334
481
  /**
335
482
  * Every live (not yet ended) subscription at the provider, optionally for
@@ -339,12 +486,19 @@ export declare interface BillingProvider {
339
486
  readonly listSubscriptions: (input: {
340
487
  readonly providerCustomerId?: string;
341
488
  }) => Effect.Effect<ReadonlyArray<ProviderSubscriptionSnapshot>, BillingError>;
342
- /** End a subscription — at period end (reversible) or immediately with a
343
- * final invoice. Returns the provider's state afterwards. */
489
+ /** Quote the provider-clock settlement deadline after final usage dispatch.
490
+ * Persist it before requesting account-close cancellation. */
491
+ readonly prepareSubscriptionClose: (providerSubscriptionId: string) => Effect.Effect<{
492
+ readonly cancelAt: Date;
493
+ }, BillingError>;
344
494
  readonly cancelSubscription: (input: {
345
495
  readonly providerSubscriptionId: string;
346
- readonly mode: CancelMode;
347
- }) => Effect.Effect<ProviderSubscriptionState, BillingError>;
496
+ } & ({
497
+ readonly mode: 'periodEnd';
498
+ } | {
499
+ readonly mode: 'close';
500
+ readonly closeOperation: SubscriptionCloseOperation;
501
+ })) => Effect.Effect<ProviderSubscriptionState, BillingError>;
348
502
  /** Undo a period-end cancellation that has not taken effect yet. */
349
503
  readonly resumeSubscription: (providerSubscriptionId: string) => Effect.Effect<ProviderSubscriptionState, BillingError>;
350
504
  /**
@@ -361,6 +515,7 @@ export declare interface BillingProvider {
361
515
  * can be paid. Returns how many were expired. */
362
516
  readonly expireOpenCheckouts: (input: {
363
517
  readonly providerCustomerId: string;
518
+ readonly exceptOperationId?: string;
364
519
  }) => Effect.Effect<number, BillingError>;
365
520
  /** Add prices the subscription is missing (metered items), without
366
521
  * proration. Prices already present are left alone. */
@@ -374,12 +529,22 @@ export declare interface BillingProvider {
374
529
  * the app keeps its own. Optional: a provider may model no customer email.
375
530
  */
376
531
  readonly customerEmail?: (providerCustomerId: string) => Effect.Effect<string | null, BillingError>;
377
- /** The provider's invoice history. We mirror it for listing but never
378
- * render an invoice ourselves — `hostedUrl` / `pdfUrl` are theirs. */
532
+ /** Bounded recent invoice preview, not complete history. Provider-hosted
533
+ * links remain theirs; use listInvoiceHistory for explicit cursor traversal. */
379
534
  readonly listInvoices: (input: {
380
535
  readonly providerCustomerId: string;
381
536
  readonly limit?: number;
382
537
  }) => Effect.Effect<ReadonlyArray<InvoiceRecord>, BillingError>;
538
+ /** Explicit bounded history pages, including draft and terminal statuses.
539
+ * Cursor exhaustion describes this traversal, not a stable global snapshot.
540
+ * Unsupported adapters must fail; the latest-preview list is no fallback. */
541
+ readonly listInvoiceHistory?: (input: ProviderInvoiceHistoryInput) => Effect.Effect<ProviderInvoiceHistoryPage, BillingError>;
542
+ /** Read one finalized invoice with every line, after confirming its customer.
543
+ * Absence means this provider cannot supply detailed invoice evidence. */
544
+ readonly fetchInvoiceSnapshot?: (input: {
545
+ readonly providerCustomerId: string;
546
+ readonly providerInvoiceId: string;
547
+ }) => Effect.Effect<ProviderInvoiceSnapshot, BillingError>;
383
548
  /**
384
549
  * Verify a webhook signature and decode the payload, using the provider's
385
550
  * OWN verification. Returns `null` when this provider does not do its own
@@ -390,10 +555,10 @@ export declare interface BillingProvider {
390
555
  readonly signatureHeader: string;
391
556
  readonly secret: string;
392
557
  }) => Effect.Effect<unknown, BillingError>;
393
- /** Map a verified, decoded webhook payload to a `BillingEvent`, or
558
+ /** Map a verified, decoded webhook payload to a `BillingProviderEvent`, or
394
559
  * `null` for an event type this provider doesn't model (handler
395
560
  * no-ops, still 200). */
396
- readonly normalizeEvent: (raw: unknown) => Effect.Effect<BillingEvent | null, BillingError>;
561
+ readonly normalizeEvent: (raw: unknown) => Effect.Effect<BillingProviderEvent | null, BillingError>;
397
562
  /**
398
563
  * Who and when a verified payload is about: the provider customer it names
399
564
  * and when the provider created it. The webhook syncs that customer from a
@@ -410,11 +575,22 @@ export declare interface BillingProvider {
410
575
  * it keeps them (Stripe: 30 days) — what an outage of the endpoint left
411
576
  * behind. Optional: a provider without it has no catch-up.
412
577
  */
413
- readonly listUndeliveredEvents?: () => Effect.Effect<ReadonlyArray<UndeliveredEvent>, BillingError>;
578
+ readonly listUndeliveredEvents?: (input: {
579
+ readonly cursor: string | null;
580
+ readonly limit: number;
581
+ }) => Effect.Effect<UndeliveredEventPage, BillingError>;
414
582
  }
415
583
 
584
+ /** Invoice ownership is resolved from native customer links before dispatch. */
585
+ export declare type BillingProviderEvent = Exclude<BillingEvent, {
586
+ readonly _tag: 'invoicePaid' | 'invoicePaymentFailed';
587
+ }> | BillingInvoiceTrigger;
588
+
416
589
  export declare type BillingProviderName = 'stripe' | 'mock';
417
590
 
591
+ /** Retained unqualified observations are not adopted by the current writer. */
592
+ export declare const billingRetainedInvoiceObservationsTable: BillingTable;
593
+
418
594
  /** The billing service Tag — `const billing = yield* BillingService`. */
419
595
  export declare class BillingService extends BillingService_base {
420
596
  }
@@ -521,6 +697,8 @@ export declare interface BillingServiceShape {
521
697
  readonly consumeEntitlement: (tenantId: string, key: string, cost: number) => Effect.Effect<void, BillingError | EntitlementExceeded>;
522
698
  /** Record metered usage locally; flushed to the provider in batches. */
523
699
  readonly reportUsage: (tenantId: string, key: string, qty: number) => Effect.Effect<void, BillingError>;
700
+ /** Atomically retain an occurrence receipt and increment its original UTC month. */
701
+ readonly reportUsageEvent: (event: MeteredUsageEvent) => Effect.Effect<void, BillingError>;
524
702
  /** Flush all pending local usage counters to the provider. */
525
703
  readonly flushUsage: () => Effect.Effect<void, BillingError>;
526
704
  /** What a plan/seat change WOULD cost, from the provider's own invoice
@@ -530,8 +708,27 @@ export declare interface BillingServiceShape {
530
708
  readonly plan?: PlanId;
531
709
  readonly quantity?: number;
532
710
  }) => Effect.Effect<SubscriptionChange, BillingError>;
533
- /** The provider's invoice history for this tenant. */
711
+ /** Bounded recent provider invoice preview for this tenant, not history coverage. */
534
712
  readonly invoices: (tenantId: string) => Effect.Effect<ReadonlyArray<InvoiceRecord>, BillingError>;
713
+ /** Read history for an exact current or retained customer owned by the tenant.
714
+ * Does not create custody or persist observations. The caller authorizes access. */
715
+ readonly invoiceHistory: (tenantId: string, providerCustomerId: string, options?: {
716
+ readonly cursor?: ProviderInvoiceHistoryCursor;
717
+ readonly limit?: number;
718
+ }) => Effect.Effect<ProviderInvoiceHistoryPage, BillingError>;
719
+ /** Fresh detailed provider evidence for this tenant's current linked customer.
720
+ * This read neither grants entitlements nor replaces native reconciliation. */
721
+ readonly invoiceSnapshot: (tenantId: string, providerInvoiceId: string) => Effect.Effect<ProviderInvoiceSnapshot, BillingError>;
722
+ /** Persist and read back a complete owned observation. A lost revision race
723
+ * repeats the provider read; event payloads never replace provider evidence. */
724
+ readonly reconcileInvoice: (tenantId: string, providerInvoiceId: string, expectedCustomerId?: string) => Effect.Effect<InvoiceObservation, BillingError>;
725
+ /** One bounded durable account/customer pass. Counts are work observations,
726
+ * not global, payment or historical financial completion. */
727
+ readonly reconcileInvoiceHistory: () => Effect.Effect<InvoiceHistoryPassResult, BillingError>;
728
+ /** Process one retained owned history reference with fresh provider/native
729
+ * reads and a fenced durable result. Open/draft/error work stays pending;
730
+ * an observed result is not settlement or historical entitlement coverage. */
731
+ readonly processInvoiceHistoryReference: (tenantId: string, providerCustomerId: string, referenceId: string) => Effect.Effect<InvoiceHistoryReferenceResult, BillingError>;
535
732
  /** Mint a provider-hosted checkout URL for `plan`. */
536
733
  readonly startCheckout: (input: {
537
734
  readonly tenantId: string;
@@ -540,6 +737,9 @@ export declare interface BillingServiceShape {
540
737
  readonly cancelUrl: string;
541
738
  /** Seats to buy up front. Omit for 1. */
542
739
  readonly quantity?: number;
740
+ /** Server-call authority check repeated after waits, before provider work.
741
+ * Native RPC routes supply their current declared-guard check. */
742
+ readonly authorize?: () => Effect.Effect<void, BillingError>;
543
743
  }) => Effect.Effect<{
544
744
  url: string;
545
745
  }, BillingError>;
@@ -589,8 +789,8 @@ export declare interface BillingServiceShape {
589
789
  * load. Every call that changes what a tenant pays already runs it first.
590
790
  */
591
791
  readonly syncTenant: (tenantId: string) => Effect.Effect<Subscription | null, BillingError>;
592
- /** Apply a normalized provider event to the DB rows (idempotent upsert). */
593
- readonly applyEvent: (event: BillingEvent) => Effect.Effect<void, BillingError>;
792
+ /** Apply a verified provider trigger and return its native tenant-resolved event. */
793
+ readonly applyEvent: (event: BillingProviderEvent) => Effect.Effect<BillingEvent, BillingError>;
594
794
  /**
595
795
  * Change the tenant's plan mid-cycle. Computes a TIME-BASED prorated
596
796
  * settlement on the amount difference for the unused remainder of the
@@ -610,9 +810,14 @@ export declare interface BillingServiceShape {
610
810
  }
611
811
 
612
812
  export declare interface BillingStores {
813
+ readonly closures: BillingCloseStore;
814
+ readonly checkouts: CheckoutOperationStore;
613
815
  readonly customers: CustomerStore;
614
816
  readonly subscriptions: SubscriptionStore;
615
817
  readonly invoices: InvoiceStore;
818
+ readonly historyJournal: InvoiceHistoryJournal;
819
+ readonly historyWork: InvoiceHistoryWorkStore;
820
+ readonly historyProgress: InvoiceHistoryProgressStore;
616
821
  readonly usage: UsageStore;
617
822
  /** The dunning sent-notice ledger — the send gate, not a report. */
618
823
  readonly dunningNotices: DunningNoticeStore;
@@ -634,6 +839,8 @@ export declare interface BillingTable extends TableLike {
634
839
 
635
840
  export declare const billingTables: () => ReadonlyArray<BillingTable>;
636
841
 
842
+ export declare const billingUsageEventsTable: BillingTable;
843
+
637
844
  /** Per-tenant metered counters driving the entitlement engine. */
638
845
  export declare const billingUsageTable: BillingTable;
639
846
 
@@ -648,6 +855,8 @@ declare interface BillingWebhookOptions {
648
855
  readonly provider: BillingProvider;
649
856
  readonly service: BillingServiceShape;
650
857
  readonly onEvent?: OnEventMap;
858
+ /** Shared native receipt binding; standalone mounts otherwise retain process memory only. */
859
+ readonly receipts?: BillingDeliveryReceipts;
651
860
  /** The signing secret. It is BOTH the secret the mount's generic HMAC check
652
861
  * verifies with AND the one the provider's own verifier (Stripe's
653
862
  * `constructEvent`) uses. Null → the mount answers 503: an unverifiable
@@ -717,6 +926,12 @@ export declare type CatalogFinding = {
717
926
 
718
927
  export declare interface CheckoutInput {
719
928
  readonly tenantId: string;
929
+ /** Identity of one logical checkout request. Providers that support request
930
+ * idempotency must forward it unchanged across transport retries. The
931
+ * caller owns persistence and must not reuse it for different parameters. */
932
+ readonly idempotencyKey?: string;
933
+ /** Fixed deadline of a persisted intent; providers enforce it on creation. */
934
+ readonly expiresAt?: Date;
720
935
  /** Provider price id to subscribe to. */
721
936
  readonly priceId: string;
722
937
  /** Usage-based prices added next to `priceId` (no quantity). */
@@ -737,6 +952,65 @@ export declare interface CheckoutInput {
737
952
  readonly trialDays?: number;
738
953
  }
739
954
 
955
+ export declare interface CheckoutOperation {
956
+ readonly id: string;
957
+ readonly tenantId: string;
958
+ readonly provider: string;
959
+ readonly requestDigest: string;
960
+ readonly plan: string;
961
+ readonly quantity: number;
962
+ readonly providerCustomerId: string | null;
963
+ readonly providerSessionId: string | null;
964
+ readonly phase: CheckoutPhase;
965
+ readonly revision: number;
966
+ readonly active: number | null;
967
+ readonly slot: string;
968
+ readonly createdAt: Date;
969
+ readonly expiresAt: Date;
970
+ readonly updatedAt: Date;
971
+ }
972
+
973
+ export declare interface CheckoutOperationStore {
974
+ readonly reserve: (input: CheckoutReservation) => Effect.Effect<CheckoutOperation, BillingError>;
975
+ readonly getActive: (tenantId: string) => Effect.Effect<CheckoutOperation | null, BillingError>;
976
+ readonly patch: (operationId: string, patch: {
977
+ readonly phase?: CheckoutPhase;
978
+ readonly providerCustomerId?: string;
979
+ readonly providerSessionId?: string;
980
+ }) => Effect.Effect<CheckoutOperation, BillingError>;
981
+ }
982
+
983
+ export declare type CheckoutPhase = 'preparing' | 'creating' | 'ready' | 'completed' | 'expired' | 'retired';
984
+
985
+ export declare type CheckoutReservation = Pick<CheckoutOperation, 'tenantId' | 'provider' | 'requestDigest' | 'plan' | 'quantity' | 'createdAt' | 'expiresAt'>;
986
+
987
+ export declare interface CheckoutSessionLookup {
988
+ readonly receipt: CheckoutSessionReceipt | null;
989
+ /** Provider-clock evidence, never the application host's clock. */
990
+ readonly observedAt: Date | null;
991
+ }
992
+
993
+ /**
994
+ * Dumb adapter over a billing provider. Holds NO policy — checkout/portal
995
+ * URL minting, usage push, and pure payload→event mapping only. The
996
+ * `BillingService` wraps it with the entitlement engine, the DB rows, and
997
+ * transient retry.
998
+ *
999
+ * `normalizeEvent` receives a payload whose signature was ALREADY verified
1000
+ * by @voltro/plugin-webhooks' mounter — this is pure mapping, never a
1001
+ * second signature check.
1002
+ */
1003
+ export declare interface CheckoutSessionReceipt {
1004
+ readonly id: string;
1005
+ readonly operationId: string | null;
1006
+ readonly tenantId: string | null;
1007
+ readonly providerCustomerId: string | null;
1008
+ readonly providerSubscriptionId: string | null;
1009
+ readonly status: 'open' | 'complete' | 'expired';
1010
+ readonly url: string | null;
1011
+ readonly expiresAt: Date;
1012
+ }
1013
+
740
1014
  /**
741
1015
  * Try to claim the flush window. INSERT-wins: the first replica's row is the
742
1016
  * winner; a racing replica's `insertIgnore` returns that same row, so it sees a
@@ -749,29 +1023,68 @@ export declare const claimFlushWindow: (store: DataStore, windowKey: string, rep
749
1023
  /** What `closeAccount` did at the provider. */
750
1024
  export declare interface CloseAccountResult {
751
1025
  readonly tenantId: string;
752
- /** Provider subscriptions ended (immediately, with a final invoice). */
1026
+ /** Provider subscriptions positively confirmed canceled. A scheduled close remains pending. */
753
1027
  readonly canceled: ReadonlyArray<string>;
754
1028
  }
755
1029
 
756
- declare interface CustomerRecord {
1030
+ export declare interface CustomerBinding extends CustomerScope {
757
1031
  readonly tenantId: string;
758
- readonly provider: string;
759
1032
  readonly providerCustomerId: string;
760
1033
  }
761
1034
 
1035
+ export declare interface CustomerCustodyInventoryPage {
1036
+ readonly customers: ReadonlyArray<CustomerRecord>;
1037
+ /** Read continuation only; not global or financial completion. */
1038
+ readonly nextCustodyId: string | null;
1039
+ }
1040
+
1041
+ declare interface CustomerRecord extends CustomerBinding {
1042
+ readonly id: string;
1043
+ readonly retiredAt: Date | null;
1044
+ }
1045
+
1046
+ export declare interface CustomerScope {
1047
+ readonly provider: string;
1048
+ readonly account: ProviderAccountIdentity;
1049
+ }
1050
+
762
1051
  export declare interface CustomerStore {
763
- readonly upsert: (record: CustomerRecord) => Effect.Effect<void, BillingError>;
1052
+ /** Immutable financial namespace intent, separate from customer ownership. */
1053
+ readonly accountForTenant: (tenantId: string) => Effect.Effect<CustomerScope | null, BillingError>;
1054
+ readonly assertAccount: (tenantId: string, scope: CustomerScope) => Effect.Effect<boolean, BillingError>;
1055
+ readonly reserveAccount: (tenantId: string, scope: CustomerScope) => Effect.Effect<void, BillingError>;
1056
+ /** Never overwrites ownership or resurrects a retired customer. */
1057
+ readonly bind: (binding: CustomerBinding, snapshot: ProviderCustomerSnapshot) => Effect.Effect<CustomerRecord, BillingError>;
764
1058
  readonly getByTenant: (tenantId: string) => Effect.Effect<CustomerRecord | null, BillingError>;
765
- readonly getByProviderCustomerId: (providerCustomerId: string) => Effect.Effect<CustomerRecord | null, BillingError>;
766
- /** Every linked customer — the provider reconcile's per-customer pass. */
767
- readonly list: () => Effect.Effect<ReadonlyArray<CustomerRecord>, BillingError>;
768
- /** Drop the link to a provider customer that no longer exists. Returns the
769
- * tenant it belonged to, or null when it was not linked. */
770
- readonly removeByProviderCustomerId: (providerCustomerId: string) => Effect.Effect<string | null, BillingError>;
1059
+ /** Includes retained retired ownership, within this exact provider account/mode. */
1060
+ readonly getByProviderCustomerId: (providerCustomerId: string, scope: CustomerScope) => Effect.Effect<CustomerRecord | null, BillingError>;
1061
+ readonly list: (scope: CustomerScope) => Effect.Effect<ReadonlyArray<CustomerRecord>, BillingError>;
1062
+ /** Bounded exact-account inventory including retained retired customers.
1063
+ * Restart at the head in later passes to discover newly inserted custody. */
1064
+ readonly inventory: (scope: CustomerScope, input?: {
1065
+ readonly afterCustodyId?: string;
1066
+ readonly limit?: number;
1067
+ }) => Effect.Effect<CustomerCustodyInventoryPage, BillingError>;
1068
+ /** Only positive provider deletion retires this exact customer; history is retained. */
1069
+ readonly retire: (scope: CustomerScope, snapshot: ProviderCustomerSnapshot) => Effect.Effect<CustomerRecord | null, BillingError>;
771
1070
  }
772
1071
 
1072
+ export declare const dataStoreBillingCloseStore: (store: DataStore) => BillingCloseStore;
1073
+
1074
+ export declare const dataStoreCheckoutOperationStore: (store: DataStore) => CheckoutOperationStore;
1075
+
773
1076
  export declare const dataStoreDunningNoticeStore: (store: DataStore) => DunningNoticeStore;
774
1077
 
1078
+ export declare const dataStoreInvoiceHistoryJournal: (store: DataStore) => InvoiceHistoryJournal;
1079
+
1080
+ /** Traversal hints never qualify an invoice or own its execution. Native journal,
1081
+ * custody and work claims remain authoritative; every update is revision fenced. */
1082
+ export declare const dataStoreInvoiceHistoryProgressStore: (store: DataStore) => InvoiceHistoryProgressStore;
1083
+
1084
+ /** Per-reference results live in this native table, not the expiring Framework
1085
+ * receipt. Receipt ownership and the business acknowledgement commit together. */
1086
+ export declare const dataStoreInvoiceHistoryWorkStore: (store: DataStore) => InvoiceHistoryWorkStore;
1087
+
775
1088
  export declare const dataStoreStores: (store: DataStore) => BillingStores;
776
1089
 
777
1090
  /**
@@ -1092,6 +1405,184 @@ export declare const formEncode: (params: Readonly<Record<string, unknown>>) =>
1092
1405
  /** When the grace for an episode runs out. */
1093
1406
  export declare const graceEndOf: (pastDueSince: Date, graceHours: number) => Date;
1094
1407
 
1408
+ /** Trusted operator/server utility. The caller authorizes tenant access and
1409
+ * supplies a root DataStore, outside any application transaction. It deliberately
1410
+ * uses integrity reads, explicit tenant predicates and all deletion/expiry scopes.
1411
+ * No provider call, customer binding, account reservation or native write occurs.
1412
+ * A custom store without integrityQuery is unsupported; query is no fallback. */
1413
+ export declare const inspectBillingCustody: (store: DataStore, input: BillingCustodyInventoryOptions) => Effect.Effect<BillingCustodyInventory, BillingError>;
1414
+
1415
+ /** Immutable native ownership of one provider invoice, not payment authority. */
1416
+ export declare interface InvoiceBinding extends ProviderAccountIdentity {
1417
+ readonly tenantId: string;
1418
+ readonly provider: string;
1419
+ readonly providerCustomerId: string;
1420
+ readonly providerInvoiceId: string;
1421
+ }
1422
+
1423
+ export declare interface InvoiceHistoryAccountProgress extends CustomerScope {
1424
+ readonly id: string;
1425
+ readonly revision: number;
1426
+ readonly afterCustodyId: string | null;
1427
+ readonly updatedAt: Date;
1428
+ }
1429
+
1430
+ export declare interface InvoiceHistoryCustomerProgress extends InvoiceHistoryDispatchPosition {
1431
+ readonly id: string;
1432
+ readonly revision: number;
1433
+ readonly owner: CustomerRecord;
1434
+ readonly updatedAt: Date;
1435
+ }
1436
+
1437
+ export declare interface InvoiceHistoryDispatchPosition {
1438
+ readonly walkId: string | null;
1439
+ readonly cursors: Readonly<Record<InvoiceHistoryWorkLane, string | null>>;
1440
+ }
1441
+
1442
+ export declare interface InvoiceHistoryJournal {
1443
+ readonly begin: (owner: CustomerRecord) => Effect.Effect<InvoiceHistoryWalk, BillingError>;
1444
+ readonly read: (owner: CustomerRecord) => Effect.Effect<InvoiceHistoryWalk | null, BillingError>;
1445
+ /** Null means a stale checkpoint with no committed change. Discard that
1446
+ * offered page and reread both native checkpoint and provider page. */
1447
+ readonly commitPage: (expected: InvoiceHistoryWalk, page: ProviderInvoiceHistoryPage) => Effect.Effect<InvoiceHistoryWalk | null, BillingError>;
1448
+ /** Retain every old page/reference when a cursor is lost or a new head is scanned. */
1449
+ readonly restart: (expected: InvoiceHistoryWalk) => Effect.Effect<InvoiceHistoryWalk | null, BillingError>;
1450
+ readonly reference: (owner: CustomerRecord, referenceId: string) => Effect.Effect<InvoiceHistoryJournalReference | null, BillingError>;
1451
+ readonly references: (owner: CustomerRecord, input?: {
1452
+ readonly afterReferenceId?: string;
1453
+ readonly limit?: number;
1454
+ }) => Effect.Effect<InvoiceHistoryReferencePage, BillingError>;
1455
+ }
1456
+
1457
+ export declare interface InvoiceHistoryJournalOwner extends CustomerBinding {
1458
+ readonly custodyId: string;
1459
+ }
1460
+
1461
+ export declare interface InvoiceHistoryJournalReference extends InvoiceHistoryJournalOwner {
1462
+ readonly id: string;
1463
+ readonly walkId: string;
1464
+ readonly round: number;
1465
+ readonly pageId: string;
1466
+ readonly pageOrdinal: number;
1467
+ readonly position: number;
1468
+ readonly providerInvoiceId: string;
1469
+ readonly discoveryStatus: ProviderInvoiceHistoryEntry['status'];
1470
+ readonly providerCreatedAt: Date;
1471
+ }
1472
+
1473
+ export declare interface InvoiceHistoryPassResult {
1474
+ readonly kind: 'idle' | 'advanced' | 'contended';
1475
+ readonly customerCustodyId: string | null;
1476
+ readonly admittedPages: number;
1477
+ readonly observed: number;
1478
+ readonly deferred: number;
1479
+ readonly busy: number;
1480
+ readonly unconfirmed: number;
1481
+ }
1482
+
1483
+ export declare interface InvoiceHistoryProgressStore {
1484
+ readonly account: (scope: CustomerScope, now: Date) => Effect.Effect<InvoiceHistoryAccountProgress, BillingError>;
1485
+ readonly advanceAccount: (expected: InvoiceHistoryAccountProgress, afterCustodyId: string | null, now: Date) => Effect.Effect<InvoiceHistoryAccountProgress | null, BillingError>;
1486
+ readonly customer: (owner: CustomerRecord, now: Date) => Effect.Effect<InvoiceHistoryCustomerProgress, BillingError>;
1487
+ readonly advanceCustomer: (expected: InvoiceHistoryCustomerProgress, next: InvoiceHistoryDispatchPosition, now: Date) => Effect.Effect<InvoiceHistoryCustomerProgress | null, BillingError>;
1488
+ }
1489
+
1490
+ export declare interface InvoiceHistoryReferencePage {
1491
+ readonly references: ReadonlyArray<InvoiceHistoryJournalReference>;
1492
+ readonly nextReferenceId: string | null;
1493
+ /** This read's admitted-page bound, never a global completion receipt. */
1494
+ readonly watermarkPageOrdinal: number;
1495
+ readonly walkRevision: number;
1496
+ }
1497
+
1498
+ export declare interface InvoiceHistoryReferenceResult {
1499
+ readonly kind: 'observed' | 'deferred' | 'busy';
1500
+ readonly work: InvoiceHistoryWork;
1501
+ }
1502
+
1503
+ export declare interface InvoiceHistoryWalk extends InvoiceHistoryJournalOwner {
1504
+ readonly id: string;
1505
+ readonly revision: number;
1506
+ readonly round: number;
1507
+ /** Monotonic across rounds; restarting never renumbers admitted pages. */
1508
+ readonly pageOrdinal: number;
1509
+ readonly cursor: ProviderInvoiceHistoryCursor | null;
1510
+ /** Provider enumeration ended, not processing or financial completion. */
1511
+ readonly exhausted: boolean;
1512
+ }
1513
+
1514
+ export declare interface InvoiceHistoryWork extends InvoiceHistoryJournalOwner {
1515
+ readonly id: string;
1516
+ readonly walkId: string;
1517
+ readonly referenceId: string;
1518
+ readonly providerInvoiceId: string;
1519
+ readonly revision: number;
1520
+ /** Logical attempts and acquired execution identities survive restarts. */
1521
+ readonly attempt: number;
1522
+ readonly executions: number;
1523
+ readonly state: 'queued' | 'working' | 'retry' | 'deferred' | 'observed';
1524
+ readonly activeClaimId: string | null;
1525
+ readonly nextAttemptAt: Date;
1526
+ readonly lastOutcome: 'unconfirmed' | 'open' | 'observed';
1527
+ /** An observed native version, never a payment or entitlement receipt. */
1528
+ readonly observationId: string | null;
1529
+ readonly observationRevision: number | null;
1530
+ readonly updatedAt: Date;
1531
+ }
1532
+
1533
+ export declare type InvoiceHistoryWorkAcquisition = {
1534
+ readonly kind: 'claimed';
1535
+ readonly work: InvoiceHistoryWork;
1536
+ readonly attempt: InvoiceHistoryWorkAttempt;
1537
+ } | {
1538
+ readonly kind: 'busy' | 'observed';
1539
+ readonly work: InvoiceHistoryWork;
1540
+ };
1541
+
1542
+ export declare interface InvoiceHistoryWorkAttempt {
1543
+ readonly work: InvoiceHistoryWork;
1544
+ readonly claim: IdempotencyClaim;
1545
+ }
1546
+
1547
+ export declare interface InvoiceHistoryWorkCursor {
1548
+ readonly lane: InvoiceHistoryWorkLane;
1549
+ readonly walkId: string;
1550
+ readonly afterId: string;
1551
+ }
1552
+
1553
+ export declare type InvoiceHistoryWorkLane = 'unprepared' | 'ready' | 'working';
1554
+
1555
+ export declare interface InvoiceHistoryWorkPage {
1556
+ readonly entries: ReadonlyArray<{
1557
+ readonly reference: InvoiceHistoryJournalReference;
1558
+ readonly work: InvoiceHistoryWork | null;
1559
+ }>;
1560
+ /** Read pagination only; null never establishes global/financial completion. */
1561
+ readonly nextCursor: InvoiceHistoryWorkCursor | null;
1562
+ readonly walkRevision: number;
1563
+ }
1564
+
1565
+ export declare interface InvoiceHistoryWorkStore {
1566
+ /** Independent bounded lanes keep new references separate from due retries
1567
+ * and claimed executions. Selection grants no execution ownership. */
1568
+ readonly pending: (owner: CustomerRecord, lane: InvoiceHistoryWorkLane, now: Date, input?: {
1569
+ readonly limit?: number;
1570
+ readonly cursor?: InvoiceHistoryWorkCursor;
1571
+ }) => Effect.Effect<InvoiceHistoryWorkPage, BillingError>;
1572
+ readonly claim: (owner: CustomerRecord, referenceId: string, now: Date) => Effect.Effect<InvoiceHistoryWorkAcquisition, BillingError>;
1573
+ readonly observed: (owner: CustomerRecord, attempt: InvoiceHistoryWorkAttempt, observation: InvoiceObservation, now: Date) => Effect.Effect<InvoiceHistoryWork | null, BillingError>;
1574
+ readonly failed: (owner: CustomerRecord, attempt: InvoiceHistoryWorkAttempt, now: Date) => Effect.Effect<InvoiceHistoryWork | null, BillingError>;
1575
+ /** Uses the shared Framework heartbeat. Stopping never proves SDK work stopped. */
1576
+ readonly hold: (attempt: InvoiceHistoryWorkAttempt, now?: () => number) => () => void;
1577
+ }
1578
+
1579
+ export declare interface InvoiceObservation extends InvoiceBinding {
1580
+ readonly id: string;
1581
+ readonly identityKey: string;
1582
+ readonly revision: number;
1583
+ readonly snapshot: ProviderInvoiceSnapshot;
1584
+ }
1585
+
1095
1586
  declare interface InvoiceRecord {
1096
1587
  readonly providerInvoiceId: string;
1097
1588
  readonly amountMinor: number;
@@ -1103,28 +1594,12 @@ declare interface InvoiceRecord {
1103
1594
  readonly pdfUrl: string | null;
1104
1595
  }
1105
1596
 
1106
- declare interface InvoiceRecord_2 {
1107
- readonly tenantId: string;
1108
- readonly provider: string;
1109
- readonly providerInvoiceId: string;
1110
- readonly amountMinor: number;
1111
- readonly currency: string;
1112
- /** 'paid' | 'open' | 'uncollectible' | 'void'. */
1113
- readonly status: string;
1114
- /** Provider-event timestamp this `status` came from; null when undated. */
1115
- readonly statusEventAt: Date | null;
1116
- }
1117
-
1118
1597
  export declare interface InvoiceStore {
1119
- /**
1120
- * Upsert by `providerInvoiceId` (idempotent under webhook replay), with the
1121
- * same stale-event guard the subscription row carries: a late-delivered
1122
- * `payment_failed` must not flip an invoice that has since been paid back to
1123
- * `'open'`, which is what a naive last-write-wins mirror does under Stripe's
1124
- * unordered delivery.
1125
- */
1126
- readonly upsert: (invoice: InvoiceRecord_2) => Effect.Effect<void, BillingError>;
1127
- readonly listByTenant: (tenantId: string) => Effect.Effect<ReadonlyArray<InvoiceRecord_2>, BillingError>;
1598
+ /** Read the revision before a provider observation, without reserving unproven ownership. */
1599
+ readonly get: (binding: InvoiceBinding) => Effect.Effect<InvoiceObservation | null, BillingError>;
1600
+ /** Insert only if absent, or compare the previously read revision. False requires
1601
+ * another native AND provider read; never retry a captured payload alone. */
1602
+ readonly complete: (binding: InvoiceBinding, expected: InvoiceObservation | null, snapshot: ProviderInvoiceSnapshot) => Effect.Effect<boolean, BillingError>;
1128
1603
  }
1129
1604
 
1130
1605
  /** Is this a real deployment boot? Only an explicitly dev-like NODE_ENV is not. */
@@ -1143,6 +1618,10 @@ export declare const isStaleStatusEvent: (existing: Date | null, incoming: Date
1143
1618
  */
1144
1619
  export declare type LockoutMode = 'hard' | 'soft';
1145
1620
 
1621
+ export declare const memoryBillingCloseStore: () => BillingCloseStore;
1622
+
1623
+ export declare const memoryCheckoutOperationStore: () => CheckoutOperationStore;
1624
+
1146
1625
  export declare const memoryDunningNoticeStore: () => DunningNoticeStore;
1147
1626
 
1148
1627
  export declare const memoryStores: () => BillingStores;
@@ -1150,6 +1629,15 @@ export declare const memoryStores: () => BillingStores;
1150
1629
  /** Replace (or add) `NAME=value` lines; every other line is kept as it was. */
1151
1630
  export declare const mergeEnvFile: (text: string, values: Readonly<Record<string, string>>) => string;
1152
1631
 
1632
+ export declare interface MeteredUsageEvent {
1633
+ readonly tenantId: string;
1634
+ readonly entitlementKey: string;
1635
+ readonly quantity: number;
1636
+ readonly occurredAt: Date;
1637
+ /** Stable tenant-scoped occurrence identity, reused unchanged on every retry. */
1638
+ readonly idempotencyKey: string;
1639
+ }
1640
+
1153
1641
  /** `meterKey → source`. The meterKey is the entitlement/usage key reported to
1154
1642
  * the provider (`billing.reportUsage(tenant, meterKey, qty)`). */
1155
1643
  declare type MeteringConfig = Readonly<Record<string, MeterSource>>;
@@ -1179,11 +1667,19 @@ declare type MeterSource =
1179
1667
  * In-memory provider with deterministic outputs. Checkout/portal URLs are
1180
1668
  * stable functions of the input so tests can assert on them; usage pushes
1181
1669
  * are recorded on `pushed` for assertion. `normalizeEvent` accepts the
1182
- * already-normalized `BillingEvent` shape directly (the test feeds events
1183
- * straight through) OR a `{ type, data }` envelope mirroring the Stripe
1184
- * mapping, so the same webhook path exercises the mock end-to-end.
1670
+ * verified `BillingProviderEvent` shape directly (the test feeds events
1671
+ * straight through). Invoice triggers carry providerCustomerId; an event never seeds provider state.
1185
1672
  */
1186
1673
  export declare interface MockProvider extends BillingProvider {
1674
+ readonly fetchAccountIdentity: NonNullable<BillingProvider['fetchAccountIdentity']>;
1675
+ readonly fetchCustomerSnapshot: NonNullable<BillingProvider['fetchCustomerSnapshot']>;
1676
+ /** Independent provider state; an event or invoice is not customer evidence. */
1677
+ readonly setCustomerSnapshot: (snapshot: ProviderCustomerSnapshot) => void;
1678
+ /** Supply actual simulated provider state; webhook payloads never create this evidence. */
1679
+ readonly setInvoiceSnapshot: (snapshot: ProviderInvoiceSnapshot) => void;
1680
+ /** Independent provider history, including drafts and original creation dates.
1681
+ * Detailed snapshots and event payloads do not manufacture this history. */
1682
+ readonly setInvoiceHistory: (entries: ReadonlyArray<ProviderInvoiceHistoryEntry>) => void;
1187
1683
  /** Recorded usage pushes — assertion surface for tests. */
1188
1684
  readonly pushed: ReadonlyArray<UsagePush>;
1189
1685
  /** Recorded subscription updates. The point of asserting on these is that
@@ -1232,6 +1728,9 @@ export declare interface MockProvider extends BillingProvider {
1232
1728
  export declare const mockProvider: (options?: {
1233
1729
  readonly baseUrl?: string;
1234
1730
  readonly supportsMeteredUsage?: boolean;
1731
+ readonly now?: () => Date;
1732
+ /** Explicit stable account for controlled fixtures; otherwise this mock instance owns a fresh account. */
1733
+ readonly accountId?: string;
1235
1734
  }) => MockProvider;
1236
1735
 
1237
1736
  /** A monetary amount: integer minor units + ISO-4217 currency. */
@@ -1262,10 +1761,9 @@ declare interface MountBillingWebhookOptions extends BillingWebhookOptions {
1262
1761
  * not model (the handler no-ops and still answers 200, which is what stops
1263
1762
  * Stripe retrying an event we will never care about).
1264
1763
  */
1265
- export declare const normalizeStripeEvent: (event: default_2.Event, resolvePrice?: (priceId: string) => string | null) => BillingEvent | null;
1764
+ export declare const normalizeStripeEvent: (event: default_2.Event, resolvePrice?: (priceId: string) => string | null) => BillingProviderEvent | null;
1266
1765
 
1267
- /** Per-event side effect, run AFTER the row is updated. */
1268
- export declare type OnEventMap = Readonly<Record<string, (event: BillingEvent) => Effect.Effect<void, unknown>>>;
1766
+ export declare type OnEventMap = Readonly<Record<string, (event: BillingEvent, delivery?: BillingEventDeliveryContext) => Effect.Effect<void, unknown>>>;
1269
1767
 
1270
1768
  /** Findings the account still differs by (after `apply`: what it could not fix). */
1271
1769
  export declare const openFindings: (findings: ProvisionResult["findings"]) => (CatalogFinding & {
@@ -1348,6 +1846,88 @@ export declare const portalParams: (portal: StripeCatalog["portal"], env: Stripe
1348
1846
  readonly priceId: string;
1349
1847
  }>) => Record<string, unknown>;
1350
1848
 
1849
+ /** Provider-observed account/mode, not a configured key prefix or tenant hint. */
1850
+ export declare interface ProviderAccountIdentity {
1851
+ readonly providerAccountId: string;
1852
+ readonly livemode: boolean;
1853
+ }
1854
+
1855
+ /** A positive customer response in an explicitly expected provider namespace.
1856
+ * observedAt is local observation time, never provider-clock settlement evidence. */
1857
+ export declare interface ProviderCustomerSnapshot {
1858
+ readonly account: ProviderAccountIdentity;
1859
+ readonly providerCustomerId: string;
1860
+ readonly deleted: boolean;
1861
+ readonly observedAt: Date;
1862
+ }
1863
+
1864
+ /** Explicit provider namespace/customer binding; not a bearer authorization. */
1865
+ export declare interface ProviderInvoiceHistoryCursor extends ProviderAccountIdentity {
1866
+ readonly providerCustomerId: string;
1867
+ readonly afterInvoiceId: string;
1868
+ }
1869
+
1870
+ /** One history reference, not a complete invoice observation or payment proof. */
1871
+ export declare interface ProviderInvoiceHistoryEntry extends InvoiceRecord {
1872
+ readonly providerCustomerId: string;
1873
+ readonly livemode: boolean;
1874
+ readonly status: 'draft' | 'open' | 'paid' | 'uncollectible' | 'void';
1875
+ readonly createdAt: Date;
1876
+ }
1877
+
1878
+ export declare interface ProviderInvoiceHistoryInput {
1879
+ readonly account: ProviderAccountIdentity;
1880
+ readonly providerCustomerId: string;
1881
+ readonly cursor?: ProviderInvoiceHistoryCursor;
1882
+ /** 1..100; defaults to 100. Invalid values fail rather than clamp. */
1883
+ readonly limit?: number;
1884
+ }
1885
+
1886
+ export declare interface ProviderInvoiceHistoryPage {
1887
+ readonly account: ProviderAccountIdentity;
1888
+ readonly providerCustomerId: string;
1889
+ readonly invoices: ReadonlyArray<ProviderInvoiceHistoryEntry>;
1890
+ readonly nextCursor: ProviderInvoiceHistoryCursor | null;
1891
+ /** Local observation time, not provider-clock or financial coverage evidence. */
1892
+ readonly observedAt: Date;
1893
+ }
1894
+
1895
+ export declare interface ProviderInvoiceLineSnapshot {
1896
+ readonly providerLineId: string;
1897
+ readonly providerSubscriptionId: string | null;
1898
+ readonly providerSubscriptionItemId: string | null;
1899
+ readonly providerPriceId: string | null;
1900
+ readonly amountMinor: number;
1901
+ readonly currency: string;
1902
+ /** Exact provider decimal; absent precision must never be replaced by a truncated integer. */
1903
+ readonly quantityDecimal: string | null;
1904
+ /** Provider period bounds are inclusive; they are not paid-through authority. */
1905
+ readonly periodStart: Date;
1906
+ readonly periodEnd: Date;
1907
+ /** Null means the provider did not identify the line's origin. */
1908
+ readonly proration: boolean | null;
1909
+ }
1910
+
1911
+ /** A complete provider observation, not an entitlement or a payment receipt. */
1912
+ export declare interface ProviderInvoiceSnapshot {
1913
+ readonly providerInvoiceId: string;
1914
+ readonly providerCustomerId: string;
1915
+ readonly providerSubscriptionId: string | null;
1916
+ readonly providerRevisionId: string | null;
1917
+ readonly status: 'open' | 'paid' | 'uncollectible' | 'void';
1918
+ readonly livemode: boolean;
1919
+ readonly currency: string;
1920
+ readonly amountDueMinor: number;
1921
+ readonly amountPaidMinor: number;
1922
+ readonly amountRemainingMinor: number;
1923
+ readonly finalizedAt: Date;
1924
+ readonly paidAt: Date | null;
1925
+ /** Local read completion time, never a provider clock or entitlement deadline. */
1926
+ readonly observedAt: Date;
1927
+ /** All pages. A bounded or failed read must fail instead of returning a prefix. */
1928
+ readonly lines: ReadonlyArray<ProviderInvoiceLineSnapshot>;
1929
+ }
1930
+
1351
1931
  /** What `reconcileFromProvider` found and corrected. */
1352
1932
  export declare interface ProviderReconcileResult {
1353
1933
  /** Subscriptions compared (provider-listed plus local rows it did not list). */
@@ -1523,6 +2103,11 @@ export declare const runStripeProvision: (catalog: StripeCatalog, argv: Readonly
1523
2103
  readonly vars?: NodeJS.ProcessEnv;
1524
2104
  }) => Promise<number>;
1525
2105
 
2106
+ /** The row that speaks for a tenant: a live one first, then the newest. */
2107
+ export declare const selectCurrentSubscription: <T extends {
2108
+ readonly status: string;
2109
+ }>(rows: ReadonlyArray<T>) => T | null;
2110
+
1526
2111
  /** What only a human can settle before an environment sells. */
1527
2112
  export declare const sellingReadiness: (input: {
1528
2113
  readonly catalog: StripeCatalog;
@@ -1532,6 +2117,10 @@ export declare const sellingReadiness: (input: {
1532
2117
  readonly activeRegistrations: ReadonlyArray<string>;
1533
2118
  }) => ReadonlyArray<CatalogFinding>;
1534
2119
 
2120
+ export declare interface StoredUsageEvent extends MeteredUsageEvent {
2121
+ readonly period: string;
2122
+ }
2123
+
1535
2124
  /**
1536
2125
  * A mutable holder so `bindDataStore` can swap the stores AFTER the service
1537
2126
  * layer is built. The service reads `holder.stores` on every call rather
@@ -1822,6 +2411,12 @@ declare interface SubscriptionChangeResult {
1822
2411
  readonly currentPeriodEnd: Date | null;
1823
2412
  }
1824
2413
 
2414
+ export declare interface SubscriptionCloseOperation {
2415
+ readonly id: string;
2416
+ /** Provider-clock deadline captured after final usage dispatch. */
2417
+ readonly cancelAt: Date;
2418
+ }
2419
+
1825
2420
  /**
1826
2421
  * Raised when dunning has locked a tenant out — the grace period after a
1827
2422
  * provider-confirmed past-due has expired. Distinct from
@@ -1910,12 +2505,20 @@ declare interface SubscriptionUpdateInput {
1910
2505
  }
1911
2506
 
1912
2507
  /** An event the provider failed to deliver, reduced to what a catch-up needs. */
1913
- declare interface UndeliveredEvent {
2508
+ export declare interface UndeliveredEvent {
1914
2509
  readonly id: string;
2510
+ readonly event: BillingProviderEvent | null;
2511
+ /** A recognized provider event could not be normalized; retry/report it, never fall back to discovery. */
2512
+ readonly rejected?: true;
1915
2513
  readonly providerCustomerId: string | null;
1916
2514
  readonly createdAt: Date | null;
1917
2515
  }
1918
2516
 
2517
+ export declare interface UndeliveredEventPage {
2518
+ readonly events: ReadonlyArray<UndeliveredEvent>;
2519
+ readonly nextCursor: string | null;
2520
+ }
2521
+
1919
2522
  export declare interface UsagePush {
1920
2523
  readonly tenantId: string;
1921
2524
  readonly entitlementKey: string;
@@ -1942,6 +2545,7 @@ declare interface UsageRow {
1942
2545
  }
1943
2546
 
1944
2547
  export declare interface UsageStore {
2548
+ readonly recordEvent: (event: StoredUsageEvent) => Effect.Effect<void, BillingError>;
1945
2549
  /** Current used count for `(tenant, key, period)`; 0 when no row. */
1946
2550
  readonly used: (tenantId: string, key: string, period: string) => Effect.Effect<number, BillingError>;
1947
2551
  /**