@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.js CHANGED
@@ -1,32 +1,43 @@
1
1
  import { BillingError as e, BillingService as t, DEFAULT_PLAN as n, EntitlementExceeded as r, SubscriptionLocked as i } from "./types.js";
2
- import { buildEnforceInterceptor as a, evaluateEntitlement as o, requireEntitled as s, requireEntitlement as c } from "./entitlement.js";
3
- import { BILLING_CUSTOMERS_TABLE as l, BILLING_INVOICES_TABLE as u, BILLING_SUBSCRIPTIONS_TABLE as d, BILLING_USAGE_TABLE as f, billingCustomersTable as p, billingInvoicesTable as m, billingSubscriptionsTable as h, billingTables as g, billingUsageTable as _ } from "./table.js";
4
- import { BILLING_DUNNING_NOTICES_TABLE as ee, DEFAULT_DUNNING_STEPS as te, DEFAULT_GRACE_HOURS as ne, DEFAULT_LOCKOUT as re, DUNNING_LOCK_STEP_ID as v, billingDunningNoticesTable as y, dataStoreDunningNoticeStore as ie, defaultDunningEmail as b, dueSteps as x, dunningMailNotifier as S, episodeKeyOf as C, evaluateBillingEntitlement as w, graceEndOf as T, memoryDunningNoticeStore as E, resolveDunningConfig as D } from "./dunning.js";
5
- import { dataStoreStores as ae, isStaleStatusEvent as O, memoryStores as k } from "./stores.js";
6
- import { billingServiceLayer as A, buildBillingService as j, periodKey as M } from "./service.js";
7
- import { buildMeteringChangeTap as oe, buildMeteringInterceptor as se, hasCdcMeters as ce, runAutopilotFlush as le, validateMeteringConfig as ue } from "./metering.js";
8
- import { buildPlanRegistry as N, emptyPlanRegistry as P } from "./plans.js";
9
- import { BILLING_FLUSH_CLAIMS_TABLE as F, billingFlushClaimsTable as I, claimFlushWindow as L, flushWindowKey as R } from "./flushClaim.js";
10
- import { STRIPE_API_VERSION as z, STRIPE_DASHBOARD_REMINDERS as B, diffMeter as V, diffPortal as H, diffPrice as U, diffProduct as W, diffTaxSettings as G, diffWebhook as K, portalParams as q, sellingReadiness as J } from "./catalog.js";
11
- import { StripeRestError as de, formEncode as fe, redactStripeKeys as pe, stripeRest as me } from "./stripeRest.js";
12
- import { STRIPE_WEBHOOK_EVENTS as he, normalizeStripeEvent as ge, stripeProvider as _e } from "./stripeProvider.js";
13
- import { assertBillingProductionReady as ve, billingEgressOf as ye, isProductionBoot as be, mockProvider as xe, resolveProvider as Se } from "./providers.js";
14
- import { BILLING_WEBHOOK_ID as Ce, BILLING_WEBHOOK_PATH as Y, billingWebhookDescriptor as we, mountBillingWebhook as Te } from "./webhook.js";
15
- import { billingRpcClientImports as Ee, cancelSubscriptionDescriptor as De, changePlanDescriptor as Oe, changeSeatsDescriptor as ke, entitlementStatusDescriptor as Ae, invoicesDescriptor as je, portalUrlDescriptor as Me, previewChangeDescriptor as Ne, reportUsageDescriptor as Pe, resumeSubscriptionDescriptor as Fe, startCheckoutDescriptor as Ie, subscriptionDescriptor as Le, syncDescriptor as Re } from "./rpc.js";
16
- import { envOutRefusal as ze, mergeEnvFile as Be, openFindings as Ve, parseProvisionArgs as He, provisionStripeCatalog as Ue, resolveProvisionKey as We, runStripeProvision as Ge } from "./provision.js";
17
- import { Effect as X } from "effect";
18
- import { pluginEnv as Ke } from "@voltro/env";
19
- import { composeRpcInterceptors as qe, definePlugin as Je, pluginInstanceName as Ye, recheckGuardsEffect as Xe } from "@voltro/protocol";
20
- import { registerRetention as Z, retentionTtlMsFromEnv as Q } from "@voltro/database";
21
- import { replicaId as Ze } from "@voltro/protocol/identity";
22
- //#region src/index.ts
23
- var $ = (e) => e === null ? null : {
2
+ import { makeBillingEventDelivery as a } from "./eventDelivery.js";
3
+ import { BILLING_CUSTODY_INVENTORY_TABLES as o, inspectBillingCustody as s } from "./custodyInventory.js";
4
+ import { BILLING_CUSTOMER_ACCOUNTS_TABLE as c, BILLING_CUSTOMER_CUSTODY_TABLE as l, billingCustomerAccountsTable as u, billingCustomerCustodyTable as ee } from "./customerStore.js";
5
+ import { buildEnforceInterceptor as te, evaluateEntitlement as d, requireEntitled as f, requireEntitlement as ne } from "./entitlement.js";
6
+ import { BILLING_INVOICE_HISTORY_PAGES_TABLE as p, BILLING_INVOICE_HISTORY_REFERENCES_TABLE as m, BILLING_INVOICE_HISTORY_WALKS_TABLE as h, billingInvoiceHistoryPagesTable as g, billingInvoiceHistoryReferencesTable as _, billingInvoiceHistoryWalksTable as v, dataStoreInvoiceHistoryJournal as y } from "./invoiceHistoryJournal.js";
7
+ import { BILLING_INVOICE_OBSERVATIONS_TABLE as re, billingInvoiceObservationsTable as ie, billingRetainedInvoiceObservationsTable as ae } from "./invoiceStore.js";
8
+ import { BILLING_INVOICE_HISTORY_WORK_TABLE as oe, billingInvoiceHistoryWorkTable as se, dataStoreInvoiceHistoryWorkStore as b } from "./invoiceHistoryWork.js";
9
+ import { BILLING_INVOICE_HISTORY_DISPATCH_TABLE as ce, BILLING_INVOICE_HISTORY_SCAN_ACCOUNTS_TABLE as x, billingInvoiceHistoryDispatchTable as S, billingInvoiceHistoryScanAccountsTable as C, dataStoreInvoiceHistoryProgressStore as w } from "./invoiceHistoryProgress.js";
10
+ import { BILLING_USAGE_EVENTS_TABLE as T, billingUsageEventsTable as E } from "./usageEvents.js";
11
+ import { BILLING_CHECKOUT_OPERATIONS_TABLE as D, billingCheckoutOperationsTable as O, dataStoreCheckoutOperationStore as k, memoryCheckoutOperationStore as A } from "./checkoutStore.js";
12
+ import { BILLING_CLOSE_OPERATIONS_TABLE as j, billingCloseOperationsTable as M, dataStoreBillingCloseStore as N, memoryBillingCloseStore as P } from "./closeStore.js";
13
+ import { BILLING_DUNNING_NOTICES_TABLE as le, DEFAULT_DUNNING_STEPS as F, DEFAULT_GRACE_HOURS as I, DEFAULT_LOCKOUT as L, DUNNING_LOCK_STEP_ID as R, billingDunningNoticesTable as z, dataStoreDunningNoticeStore as B, defaultDunningEmail as ue, dueSteps as de, dunningMailNotifier as fe, episodeKeyOf as pe, evaluateBillingEntitlement as me, graceEndOf as he, memoryDunningNoticeStore as ge, resolveDunningConfig as V } from "./dunning.js";
14
+ import { BILLING_CUSTOMERS_TABLE as _e, BILLING_INVOICES_TABLE as ve, BILLING_SUBSCRIPTIONS_TABLE as H, BILLING_USAGE_TABLE as U, billingCustomersTable as ye, billingInvoicesTable as be, billingSubscriptionsTable as xe, billingTables as W, billingUsageTable as Se } from "./table.js";
15
+ import { dataStoreStores as G, isStaleStatusEvent as Ce, memoryStores as we, selectCurrentSubscription as Te } from "./stores.js";
16
+ import { billingServiceLayer as K, buildBillingService as Ee, periodKey as De } from "./service.js";
17
+ import { buildMeteringChangeTap as Oe, buildMeteringInterceptor as ke, hasCdcMeters as Ae, runAutopilotFlush as je, validateMeteringConfig as Me } from "./metering.js";
18
+ import { buildPlanRegistry as Ne, emptyPlanRegistry as Pe } from "./plans.js";
19
+ import { BILLING_FLUSH_CLAIMS_TABLE as Fe, billingFlushClaimsTable as Ie, claimFlushWindow as Le, flushWindowKey as Re } from "./flushClaim.js";
20
+ import { STRIPE_API_VERSION as ze, STRIPE_DASHBOARD_REMINDERS as Be, diffMeter as Ve, diffPortal as He, diffPrice as Ue, diffProduct as We, diffTaxSettings as Ge, diffWebhook as Ke, portalParams as qe, sellingReadiness as Je } from "./catalog.js";
21
+ import { StripeRestError as Ye, formEncode as Xe, redactStripeKeys as Ze, stripeRest as Qe } from "./stripeRest.js";
22
+ import { STRIPE_WEBHOOK_EVENTS as $e, normalizeStripeEvent as et, stripeProvider as tt } from "./stripeProvider.js";
23
+ import { assertBillingProductionReady as nt, billingEgressOf as rt, isProductionBoot as it, mockProvider as at, resolveProvider as ot } from "./providers.js";
24
+ import { BILLING_WEBHOOK_ID as st, BILLING_WEBHOOK_PATH as q, billingWebhookDescriptor as ct, mountBillingWebhook as lt } from "./webhook.js";
25
+ import { billingRpcClientImports as ut, cancelSubscriptionDescriptor as dt, changePlanDescriptor as ft, changeSeatsDescriptor as pt, entitlementStatusDescriptor as mt, invoicesDescriptor as ht, portalUrlDescriptor as gt, previewChangeDescriptor as _t, reportUsageDescriptor as vt, resumeSubscriptionDescriptor as yt, startCheckoutDescriptor as J, subscriptionDescriptor as bt, syncDescriptor as xt } from "./rpc.js";
26
+ import { envOutRefusal as St, mergeEnvFile as Ct, openFindings as wt, parseProvisionArgs as Tt, provisionStripeCatalog as Et, resolveProvisionKey as Dt, runStripeProvision as Ot } from "./provision.js";
27
+ import { Effect as Y } from "effect";
28
+ import { pluginEnv as kt } from "@voltro/env";
29
+ import { composeRpcInterceptors as At, definePlugin as jt, pluginInstanceName as Mt, recheckGuardsEffect as Nt } from "@voltro/protocol";
30
+ import { and as Pt, eq as Ft, registerRetention as X, retentionTtlMsFromEnv as Z } from "@voltro/database";
31
+ import { replicaId as It } from "@voltro/protocol/identity";
32
+ import { dataStoreIdempotencyStore as Lt } from "@voltro/runtime";
33
+ // src/index.ts
34
+ var Q = (e) => e === null ? null : {
24
35
  plan: e.plan,
25
36
  status: e.status,
26
37
  quantity: e.quantity,
27
38
  currentPeriodEnd: e.currentPeriodEnd ? e.currentPeriodEnd.toISOString() : null,
28
39
  cancelAt: e.cancelAt ? e.cancelAt.toISOString() : null
29
- }, Qe = Ke([{
40
+ }, Rt = kt([{
30
41
  name: "STRIPE_SECRET_KEY",
31
42
  required: !1,
32
43
  secret: !0,
@@ -36,41 +47,42 @@ var $ = (e) => e === null ? null : {
36
47
  required: !1,
37
48
  secret: !0,
38
49
  description: "Stripe webhook signing secret (whsec_…) for verifying webhook payloads."
39
- }]), $e = "@voltro/plugin-billing", et = (t = {}) => {
40
- let n = process.env, o = N(t.plans ?? {}), s = Se({
50
+ }]), $ = "@voltro/plugin-billing", zt = (t = {}) => {
51
+ let n = process.env, o = Ne(t.plans ?? {}), s = ot({
41
52
  ...t.provider === void 0 ? {} : { provider: t.provider },
42
53
  ...t.apiKey === void 0 ? {} : { apiKey: t.apiKey },
43
54
  planForPriceId: o.planForPriceId,
44
55
  env: n
45
- }), c = t.webhookSecret ?? Qe.read("STRIPE_WEBHOOK_SECRET") ?? null;
46
- ve({
56
+ }), u = t.webhookSecret ?? Rt.read("STRIPE_WEBHOOK_SECRET") ?? null;
57
+ nt({
47
58
  env: n,
48
59
  providerOption: t.provider,
49
60
  resolvedName: s.name,
50
- webhookSecret: c,
61
+ webhookSecret: u,
51
62
  redirectOrigins: t.redirectOrigins
52
63
  });
53
- let p = D(t.dunning, n);
54
- Z({
64
+ let ee = V(t.dunning, n);
65
+ X({
55
66
  source: "plugin",
56
- table: f,
67
+ table: U,
57
68
  timeColumn: "updatedAt",
58
- ttlMs: Q(process.env.VOLTRO_BILLING_USAGE_TTL_HOURS, 9600),
59
- label: "billing usage counters"
60
- }), Z({
69
+ where: Pt(Ft("used", 0), Ft("reportedToProvider", 0)),
70
+ ttlMs: Z(process.env.VOLTRO_BILLING_USAGE_TTL_HOURS, 9600),
71
+ label: "unused billing usage counters"
72
+ }), X({
61
73
  source: "plugin",
62
- table: F,
74
+ table: Fe,
63
75
  timeColumn: "claimedAt",
64
- ttlMs: Q(process.env.VOLTRO_BILLING_FLUSH_CLAIM_TTL_HOURS, 1),
76
+ ttlMs: Z(process.env.VOLTRO_BILLING_FLUSH_CLAIM_TTL_HOURS, 1),
65
77
  label: "billing flush claims"
66
- }), Z({
78
+ }), X({
67
79
  source: "plugin",
68
- table: ee,
80
+ table: le,
69
81
  timeColumn: "sentAt",
70
- ttlMs: Q(process.env.VOLTRO_BILLING_DUNNING_TTL_HOURS, 9600),
82
+ ttlMs: Z(process.env.VOLTRO_BILLING_DUNNING_TTL_HOURS, 9600),
71
83
  label: "billing dunning notices"
72
84
  });
73
- let { service: m, holder: h } = j({
85
+ let { service: d, holder: f } = Ee({
74
86
  provider: s,
75
87
  plans: o,
76
88
  ...t.attempts === void 0 ? {} : { attempts: t.attempts },
@@ -80,21 +92,37 @@ var $ = (e) => e === null ? null : {
80
92
  ...t.isTenantClosed === void 0 ? {} : { isTenantClosed: t.isTenantClosed },
81
93
  ...t.onEvent === void 0 ? {} : { onEvent: t.onEvent },
82
94
  ...t.resolveEntitlementLimit === void 0 ? {} : { resolveEntitlementLimit: t.resolveEntitlementLimit },
83
- dunning: p
84
- }), _ = A(m), te = Te({
95
+ dunning: ee
96
+ }), ne = K(d), p, m = {
97
+ scope: Mt({
98
+ base: $,
99
+ alias: t.alias,
100
+ instance: t.name
101
+ }),
102
+ store: () => {
103
+ if (p === void 0) throw Error("Billing delivery requires the bound native store.");
104
+ return p;
105
+ }
106
+ }, h = a({
107
+ provider: s,
108
+ service: d,
109
+ receipts: m,
110
+ ...t.onEvent === void 0 ? {} : { onEvent: t.onEvent }
111
+ }), g = lt({
85
112
  provider: s,
86
- service: m,
87
- webhookSecret: c,
113
+ service: d,
114
+ receipts: m,
115
+ webhookSecret: u,
88
116
  ...t.onEvent === void 0 ? {} : { onEvent: t.onEvent },
89
117
  onLateDelivery: () => {
90
- K();
118
+ R();
91
119
  }
92
- }), ne = {
120
+ }), _ = {
93
121
  method: "POST",
94
- path: Y,
122
+ path: q,
95
123
  originGuard: "exempt",
96
124
  handle: async (e) => {
97
- let t = await te({
125
+ let t = await g({
98
126
  method: e.method,
99
127
  path: e.path,
100
128
  headers: e.headers,
@@ -107,46 +135,59 @@ var $ = (e) => e === null ? null : {
107
135
  ...t.headers === void 0 ? {} : { headers: { ...t.headers } }
108
136
  };
109
137
  }
110
- }, re = (t) => ({
138
+ }, v = (t) => ({
111
139
  ...t,
112
140
  execute: (n, r) => {
113
141
  let i = r.request.subject;
114
- return i.type === "anonymous" || typeof i.tenantId != "string" || i.tenantId === "" ? X.fail(new e({
142
+ return i.type === "anonymous" || typeof i.tenantId != "string" || i.tenantId === "" ? Y.fail(new e({
115
143
  source: "billing",
116
144
  message: "billing routes answer only a signed-in caller that belongs to a tenant",
117
145
  transient: !1
118
146
  })) : t.execute(n, r);
119
147
  }
120
- }), v = (e, t, n, r) => e.pipe(X.flatMap((e) => Xe(r.request.subject, t.guards, n).pipe(X.flatMap((t) => t === null ? X.succeed(e) : X.fail(t))))), ie = [
148
+ }), y = (e, t, n, r) => e.pipe(Y.flatMap((e) => Nt(r.request.subject, t.guards, n).pipe(Y.flatMap((t) => t === null ? Y.succeed(e) : Y.fail(t))))), ie = [
121
149
  {
122
- ...Ie,
123
- execute: (e, t) => {
124
- let n = e;
125
- return v(m.startCheckout({
126
- tenantId: t.request.subject.tenantId ?? "",
127
- plan: n.plan,
128
- successUrl: n.successUrl,
129
- cancelUrl: n.cancelUrl,
130
- ...n.quantity === void 0 ? {} : { quantity: n.quantity }
131
- }), Ie, e, t);
150
+ ...J,
151
+ execute: (t, n) => {
152
+ let r = t;
153
+ return y(d.startCheckout({
154
+ tenantId: n.request.subject.tenantId ?? "",
155
+ plan: r.plan,
156
+ successUrl: r.successUrl,
157
+ cancelUrl: r.cancelUrl,
158
+ ...r.quantity === void 0 ? {} : { quantity: r.quantity },
159
+ authorize: () => Nt(n.request.subject, J.guards, t).pipe(Y.flatMap((t) => t === null ? Y.void : Y.fail(new e({
160
+ source: "billing",
161
+ message: "Current checkout authority could not be confirmed.",
162
+ transient: !1
163
+ }))))
164
+ }), J, t, n);
132
165
  }
133
166
  },
134
167
  {
135
- ...Me,
168
+ ...gt,
136
169
  execute: (e, t) => {
137
170
  let n = e;
138
- return v(m.portalUrl(t.request.subject.tenantId ?? "", n.returnUrl), Me, e, t);
171
+ return y(d.portalUrl(t.request.subject.tenantId ?? "", n.returnUrl), gt, e, t);
139
172
  }
140
173
  },
141
174
  {
142
- ...Le,
143
- source: d,
144
- execute: (e, t) => m.subscription(t.request.subject.tenantId ?? "").pipe(X.map((e) => ({ subscription: $(e) })))
175
+ ...bt,
176
+ source: [
177
+ H,
178
+ c,
179
+ l
180
+ ],
181
+ execute: (e, t) => d.subscription(t.request.subject.tenantId ?? "").pipe(Y.map((e) => ({ subscription: Q(e) })))
145
182
  },
146
183
  {
147
- ...Ae,
148
- source: d,
149
- execute: (e, t) => m.entitlementStatus(t.request.subject.tenantId ?? "").pipe(X.map((e) => ({
184
+ ...mt,
185
+ source: [
186
+ H,
187
+ c,
188
+ l
189
+ ],
190
+ execute: (e, t) => d.entitlementStatus(t.request.subject.tenantId ?? "").pipe(Y.map((e) => ({
150
191
  plan: e.plan,
151
192
  billedPlan: e.billedPlan,
152
193
  status: e.status,
@@ -158,13 +199,13 @@ var $ = (e) => e === null ? null : {
158
199
  })))
159
200
  },
160
201
  {
161
- ...Ne,
202
+ ..._t,
162
203
  execute: (e, t) => {
163
204
  let n = e;
164
- return m.previewChange(t.request.subject.tenantId ?? "", {
205
+ return d.previewChange(t.request.subject.tenantId ?? "", {
165
206
  ...n.plan === void 0 ? {} : { plan: n.plan },
166
207
  ...n.quantity === void 0 ? {} : { quantity: n.quantity }
167
- }).pipe(X.map((e) => ({
208
+ }).pipe(Y.map((e) => ({
168
209
  plan: e.plan,
169
210
  quantity: e.quantity,
170
211
  prorationMinor: e.prorationMinor,
@@ -173,25 +214,29 @@ var $ = (e) => e === null ? null : {
173
214
  }
174
215
  },
175
216
  {
176
- ...je,
177
- source: [u, l],
178
- execute: (e, t) => m.invoices(t.request.subject.tenantId ?? "").pipe(X.map((e) => ({ invoices: e.map((e) => ({
217
+ ...ht,
218
+ source: [
219
+ re,
220
+ l,
221
+ c
222
+ ],
223
+ execute: (e, t) => d.invoices(t.request.subject.tenantId ?? "").pipe(Y.map((e) => ({ invoices: e.map((e) => ({
179
224
  ...e,
180
225
  createdAt: e.createdAt?.toISOString() ?? null
181
226
  })) })))
182
227
  },
183
228
  {
184
- ...Pe,
229
+ ...vt,
185
230
  execute: (e, t) => {
186
231
  let n = e;
187
- return m.reportUsage(t.request.subject.tenantId ?? "", n.key, n.quantity).pipe(X.map(() => ({ ok: !0 })));
232
+ return d.reportUsage(t.request.subject.tenantId ?? "", n.key, n.quantity).pipe(Y.map(() => ({ ok: !0 })));
188
233
  }
189
234
  },
190
235
  {
191
- ...Oe,
236
+ ...ft,
192
237
  execute: (e, t) => {
193
238
  let n = e;
194
- return m.changePlan(t.request.subject.tenantId ?? "", n.plan).pipe(X.map((e) => ({
239
+ return d.changePlan(t.request.subject.tenantId ?? "", n.plan).pipe(Y.map((e) => ({
195
240
  plan: e.plan,
196
241
  quantity: e.quantity,
197
242
  prorationMinor: e.prorationMinor,
@@ -200,10 +245,10 @@ var $ = (e) => e === null ? null : {
200
245
  }
201
246
  },
202
247
  {
203
- ...ke,
248
+ ...pt,
204
249
  execute: (e, t) => {
205
250
  let n = e;
206
- return m.changeSeats(t.request.subject.tenantId ?? "", n.quantity).pipe(X.map((e) => ({
251
+ return d.changeSeats(t.request.subject.tenantId ?? "", n.quantity).pipe(Y.map((e) => ({
207
252
  plan: e.plan,
208
253
  quantity: e.quantity,
209
254
  prorationMinor: e.prorationMinor,
@@ -212,34 +257,34 @@ var $ = (e) => e === null ? null : {
212
257
  }
213
258
  },
214
259
  {
215
- ...De,
216
- execute: (e, t) => m.cancelSubscription(t.request.subject.tenantId ?? "").pipe(X.map((e) => ({ subscription: $(e) })))
260
+ ...dt,
261
+ execute: (e, t) => d.cancelSubscription(t.request.subject.tenantId ?? "").pipe(Y.map((e) => ({ subscription: Q(e) })))
217
262
  },
218
263
  {
219
- ...Re,
220
- execute: (e, t) => m.syncTenant(t.request.subject.tenantId ?? "").pipe(X.map((e) => ({ subscription: e === null ? null : $(e) })))
264
+ ...xt,
265
+ execute: (e, t) => d.syncTenant(t.request.subject.tenantId ?? "").pipe(Y.map((e) => ({ subscription: e === null ? null : Q(e) })))
221
266
  },
222
267
  {
223
- ...Fe,
224
- execute: (e, t) => m.resumeSubscription(t.request.subject.tenantId ?? "").pipe(X.map((e) => ({ subscription: $(e) })))
268
+ ...yt,
269
+ execute: (e, t) => d.resumeSubscription(t.request.subject.tenantId ?? "").pipe(Y.map((e) => ({ subscription: Q(e) })))
225
270
  }
226
- ].map(re), b = (e, t) => ({
271
+ ].map(v), ae = (e, t) => ({
227
272
  kind: "json",
228
273
  status: e,
229
274
  data: { error: t }
230
- }), x = [{
275
+ }), oe = [{
231
276
  method: "GET",
232
277
  path: "/stats",
233
278
  scope: "declaration",
234
279
  description: "Billing config: provider, declared plans, enforced tags",
235
- handler: () => X.succeed({
280
+ handler: () => Y.succeed({
236
281
  kind: "json",
237
282
  data: {
238
283
  provider: s.name,
239
284
  supportsMeteredUsage: s.supportsMeteredUsage,
240
285
  plans: o.planIds(),
241
286
  enforce: t.enforce ? Object.keys(t.enforce) : [],
242
- webhookPath: Y
287
+ webhookPath: q
243
288
  }
244
289
  })
245
290
  }, {
@@ -247,11 +292,11 @@ var $ = (e) => e === null ? null : {
247
292
  path: "/usage",
248
293
  scope: "process",
249
294
  description: "Pending (un-flushed) metered usage counters",
250
- handler: () => h.stores.usage.pending().pipe(X.map((e) => ({
295
+ handler: () => f.stores.usage.pending().pipe(Y.map((e) => ({
251
296
  kind: "json",
252
297
  data: { usage: e }
253
- })), X.catch((e) => X.succeed(b(500, `${e.source} failed`))))
254
- }], S = [
298
+ })), Y.catch((e) => Y.succeed(ae(500, `${e.source} failed`))))
299
+ }], se = [
255
300
  {
256
301
  schema: e,
257
302
  import: {
@@ -273,126 +318,138 @@ var $ = (e) => e === null ? null : {
273
318
  name: "SubscriptionLocked"
274
319
  }
275
320
  }
276
- ], C = t.enforce, w = C !== void 0 && Object.keys(C).length > 0 ? a(C, m) : void 0, T = t.metering;
277
- T !== void 0 && ue(T);
278
- let E = T !== void 0 && Object.keys(T).length > 0 ? se(m, T) : void 0, O = T !== void 0 && ce(T), k = { claim: void 0 }, M = T === void 0 ? void 0 : oe(m, T, {
279
- claimChange: () => k.claim,
321
+ ], b = t.enforce, ce = b !== void 0 && Object.keys(b).length > 0 ? te(b, d) : void 0, x = t.metering;
322
+ x !== void 0 && Me(x);
323
+ let S = x !== void 0 && Object.keys(x).length > 0 ? ke(d, x) : void 0, C = x !== void 0 && Ae(x), w = { claim: void 0 }, T = x === void 0 ? void 0 : Oe(d, x, {
324
+ claimChange: () => w.claim,
280
325
  onUngated: () => {
281
- X.runSync(X.logWarning("metering: CDC meters are accruing PER PROCESS — with N replicas a tenant is billed N times. The framework did not hand this plugin a change claimer (expected on the memory store; on a SQL store this is a bug worth reporting).").pipe(X.annotateLogs({ scope: "voltro:billing" })));
326
+ Y.runSync(Y.logWarning("metering: CDC meters are accruing PER PROCESS — with N replicas a tenant is billed N times. The framework did not hand this plugin a change claimer (expected on the memory store; on a SQL store this is a bug worth reporting).").pipe(Y.annotateLogs({ scope: "voltro:billing" })));
282
327
  }
283
- }), P = qe([E, w].filter((e) => e !== void 0)), z, B, V = !1, H = !1, U = !1, W = 0, G = {
328
+ }), E = At([S, ce].filter((e) => e !== void 0)), D, O, k = !1, A = !1, j, M, N = null, P = !1, F, I = {
284
329
  info: (e, t) => {
285
330
  console.info(e, t ?? {});
286
331
  },
287
332
  error: (e, t) => {
288
333
  console.error(e, t ?? {});
289
334
  }
290
- }, K = async () => {
291
- let e = z, n = s.listUndeliveredEvents;
292
- if (!(e === void 0 || n === void 0 || U) && !(Date.now() - W < 6e4)) {
293
- U = !0;
335
+ }, L = (e) => {
336
+ k && M === void 0 && (M = setTimeout(() => {
337
+ M = void 0, R();
338
+ }, e), M.unref?.());
339
+ }, R = () => {
340
+ let e = s.listUndeliveredEvents;
341
+ if (!k || D === void 0 || e === void 0) return Promise.resolve();
342
+ if (j !== void 0) return j;
343
+ let t = F === void 0 ? 0 : 6e4 - (performance.now() - F);
344
+ return t > 0 ? (L(t), Promise.resolve()) : (j = Promise.resolve().then(async () => {
345
+ let t = !1, n = 0, r = 0, i = 0, a = /* @__PURE__ */ new Set();
294
346
  try {
295
- let r = await X.runPromise(n()), i = Ze(), a = /* @__PURE__ */ new Set();
296
- for (let t of r) t.providerCustomerId !== null && await L(e, `event:${t.id}`, i, Date.now()) && a.add(t.providerCustomerId);
297
- let o = 0;
298
- for (let e of a) await X.runPromise(m.syncCustomer(e).pipe(X.flatMap((e) => X.forEach(e.events, (e) => {
299
- let n = t.onEvent?.[e._tag];
300
- return n === void 0 ? X.void : n(e).pipe(X.catchCause((t) => X.sync(() => {
301
- G.error("billing catch-up: onEvent failed for a correction", {
302
- event: e._tag,
303
- cause: String(t)
304
- });
305
- })));
306
- }, { discard: !0 })), X.tap(() => X.sync(() => {
307
- o += 1;
308
- })), X.catch((e) => X.sync(() => {
309
- G.error("billing catch-up: syncing a customer failed — the provider redelivers its events", { message: e.message });
310
- }))));
311
- G.info("billing catch-up", {
312
- undelivered: r.length,
313
- customers: a.size,
314
- synced: o
347
+ for (let o = 0; o < 10 && k; o++) {
348
+ let o = N;
349
+ if (o !== null) {
350
+ if (a.has(o)) throw Error("The provider event cursor repeated.");
351
+ a.add(o);
352
+ }
353
+ let s = await Y.runPromise(e({
354
+ cursor: o,
355
+ limit: 100
356
+ }));
357
+ if (!Array.isArray(s.events) || s.events.length > 100 || s.nextCursor !== null && (typeof s.nextCursor != "string" || !s.nextCursor || s.nextCursor !== s.events.at(-1)?.id || s.nextCursor === o)) throw Error("The provider event page did not supply a bounded advancing cursor.");
358
+ for (let e of s.events) {
359
+ if (!k) return;
360
+ try {
361
+ await Y.runPromise(h(e)) === "processed" ? n++ : r++;
362
+ } catch {
363
+ i++, P = !0, I.error("billing catch-up: delivery remains retryable", { eventId: e.id });
364
+ }
365
+ }
366
+ if (N = s.nextCursor, N === null) {
367
+ t = P, P = !1;
368
+ break;
369
+ }
370
+ t = !0;
371
+ }
372
+ I.info("billing catch-up", {
373
+ processed: n,
374
+ replayed: r,
375
+ failed: i,
376
+ continuation: N !== null
315
377
  });
316
- } catch (e) {
317
- G.error("billing catch-up failed — the next late delivery or restart retries it", { message: String(e?.message ?? e) });
378
+ } catch {
379
+ t = !0, I.error("billing catch-up failed; the current page will be retried");
318
380
  } finally {
319
- W = Date.now(), U = !1;
381
+ F = performance.now(), j = void 0, t && L(6e4);
320
382
  }
321
- }
322
- }, q = (e) => {
323
- e !== void 0 && (G = e), !H && V && z !== void 0 && (H = !0, K());
324
- }, J = [
383
+ }), j);
384
+ }, z = (e) => {
385
+ e !== void 0 && (I = e), !A && k && D !== void 0 && (A = !0, R());
386
+ }, B = [
325
387
  "store:write",
326
388
  "inspect:read",
327
389
  "inspect:write",
328
390
  "network:outbound:*",
329
- ...P === void 0 ? [] : [
391
+ ...E === void 0 ? [] : [
330
392
  "rpc:intercept:mutation",
331
393
  "rpc:intercept:action",
332
394
  "rpc:intercept:workflow"
333
395
  ],
334
- ...E === void 0 ? [] : ["rpc:intercept:query"],
335
- ...O ? ["store:changes:read"] : []
396
+ ...S === void 0 ? [] : ["rpc:intercept:query"],
397
+ ...C ? ["store:changes:read"] : []
336
398
  ];
337
- return Je({
338
- name: Ye({
339
- base: $e,
399
+ return jt({
400
+ name: Mt({
401
+ base: $,
340
402
  alias: t.alias,
341
403
  instance: t.name
342
404
  }),
343
- baseName: $e,
405
+ baseName: $,
344
406
  description: "Billing — subscriptions, plans, entitlements + usage metering over a pluggable BillingProvider (Stripe + mock). Ships the BillingService, the requireEntitlement() in-handler guard + the enforce-map interceptor, the /billing/webhook receiver (riding @voltro/plugin-webhooks for signature + idempotency), and the _voltro_billing_* tables. Money is integer minor units.",
345
- permissions: J,
346
- declaredEnv: Qe.declared,
347
- egress: ye(s),
348
- services: _,
407
+ permissions: B,
408
+ declaredEnv: Rt.declared,
409
+ egress: rt(s),
410
+ services: ne,
349
411
  routes: ie,
350
- rpcClientDescriptors: Ee,
351
- httpRoutes: [ne],
352
- inspectEndpoints: x,
353
- extendSchema: { tables: [
354
- ...g(),
355
- I,
356
- y
357
- ] },
358
- errorSchemas: S,
359
- ...P === void 0 ? {} : {
360
- interceptMutation: P,
361
- interceptAction: P,
362
- interceptWorkflow: P
412
+ rpcClientDescriptors: ut,
413
+ httpRoutes: [_],
414
+ inspectEndpoints: oe,
415
+ extendSchema: { tables: [...W(), Ie] },
416
+ errorSchemas: se,
417
+ ...E === void 0 ? {} : {
418
+ interceptMutation: E,
419
+ interceptAction: E,
420
+ interceptWorkflow: E
363
421
  },
364
- ...E === void 0 ? {} : { interceptQuery: E },
365
- ...O ? { onChangeEvent: M } : {},
422
+ ...S === void 0 ? {} : { interceptQuery: S },
423
+ ...C ? { onChangeEvent: T } : {},
366
424
  bindDataStore: (e, t) => {
367
- h.stores = ae(e), z = e, q(), k.claim = t?.claimChange;
425
+ f.stores = G(e), D = e, p = Lt(e), z(), w.claim = t?.claimChange;
368
426
  },
369
- onActivate: (e) => X.sync(() => {
427
+ onActivate: (e) => Y.sync(() => {
370
428
  e.logger.info("billing active", {
371
429
  provider: s.name,
372
430
  plans: o.planIds().length,
373
- enforce: C ? Object.keys(C).length : 0,
374
- metering: T ? Object.keys(T).length : 0,
375
- webhook: Y,
431
+ enforce: b ? Object.keys(b).length : 0,
432
+ metering: x ? Object.keys(x).length : 0,
433
+ webhook: q,
376
434
  redirectOrigins: t.redirectOrigins?.length ?? "any"
377
435
  }), s.name === "stripe" && t.redirectOrigins === void 0 && e.logger.warn("billing: no redirectOrigins — checkout and portal accept any return URL; set billingPlugin({ redirectOrigins })", {});
378
- let n = t.flushIntervalMs ?? 6e4, r = T, i = r !== void 0 && Object.keys(r).length > 0;
436
+ let n = t.flushIntervalMs ?? 6e4, r = x, i = r !== void 0 && Object.keys(r).length > 0;
379
437
  if (n > 0) {
380
- let e = Ze();
381
- B = setInterval(() => {
382
- let t = z;
438
+ let e = It();
439
+ O = setInterval(() => {
440
+ let t = D;
383
441
  if (t === void 0) return;
384
442
  let a = Date.now();
385
443
  (async () => {
386
- await L(t, R(a, n), e, a) && await X.runPromise((i ? le(m, t, r) : m.flushUsage()).pipe(X.catch(() => X.void)));
444
+ await Le(t, Re(a, n), e, a) && await Y.runPromise((i ? je(d, t, r) : d.flushUsage()).pipe(Y.catch(() => Y.void)));
387
445
  })();
388
- }, n), B.unref?.();
446
+ }, n), O.unref?.();
389
447
  }
390
- V = !0, q(e.logger);
448
+ k = !0, z(e.logger);
391
449
  }),
392
- onDeactivate: () => X.sync(() => {
393
- B !== void 0 && (clearInterval(B), B = void 0);
450
+ onDeactivate: () => Y.promise(async () => {
451
+ k = !1, A = !1, M !== void 0 && (clearTimeout(M), M = void 0), O !== void 0 && (clearInterval(O), O = void 0), await j;
394
452
  })
395
453
  });
396
454
  };
397
- //#endregion
398
- export { l as BILLING_CUSTOMERS_TABLE, ee as BILLING_DUNNING_NOTICES_TABLE, F as BILLING_FLUSH_CLAIMS_TABLE, u as BILLING_INVOICES_TABLE, d as BILLING_SUBSCRIPTIONS_TABLE, f as BILLING_USAGE_TABLE, Ce as BILLING_WEBHOOK_ID, Y as BILLING_WEBHOOK_PATH, e as BillingError, t as BillingService, te as DEFAULT_DUNNING_STEPS, ne as DEFAULT_GRACE_HOURS, re as DEFAULT_LOCKOUT, n as DEFAULT_PLAN, v as DUNNING_LOCK_STEP_ID, r as EntitlementExceeded, z as STRIPE_API_VERSION, B as STRIPE_DASHBOARD_REMINDERS, he as STRIPE_WEBHOOK_EVENTS, de as StripeRestError, i as SubscriptionLocked, ve as assertBillingProductionReady, p as billingCustomersTable, y as billingDunningNoticesTable, ye as billingEgressOf, I as billingFlushClaimsTable, m as billingInvoicesTable, et as billingPlugin, A as billingServiceLayer, h as billingSubscriptionsTable, g as billingTables, _ as billingUsageTable, we as billingWebhookDescriptor, j as buildBillingService, a as buildEnforceInterceptor, N as buildPlanRegistry, L as claimFlushWindow, ie as dataStoreDunningNoticeStore, ae as dataStoreStores, b as defaultDunningEmail, V as diffMeter, H as diffPortal, U as diffPrice, W as diffProduct, G as diffTaxSettings, K as diffWebhook, x as dueSteps, S as dunningMailNotifier, P as emptyPlanRegistry, ze as envOutRefusal, C as episodeKeyOf, w as evaluateBillingEntitlement, o as evaluateEntitlement, R as flushWindowKey, fe as formEncode, T as graceEndOf, be as isProductionBoot, O as isStaleStatusEvent, E as memoryDunningNoticeStore, k as memoryStores, Be as mergeEnvFile, xe as mockProvider, Te as mountBillingWebhook, ge as normalizeStripeEvent, Ve as openFindings, He as parseProvisionArgs, M as periodKey, q as portalParams, Ue as provisionStripeCatalog, pe as redactStripeKeys, s as requireEntitled, c as requireEntitlement, D as resolveDunningConfig, Se as resolveProvider, We as resolveProvisionKey, Ge as runStripeProvision, J as sellingReadiness, _e as stripeProvider, me as stripeRest };
455
+ export { D as BILLING_CHECKOUT_OPERATIONS_TABLE, j as BILLING_CLOSE_OPERATIONS_TABLE, o as BILLING_CUSTODY_INVENTORY_TABLES, _e as BILLING_CUSTOMERS_TABLE, c as BILLING_CUSTOMER_ACCOUNTS_TABLE, l as BILLING_CUSTOMER_CUSTODY_TABLE, le as BILLING_DUNNING_NOTICES_TABLE, Fe as BILLING_FLUSH_CLAIMS_TABLE, ve as BILLING_INVOICES_TABLE, ce as BILLING_INVOICE_HISTORY_DISPATCH_TABLE, p as BILLING_INVOICE_HISTORY_PAGES_TABLE, m as BILLING_INVOICE_HISTORY_REFERENCES_TABLE, x as BILLING_INVOICE_HISTORY_SCAN_ACCOUNTS_TABLE, h as BILLING_INVOICE_HISTORY_WALKS_TABLE, oe as BILLING_INVOICE_HISTORY_WORK_TABLE, re as BILLING_INVOICE_OBSERVATIONS_TABLE, H as BILLING_SUBSCRIPTIONS_TABLE, T as BILLING_USAGE_EVENTS_TABLE, U as BILLING_USAGE_TABLE, st as BILLING_WEBHOOK_ID, q as BILLING_WEBHOOK_PATH, e as BillingError, t as BillingService, F as DEFAULT_DUNNING_STEPS, I as DEFAULT_GRACE_HOURS, L as DEFAULT_LOCKOUT, n as DEFAULT_PLAN, R as DUNNING_LOCK_STEP_ID, r as EntitlementExceeded, ze as STRIPE_API_VERSION, Be as STRIPE_DASHBOARD_REMINDERS, $e as STRIPE_WEBHOOK_EVENTS, Ye as StripeRestError, i as SubscriptionLocked, nt as assertBillingProductionReady, O as billingCheckoutOperationsTable, M as billingCloseOperationsTable, u as billingCustomerAccountsTable, ee as billingCustomerCustodyTable, ye as billingCustomersTable, z as billingDunningNoticesTable, rt as billingEgressOf, Ie as billingFlushClaimsTable, S as billingInvoiceHistoryDispatchTable, g as billingInvoiceHistoryPagesTable, _ as billingInvoiceHistoryReferencesTable, C as billingInvoiceHistoryScanAccountsTable, v as billingInvoiceHistoryWalksTable, se as billingInvoiceHistoryWorkTable, ie as billingInvoiceObservationsTable, be as billingInvoicesTable, zt as billingPlugin, ae as billingRetainedInvoiceObservationsTable, K as billingServiceLayer, xe as billingSubscriptionsTable, W as billingTables, E as billingUsageEventsTable, Se as billingUsageTable, ct as billingWebhookDescriptor, Ee as buildBillingService, te as buildEnforceInterceptor, Ne as buildPlanRegistry, Le as claimFlushWindow, N as dataStoreBillingCloseStore, k as dataStoreCheckoutOperationStore, B as dataStoreDunningNoticeStore, y as dataStoreInvoiceHistoryJournal, w as dataStoreInvoiceHistoryProgressStore, b as dataStoreInvoiceHistoryWorkStore, G as dataStoreStores, ue as defaultDunningEmail, Ve as diffMeter, He as diffPortal, Ue as diffPrice, We as diffProduct, Ge as diffTaxSettings, Ke as diffWebhook, de as dueSteps, fe as dunningMailNotifier, Pe as emptyPlanRegistry, St as envOutRefusal, pe as episodeKeyOf, me as evaluateBillingEntitlement, d as evaluateEntitlement, Re as flushWindowKey, Xe as formEncode, he as graceEndOf, s as inspectBillingCustody, it as isProductionBoot, Ce as isStaleStatusEvent, P as memoryBillingCloseStore, A as memoryCheckoutOperationStore, ge as memoryDunningNoticeStore, we as memoryStores, Ct as mergeEnvFile, at as mockProvider, lt as mountBillingWebhook, et as normalizeStripeEvent, wt as openFindings, Tt as parseProvisionArgs, De as periodKey, qe as portalParams, Et as provisionStripeCatalog, Ze as redactStripeKeys, f as requireEntitled, ne as requireEntitlement, V as resolveDunningConfig, ot as resolveProvider, Dt as resolveProvisionKey, Ot as runStripeProvision, Te as selectCurrentSubscription, Je as sellingReadiness, tt as stripeProvider, Qe as stripeRest };