@voltro/plugin-billing 0.80.0 → 0.82.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,35 +1,38 @@
1
1
  import { BillingError as e, BillingService as t, DEFAULT_PLAN as n, EntitlementExceeded as r, SubscriptionLocked as i } from "./types.js";
2
- import { billingRpcClientImports as a, changePlanDescriptor as o, changeSeatsDescriptor as s, entitlementStatusDescriptor as c, invoicesDescriptor as l, portalUrlDescriptor as u, previewChangeDescriptor as d, reportUsageDescriptor as f, startCheckoutDescriptor as ee, subscriptionDescriptor as te } from "./rpc.js";
3
- import { Effect as p, Layer as m, Schedule as h, Schema as ne } from "effect";
4
- import { pluginEnv as re } from "@voltro/env";
5
- import { composeRpcInterceptors as ie, definePlugin as ae, pluginInstanceName as oe } from "@voltro/protocol";
6
- import { OccurrenceCounter as g, and as _, changeDigest as se, composeChangeKey as ce, eq as v, gte as le, id as y, integer as b, occurrenceWindowMs as ue, registerRetention as x, retentionTtlMsFromEnv as S, table as C, text as w, timestamp as T } from "@voltro/database";
7
- import { replicaId as de } from "@voltro/protocol/identity";
8
- import { createLogger as E } from "@voltro/logger";
9
- import D from "stripe";
10
- import { defineIncomingWebhook as fe, mountIncomingWebhook as pe } from "@voltro/plugin-webhooks";
11
- import { stripeProvider as O } from "@voltro/plugin-webhooks/providers";
2
+ import { billingRpcClientImports as a, cancelSubscriptionDescriptor as o, changePlanDescriptor as s, changeSeatsDescriptor as c, entitlementStatusDescriptor as l, invoicesDescriptor as u, portalUrlDescriptor as d, previewChangeDescriptor as f, reportUsageDescriptor as p, resumeSubscriptionDescriptor as m, startCheckoutDescriptor as h, subscriptionDescriptor as g, syncDescriptor as _ } from "./rpc.js";
3
+ import { Effect as v, Layer as y, Schedule as b, Schema as x } from "effect";
4
+ import { pluginEnv as S } from "@voltro/env";
5
+ import { composeRpcInterceptors as C, definePlugin as ee, pluginInstanceName as w } from "@voltro/protocol";
6
+ import { OccurrenceCounter as T, and as E, changeDigest as te, composeChangeKey as D, eq as O, gte as ne, id as k, integer as A, occurrenceWindowMs as re, registerRetention as ie, retentionTtlMsFromEnv as ae, table as j, text as M, timestamp as N } from "@voltro/database";
7
+ import { replicaId as oe } from "@voltro/protocol/identity";
8
+ import { createLogger as P } from "@voltro/logger";
9
+ import F from "stripe";
10
+ import { defineIncomingWebhook as I, mountIncomingWebhook as L } from "@voltro/plugin-webhooks";
11
+ import { stripeProvider as se } from "@voltro/plugin-webhooks/providers";
12
+ import { execFileSync as R } from "node:child_process";
13
+ import { chmodSync as z, existsSync as B, readFileSync as ce, realpathSync as le, writeFileSync as ue } from "node:fs";
14
+ import { dirname as de, resolve as V } from "node:path";
12
15
  //#region src/entitlement.ts
13
- var k = (e, t, n, r) => ({
16
+ var fe = (e, t, n, r) => ({
14
17
  allowed: !Number.isFinite(t) || r <= 0 || n + r <= t,
15
18
  entitlement: e,
16
19
  limit: t,
17
20
  used: n,
18
21
  cost: r
19
- }), A = (e) => e.tenantId, me = (e, n, i) => p.gen(function* () {
20
- let a = A(e.request.subject);
21
- if (a === null) return i <= 0 ? void 0 : yield* p.fail(new r({
22
+ }), pe = (e) => e.tenantId, me = (e, n, i) => v.gen(function* () {
23
+ let a = pe(e.request.subject);
24
+ if (a === null) return i <= 0 ? void 0 : yield* v.fail(new r({
22
25
  entitlement: n,
23
26
  limit: 0,
24
27
  used: 0,
25
28
  cost: i
26
29
  }));
27
30
  yield* (yield* t).consumeEntitlement(a, n, i);
28
- }), he = (e) => p.gen(function* () {
29
- let n = A(e.request.subject);
31
+ }), he = (e) => v.gen(function* () {
32
+ let n = pe(e.request.subject);
30
33
  if (n === null) return;
31
34
  let r = yield* (yield* t).entitlementStatus(n);
32
- if (!r.entitled) return yield* p.fail(new i({
35
+ if (!r.entitled) return yield* v.fail(new i({
33
36
  tenantId: n,
34
37
  status: r.status,
35
38
  lockedSince: (r.lockedSince ?? /* @__PURE__ */ new Date()).toISOString(),
@@ -39,52 +42,52 @@ var k = (e, t, n, r) => ({
39
42
  let a = e[i.tag];
40
43
  if (a === void 0) return n;
41
44
  let o = a.cost ?? 1, s = i.subject.tenantId;
42
- return s === null ? o <= 0 ? n : p.fail(new r({
45
+ return s === null ? o <= 0 ? n : v.fail(new r({
43
46
  entitlement: a.entitlement,
44
47
  limit: 0,
45
48
  used: 0,
46
49
  cost: o
47
- })) : p.flatMap(t.consumeEntitlement(s, a.entitlement, o), () => n);
48
- }, j = "_voltro_billing_customers", M = "_voltro_billing_subscriptions", N = "_voltro_billing_invoices", P = "_voltro_billing_usage", _e = C(j, {
49
- id: y({ prefix: "bcust" }),
50
- tenantId: w(),
51
- provider: w(),
52
- providerCustomerId: w(),
53
- createdAt: T(),
54
- updatedAt: T()
55
- }).index("byBillingCustomerTenant", ["tenantId"]).index("byBillingProviderCustomer", ["providerCustomerId"]), ve = C(M, {
56
- id: y({ prefix: "bsub" }),
57
- tenantId: w(),
58
- provider: w(),
59
- providerSubscriptionId: w(),
60
- plan: w(),
61
- status: w(),
62
- quantity: b().default(1),
63
- currentPeriodStart: T().nullable(),
64
- currentPeriodEnd: T().nullable(),
65
- cancelAt: T().nullable(),
66
- pastDueSince: T().nullable(),
67
- statusEventAt: T().nullable(),
68
- createdAt: T(),
69
- updatedAt: T()
70
- }).index("byBillingSubscriptionTenant", ["tenantId"]).index("byBillingProviderSubscription", ["providerSubscriptionId"]).index("byBillingSubscriptionStatus", ["status"]), ye = C(N, {
71
- id: y({ prefix: "binv" }),
72
- tenantId: w(),
73
- provider: w(),
74
- providerInvoiceId: w(),
75
- amountMinor: b(),
76
- currency: w(),
77
- status: w(),
78
- statusEventAt: T().nullable(),
79
- createdAt: T()
80
- }).index("byBillingInvoiceTenant", ["tenantId"]).index("byBillingProviderInvoice", ["providerInvoiceId"]), be = C(P, {
81
- id: y({ prefix: "buse" }),
82
- tenantId: w().maxLength(191),
83
- entitlementKey: w().maxLength(191),
84
- period: w().maxLength(16),
85
- used: b().default(0),
86
- reportedToProvider: b().default(0),
87
- updatedAt: T()
50
+ })) : v.flatMap(t.consumeEntitlement(s, a.entitlement, o), () => n);
51
+ }, H = "_voltro_billing_customers", U = "_voltro_billing_subscriptions", W = "_voltro_billing_invoices", G = "_voltro_billing_usage", _e = j(H, {
52
+ id: k({ prefix: "bcust" }),
53
+ tenantId: M(),
54
+ provider: M(),
55
+ providerCustomerId: M(),
56
+ createdAt: N(),
57
+ updatedAt: N()
58
+ }).index("byBillingCustomerTenant", ["tenantId"]).index("byBillingProviderCustomer", ["providerCustomerId"]), ve = j(U, {
59
+ id: k({ prefix: "bsub" }),
60
+ tenantId: M(),
61
+ provider: M(),
62
+ providerSubscriptionId: M(),
63
+ plan: M(),
64
+ status: M(),
65
+ quantity: A().default(1),
66
+ currentPeriodStart: N().nullable(),
67
+ currentPeriodEnd: N().nullable(),
68
+ cancelAt: N().nullable(),
69
+ pastDueSince: N().nullable(),
70
+ statusEventAt: N().nullable(),
71
+ createdAt: N(),
72
+ updatedAt: N()
73
+ }).index("byBillingSubscriptionTenant", ["tenantId"]).index("byBillingProviderSubscription", ["providerSubscriptionId"]).index("byBillingSubscriptionStatus", ["status"]), ye = j(W, {
74
+ id: k({ prefix: "binv" }),
75
+ tenantId: M(),
76
+ provider: M(),
77
+ providerInvoiceId: M(),
78
+ amountMinor: A(),
79
+ currency: M(),
80
+ status: M(),
81
+ statusEventAt: N().nullable(),
82
+ createdAt: N()
83
+ }).index("byBillingInvoiceTenant", ["tenantId"]).index("byBillingProviderInvoice", ["providerInvoiceId"]), be = j(G, {
84
+ id: k({ prefix: "buse" }),
85
+ tenantId: M().maxLength(191),
86
+ entitlementKey: M().maxLength(191),
87
+ period: M().maxLength(16),
88
+ used: A().default(0),
89
+ reportedToProvider: A().default(0),
90
+ updatedAt: N()
88
91
  }).unique("byBillingUsageWindow", [
89
92
  "tenantId",
90
93
  "entitlementKey",
@@ -94,17 +97,17 @@ var k = (e, t, n, r) => ({
94
97
  ve,
95
98
  ye,
96
99
  be
97
- ], F = "_voltro_billing_dunning_notices", Se = C(F, {
98
- id: y({ prefix: "bdun" }),
99
- tenantId: w().maxLength(191),
100
- episode: w().maxLength(32),
101
- stepId: w().maxLength(64),
102
- sentAt: T()
100
+ ], Se = "_voltro_billing_dunning_notices", Ce = j(Se, {
101
+ id: k({ prefix: "bdun" }),
102
+ tenantId: M().maxLength(191),
103
+ episode: M().maxLength(32),
104
+ stepId: M().maxLength(64),
105
+ sentAt: N()
103
106
  }).unique("byBillingDunningNotice", [
104
107
  "tenantId",
105
108
  "episode",
106
109
  "stepId"
107
- ]), I = "locked", Ce = [
110
+ ]), we = "locked", Te = [
108
111
  {
109
112
  id: "payment-failed",
110
113
  afterHours: 0
@@ -117,28 +120,28 @@ var k = (e, t, n, r) => ({
117
120
  id: "final-warning",
118
121
  afterHours: 144
119
122
  }
120
- ], we = 168, Te = "hard", Ee = (e, t, n) => {
123
+ ], Ee = 168, De = "hard", Oe = (e, t, n) => {
121
124
  if (e === void 0 || e.trim() === "") return t;
122
125
  let r = Number(e);
123
126
  if (!Number.isFinite(r) || r < 0) throw Error(`billingPlugin: ${n} must be a non-negative number, got '${e}'`);
124
127
  return r;
125
- }, L = (e, t) => {
128
+ }, ke = (e, t) => {
126
129
  let n = t.VOLTRO_BILLING_DUNNING;
127
130
  if (n !== void 0 && n !== "" && n !== "on" && n !== "off") throw Error(`billingPlugin: VOLTRO_BILLING_DUNNING must be 'on' or 'off', got '${n}'`);
128
131
  let r = n === "off" ? !1 : n === "on" ? !0 : e?.enabled ?? !0, i = t.VOLTRO_BILLING_LOCKOUT;
129
132
  if (i !== void 0 && i !== "" && i !== "hard" && i !== "soft") throw Error(`billingPlugin: VOLTRO_BILLING_LOCKOUT must be 'hard' or 'soft', got '${i}'`);
130
- let a = i === "hard" || i === "soft" ? i : e?.lockout ?? "hard", o = Ee(t.VOLTRO_BILLING_GRACE_HOURS, e?.graceHours ?? 168, "VOLTRO_BILLING_GRACE_HOURS"), s = e?.steps ?? Ce, c = t.VOLTRO_BILLING_DUNNING_STEP_HOURS, l = s;
133
+ let a = i === "hard" || i === "soft" ? i : e?.lockout ?? "hard", o = Oe(t.VOLTRO_BILLING_GRACE_HOURS, e?.graceHours ?? 168, "VOLTRO_BILLING_GRACE_HOURS"), s = e?.steps ?? Te, c = t.VOLTRO_BILLING_DUNNING_STEP_HOURS, l = s;
131
134
  if (c !== void 0 && c.trim() !== "") {
132
135
  let e = c.split(",").map((e) => e.trim());
133
136
  if (e.length !== s.length) throw Error(`billingPlugin: VOLTRO_BILLING_DUNNING_STEP_HOURS lists ${e.length} value(s) but the sequence declares ${s.length} step(s) (${s.map((e) => e.id).join(", ")})`);
134
137
  l = s.map((t, n) => ({
135
138
  id: t.id,
136
- afterHours: Ee(e[n], t.afterHours, "VOLTRO_BILLING_DUNNING_STEP_HOURS")
139
+ afterHours: Oe(e[n], t.afterHours, "VOLTRO_BILLING_DUNNING_STEP_HOURS")
137
140
  }));
138
141
  }
139
142
  let u = /* @__PURE__ */ new Set();
140
143
  for (let e of l) {
141
- if (e.id === "locked") throw Error(`billingPlugin: '${I}' is the reserved lockout step id`);
144
+ if (e.id === "locked") throw Error(`billingPlugin: '${we}' is the reserved lockout step id`);
142
145
  if (u.has(e.id)) throw Error(`billingPlugin: duplicate dunning step id '${e.id}'`);
143
146
  u.add(e.id);
144
147
  }
@@ -151,10 +154,10 @@ var k = (e, t, n, r) => ({
151
154
  ...e?.resolveRecipient === void 0 ? {} : { resolveRecipient: e.resolveRecipient },
152
155
  ...e?.portalReturnUrl === void 0 ? {} : { portalReturnUrl: e.portalReturnUrl }
153
156
  };
154
- }, De = 36e5, Oe = (e) => String(e.getTime()), ke = (e, t) => new Date(e.getTime() + t * De), Ae = (e, t, n) => {
155
- let r = (n.getTime() - t.getTime()) / De;
157
+ }, Ae = 36e5, je = (e) => String(e.getTime()), Me = (e, t) => new Date(e.getTime() + t * Ae), Ne = (e, t, n) => {
158
+ let r = (n.getTime() - t.getTime()) / Ae;
156
159
  return e.filter((e) => r >= e.afterHours);
157
- }, R = (e, t, r, i) => {
160
+ }, Pe = (e, t, r, i) => {
158
161
  let a = {
159
162
  tenantId: e?.tenantId ?? i ?? "",
160
163
  lockout: t.lockout
@@ -200,7 +203,7 @@ var k = (e, t, n, r) => ({
200
203
  graceEndsAt: null,
201
204
  lockedSince: null
202
205
  };
203
- let i = ke(e.pastDueSince, t.graceHours), o = r.getTime() >= i.getTime();
206
+ let i = Me(e.pastDueSince, t.graceHours), o = r.getTime() >= i.getTime();
204
207
  return {
205
208
  ...a,
206
209
  plan: o && t.lockout === "hard" ? n : e.plan,
@@ -222,29 +225,29 @@ var k = (e, t, n, r) => ({
222
225
  graceEndsAt: null,
223
226
  lockedSince: null
224
227
  };
225
- }, je = (e) => e.toISOString().slice(0, 10), Me = (e) => {
228
+ }, Fe = (e) => e.toISOString().slice(0, 10), Ie = (e) => {
226
229
  let t = e.portalUrl, n = t === null ? "<p>Please update your payment method in the billing settings.</p>" : `<p><a href="${t}">Update your payment method</a></p>`, r = t === null ? "Please update your payment method in the billing settings." : `Update your payment method: ${t}`;
227
230
  if (e.locked) return {
228
231
  subject: "Your subscription has been suspended",
229
232
  html: `<p>We were unable to collect payment, and your subscription is now suspended.</p>${n}`,
230
233
  text: `We were unable to collect payment, and your subscription is now suspended.\n\n${r}`
231
234
  };
232
- let i = je(e.graceEndsAt), a = e.stepId === "payment-failed" ? "Your last payment could not be collected." : "Your payment is still outstanding.";
235
+ let i = Fe(e.graceEndsAt), a = e.stepId === "payment-failed" ? "Your last payment could not be collected." : "Your payment is still outstanding.";
233
236
  return {
234
237
  subject: "Action needed: your payment could not be collected",
235
238
  html: `<p>${a} Your subscription stays active until ${i}.</p>${n}`,
236
239
  text: `${a} Your subscription stays active until ${i}.\n\n${r}`
237
240
  };
238
- }, Ne = (e) => (t) => t.to === null ? p.void : e.send({
241
+ }, Le = (e) => (t) => t.to === null ? v.void : e.send({
239
242
  to: t.to,
240
243
  subject: t.subject,
241
244
  html: t.html,
242
245
  text: t.text
243
- }).pipe(p.asVoid), Pe = (e, t, n) => `${e}\u0000${t}\u0000${n}`, Fe = () => {
246
+ }).pipe(v.asVoid), Re = (e, t, n) => `${e}\u0000${t}\u0000${n}`, ze = () => {
244
247
  let e = /* @__PURE__ */ new Map();
245
248
  return {
246
- claim: (t, n, r, i) => p.sync(() => {
247
- let a = Pe(t, n, r);
249
+ claim: (t, n, r, i) => v.sync(() => {
250
+ let a = Re(t, n, r);
248
251
  return !e.has(a) && (e.set(a, {
249
252
  tenantId: t,
250
253
  episode: n,
@@ -252,12 +255,12 @@ var k = (e, t, n, r) => ({
252
255
  sentAt: i
253
256
  }), !0);
254
257
  }),
255
- sentSteps: (t, n) => p.sync(() => [...e.values()].filter((e) => e.tenantId === t && e.episode === n))
258
+ sentSteps: (t, n) => v.sync(() => [...e.values()].filter((e) => e.tenantId === t && e.episode === n))
256
259
  };
257
- }, Ie = () => `bdun_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`, Le = (t) => ({
258
- claim: (n, r, i, a) => p.tryPromise({
260
+ }, Be = () => `bdun_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`, Ve = (t) => ({
261
+ claim: (n, r, i, a) => v.tryPromise({
259
262
  try: async () => {
260
- let e = Ie(), o = await t.insertIgnore(F, {
263
+ let e = Be(), o = await t.insertIgnore(Se, {
261
264
  id: e,
262
265
  tenantId: n,
263
266
  episode: r,
@@ -276,10 +279,10 @@ var k = (e, t, n, r) => ({
276
279
  transient: !0
277
280
  })
278
281
  }),
279
- sentSteps: (n, r) => p.tryPromise({
282
+ sentSteps: (n, r) => v.tryPromise({
280
283
  try: async () => (await t.query({
281
- table: F,
282
- predicate: _(v("tenantId", n), v("episode", r)),
284
+ table: Se,
285
+ predicate: E(O("tenantId", n), O("episode", r)),
283
286
  order: [],
284
287
  take: void 0,
285
288
  skip: void 0,
@@ -296,91 +299,132 @@ var k = (e, t, n, r) => ({
296
299
  transient: !0
297
300
  })
298
301
  })
299
- }), z = (e) => `${e}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`, B = (t, n) => p.tryPromise({
302
+ }), He = (e) => `${e}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`, Ue = P({ scope: "@voltro/plugin-billing" }), K = (t, n) => v.tryPromise({
300
303
  try: t,
301
- catch: (t) => new e({
304
+ catch: (t) => (Ue.warn("billing store operation failed", {
302
305
  source: n,
303
- message: String(t?.message ?? t),
306
+ detail: String(t?.message ?? t)
307
+ }), new e({
308
+ source: n,
309
+ message: `${n} failed`,
304
310
  transient: !0
305
- })
306
- }), V = (e) => ({
311
+ }))
312
+ }), q = (e) => ({
307
313
  table: e.table,
308
314
  predicate: e.predicate,
309
315
  order: e.order ?? [],
310
316
  take: e.take,
311
317
  skip: void 0,
312
318
  projection: void 0
313
- }), Re = () => {
319
+ }), We = () => {
314
320
  let e = /* @__PURE__ */ new Map();
315
321
  return {
316
- upsert: (t) => p.sync(() => {
322
+ upsert: (t) => v.sync(() => {
317
323
  e.set(t.tenantId, t);
318
324
  }),
319
- getByTenant: (t) => p.sync(() => e.get(t) ?? null)
325
+ getByTenant: (t) => v.sync(() => e.get(t) ?? null),
326
+ getByProviderCustomerId: (t) => v.sync(() => [...e.values()].find((e) => e.providerCustomerId === t) ?? null),
327
+ list: () => v.sync(() => [...e.values()]),
328
+ removeByProviderCustomerId: (t) => v.sync(() => {
329
+ for (let [n, r] of e) if (r.providerCustomerId === t) return e.delete(n), n;
330
+ return null;
331
+ })
320
332
  };
321
- }, ze = (e) => ({
333
+ }, Ge = (e) => ({
322
334
  tenantId: String(e.tenantId),
323
335
  provider: String(e.provider),
324
336
  providerCustomerId: String(e.providerCustomerId)
325
- }), Be = (e) => ({
326
- upsert: (t) => B(async () => {
327
- let n = await e.query(V({
328
- table: j,
329
- predicate: v("tenantId", t.tenantId),
337
+ }), Ke = (e) => ({
338
+ upsert: (t) => K(async () => {
339
+ let n = await e.query(q({
340
+ table: H,
341
+ predicate: O("tenantId", t.tenantId),
330
342
  take: 1
331
343
  })), r = /* @__PURE__ */ new Date();
332
- n[0] ? await e.update(j, String(n[0].id), {
344
+ n[0] ? await e.update(H, String(n[0].id), {
333
345
  provider: t.provider,
334
346
  providerCustomerId: t.providerCustomerId,
335
347
  updatedAt: r
336
- }) : await e.insert(j, {
337
- id: z("bcust"),
348
+ }) : await e.insert(H, {
349
+ id: He("bcust"),
338
350
  ...t,
339
351
  createdAt: r,
340
352
  updatedAt: r
341
353
  });
342
354
  }, "customerStore"),
343
- getByTenant: (t) => B(async () => {
344
- let n = await e.query(V({
345
- table: j,
346
- predicate: v("tenantId", t),
355
+ getByTenant: (t) => K(async () => {
356
+ let n = await e.query(q({
357
+ table: H,
358
+ predicate: O("tenantId", t),
347
359
  take: 1
348
360
  }));
349
- return n[0] ? ze(n[0]) : null;
361
+ return n[0] ? Ge(n[0]) : null;
362
+ }, "customerStore"),
363
+ getByProviderCustomerId: (t) => K(async () => {
364
+ let n = await e.query(q({
365
+ table: H,
366
+ predicate: O("providerCustomerId", t),
367
+ take: 1
368
+ }));
369
+ return n[0] ? Ge(n[0]) : null;
370
+ }, "customerStore"),
371
+ list: () => K(async () => (await e.query(q({ table: H }))).map(Ge), "customerStore"),
372
+ removeByProviderCustomerId: (t) => K(async () => {
373
+ let n = await e.query(q({
374
+ table: H,
375
+ predicate: O("providerCustomerId", t)
376
+ })), r = null;
377
+ for (let t of n) r = String(t.tenantId), await e.delete(H, String(t.id));
378
+ return r;
350
379
  }, "customerStore")
351
- }), H = (e, t) => e === null ? !1 : t === null || t.getTime() < e.getTime(), Ve = () => {
352
- let e = /* @__PURE__ */ new Map();
380
+ }), qe = (e) => e === "canceled", Je = /* @__PURE__ */ new Set([
381
+ "active",
382
+ "trialing",
383
+ "pastDue",
384
+ "incomplete"
385
+ ]), Ye = (e) => Je.has(e), Xe = (e) => e.find((e) => Ye(e.status)) ?? e[0] ?? null, Ze = (e, t) => e === null ? !1 : t === null || t.getTime() < e.getTime(), Qe = () => {
386
+ let e = /* @__PURE__ */ new Map(), t = 0, n = (t) => [...e.values()].filter((e) => e.tenantId === t).sort((e, t) => t.touched - e.touched), r = ({ touched: e, ...t }) => t;
353
387
  return {
354
- upsert: (t) => p.sync(() => {
355
- let n = e.get(t.tenantId);
356
- n && H(n.statusEventAt, t.statusEventAt) || e.set(t.tenantId, {
357
- ...t,
358
- pastDueSince: n ? n.pastDueSince : t.pastDueSince
388
+ upsert: (n) => v.sync(() => {
389
+ let r = e.get(n.providerSubscriptionId);
390
+ r && Ze(r.statusEventAt, n.statusEventAt) || r && qe(r.status) && !qe(n.status) || e.set(n.providerSubscriptionId, {
391
+ ...n,
392
+ pastDueSince: r ? r.pastDueSince : n.pastDueSince,
393
+ touched: ++t
359
394
  });
360
395
  }),
361
- getByTenant: (t) => p.sync(() => e.get(t) ?? null),
362
- listByStatus: (t) => p.sync(() => [...e.values()].filter((e) => e.status === t)),
363
- markCanceled: (t) => p.sync(() => {
364
- for (let [n, r] of e) r.providerSubscriptionId === t && e.set(n, {
396
+ getByTenant: (e) => v.sync(() => {
397
+ let t = Xe(n(e));
398
+ return t ? r(t) : null;
399
+ }),
400
+ listByTenant: (e) => v.sync(() => n(e).map(r)),
401
+ listByStatus: (t) => v.sync(() => [...e.values()].filter((e) => e.status === t).map(r)),
402
+ markCanceled: (n) => v.sync(() => {
403
+ let r = e.get(n);
404
+ r && e.set(n, {
365
405
  ...r,
366
- status: "canceled"
406
+ status: "canceled",
407
+ touched: ++t
367
408
  });
368
409
  }),
369
- patchByTenant: (t, n) => p.sync(() => {
370
- let r = e.get(t);
371
- r && e.set(t, {
372
- ...r,
373
- ...n
410
+ patchByTenant: (r, i) => v.sync(() => {
411
+ let a = Xe(n(r));
412
+ a && e.set(a.providerSubscriptionId, {
413
+ ...a,
414
+ ...i,
415
+ touched: ++t
374
416
  });
375
417
  }),
376
- setStatus: (t, n) => p.sync(() => {
377
- for (let [r, i] of e) i.providerSubscriptionId === t && e.set(r, {
418
+ setStatus: (n, r) => v.sync(() => {
419
+ let i = e.get(n);
420
+ i && e.set(n, {
378
421
  ...i,
379
- status: n
422
+ status: r,
423
+ touched: ++t
380
424
  });
381
425
  })
382
426
  };
383
- }, He = (e) => ({
427
+ }, $e = (e) => ({
384
428
  tenantId: String(e.tenantId),
385
429
  provider: String(e.provider),
386
430
  providerSubscriptionId: String(e.providerSubscriptionId),
@@ -392,16 +436,23 @@ var k = (e, t, n, r) => ({
392
436
  cancelAt: e.cancelAt == null ? null : new Date(e.cancelAt),
393
437
  pastDueSince: e.pastDueSince == null ? null : new Date(e.pastDueSince),
394
438
  statusEventAt: e.statusEventAt == null ? null : new Date(e.statusEventAt)
395
- }), Ue = async (e, t) => (await e.query(V({
439
+ }), et = async (e, t) => (await e.query(q({
396
440
  table: "_voltro_billing_subscriptions",
397
- predicate: v("providerSubscriptionId", t),
441
+ predicate: O("providerSubscriptionId", t),
398
442
  take: 1
399
- })))[0] ?? null, We = (e) => ({
400
- upsert: (t) => B(async () => {
401
- let n = await Ue(e, t.providerSubscriptionId), r = /* @__PURE__ */ new Date();
443
+ })))[0] ?? null, tt = async (e, t) => (await e.query(q({
444
+ table: U,
445
+ predicate: O("tenantId", t),
446
+ order: [{
447
+ column: "updatedAt",
448
+ direction: "desc"
449
+ }]
450
+ }))).map($e), nt = (e) => ({
451
+ upsert: (t) => K(async () => {
452
+ let n = await et(e, t.providerSubscriptionId), r = /* @__PURE__ */ new Date();
402
453
  if (n) {
403
- if (H(n.statusEventAt == null ? null : new Date(n.statusEventAt), t.statusEventAt)) return;
404
- await e.update(M, String(n.id), {
454
+ if (Ze(n.statusEventAt == null ? null : new Date(n.statusEventAt), t.statusEventAt) || qe(String(n.status)) && !qe(t.status)) return;
455
+ await e.update(U, String(n.id), {
405
456
  tenantId: t.tenantId,
406
457
  provider: t.provider,
407
458
  plan: t.plan,
@@ -413,8 +464,8 @@ var k = (e, t, n, r) => ({
413
464
  statusEventAt: t.statusEventAt,
414
465
  updatedAt: r
415
466
  });
416
- } else await e.insert(M, {
417
- id: z("bsub"),
467
+ } else await e.insert(U, {
468
+ id: He("bsub"),
418
469
  tenantId: t.tenantId,
419
470
  provider: t.provider,
420
471
  providerSubscriptionId: t.providerSubscriptionId,
@@ -430,44 +481,26 @@ var k = (e, t, n, r) => ({
430
481
  updatedAt: r
431
482
  });
432
483
  }, "subscriptionStore"),
433
- getByTenant: (t) => B(async () => {
434
- let n = await e.query(V({
435
- table: M,
436
- predicate: v("tenantId", t),
437
- order: [{
438
- column: "updatedAt",
439
- direction: "desc"
440
- }],
441
- take: 1
442
- }));
443
- return n[0] ? He(n[0]) : null;
444
- }, "subscriptionStore"),
445
- listByStatus: (t) => B(async () => (await e.query(V({
446
- table: M,
447
- predicate: v("status", t),
484
+ getByTenant: (t) => K(async () => Xe(await tt(e, t)), "subscriptionStore"),
485
+ listByTenant: (t) => K(() => tt(e, t), "subscriptionStore"),
486
+ listByStatus: (t) => K(async () => (await e.query(q({
487
+ table: U,
488
+ predicate: O("status", t),
448
489
  order: [{
449
490
  column: "updatedAt",
450
491
  direction: "asc"
451
492
  }]
452
- }))).map(He), "subscriptionStore"),
453
- markCanceled: (t) => B(async () => {
454
- let n = await Ue(e, t);
455
- n && await e.update(M, String(n.id), {
493
+ }))).map($e), "subscriptionStore"),
494
+ markCanceled: (t) => K(async () => {
495
+ let n = await et(e, t);
496
+ n && await e.update(U, String(n.id), {
456
497
  status: "canceled",
457
498
  updatedAt: /* @__PURE__ */ new Date()
458
499
  });
459
500
  }, "subscriptionStore"),
460
- patchByTenant: (t, n) => B(async () => {
461
- let r = (await e.query(V({
462
- table: M,
463
- predicate: v("tenantId", t),
464
- order: [{
465
- column: "updatedAt",
466
- direction: "desc"
467
- }],
468
- take: 1
469
- })))[0];
470
- r && await e.update(M, String(r.id), {
501
+ patchByTenant: (t, n) => K(async () => {
502
+ let r = Xe(await tt(e, t)), i = r === null ? null : await et(e, r.providerSubscriptionId);
503
+ i && await e.update(U, String(i.id), {
471
504
  ...n.plan === void 0 ? {} : { plan: n.plan },
472
505
  ...n.quantity === void 0 ? {} : { quantity: n.quantity },
473
506
  ...n.status === void 0 ? {} : { status: n.status },
@@ -478,23 +511,23 @@ var k = (e, t, n, r) => ({
478
511
  updatedAt: /* @__PURE__ */ new Date()
479
512
  });
480
513
  }, "subscriptionStore"),
481
- setStatus: (t, n) => B(async () => {
482
- let r = await Ue(e, t);
483
- r && await e.update(M, String(r.id), {
514
+ setStatus: (t, n) => K(async () => {
515
+ let r = await et(e, t);
516
+ r && await e.update(U, String(r.id), {
484
517
  status: n,
485
518
  updatedAt: /* @__PURE__ */ new Date()
486
519
  });
487
520
  }, "subscriptionStore")
488
- }), Ge = () => {
521
+ }), rt = () => {
489
522
  let e = /* @__PURE__ */ new Map();
490
523
  return {
491
- upsert: (t) => p.sync(() => {
524
+ upsert: (t) => v.sync(() => {
492
525
  let n = e.get(t.providerInvoiceId);
493
- n && H(n.statusEventAt, t.statusEventAt) || e.set(t.providerInvoiceId, t);
526
+ n && Ze(n.statusEventAt, t.statusEventAt) || e.set(t.providerInvoiceId, t);
494
527
  }),
495
- listByTenant: (t) => p.sync(() => [...e.values()].filter((e) => e.tenantId === t))
528
+ listByTenant: (t) => v.sync(() => [...e.values()].filter((e) => e.tenantId === t))
496
529
  };
497
- }, Ke = (e) => ({
530
+ }, it = (e) => ({
498
531
  tenantId: String(e.tenantId),
499
532
  provider: String(e.provider),
500
533
  providerInvoiceId: String(e.providerInvoiceId),
@@ -502,41 +535,41 @@ var k = (e, t, n, r) => ({
502
535
  currency: String(e.currency),
503
536
  status: String(e.status),
504
537
  statusEventAt: e.statusEventAt == null ? null : new Date(e.statusEventAt)
505
- }), qe = (e) => ({
506
- upsert: (t) => B(async () => {
507
- let n = (await e.query(V({
508
- table: N,
509
- predicate: v("providerInvoiceId", t.providerInvoiceId),
538
+ }), at = (e) => ({
539
+ upsert: (t) => K(async () => {
540
+ let n = (await e.query(q({
541
+ table: W,
542
+ predicate: O("providerInvoiceId", t.providerInvoiceId),
510
543
  take: 1
511
544
  })))[0];
512
545
  if (n) {
513
- if (H(n.statusEventAt == null ? null : new Date(n.statusEventAt), t.statusEventAt)) return;
514
- await e.update(N, String(n.id), {
546
+ if (Ze(n.statusEventAt == null ? null : new Date(n.statusEventAt), t.statusEventAt)) return;
547
+ await e.update(W, String(n.id), {
515
548
  amountMinor: t.amountMinor,
516
549
  currency: t.currency,
517
550
  status: t.status,
518
551
  statusEventAt: t.statusEventAt
519
552
  });
520
- } else await e.insert(N, {
521
- id: z("binv"),
553
+ } else await e.insert(W, {
554
+ id: He("binv"),
522
555
  ...t,
523
556
  createdAt: /* @__PURE__ */ new Date()
524
557
  });
525
558
  }, "invoiceStore"),
526
- listByTenant: (t) => B(async () => (await e.query(V({
527
- table: N,
528
- predicate: v("tenantId", t),
559
+ listByTenant: (t) => K(async () => (await e.query(q({
560
+ table: W,
561
+ predicate: O("tenantId", t),
529
562
  order: [{
530
563
  column: "createdAt",
531
564
  direction: "desc"
532
565
  }]
533
- }))).map(Ke), "invoiceStore")
534
- }), U = (e, t, n) => `${e}\u0000${t}\u0000${n}`, Je = () => {
566
+ }))).map(it), "invoiceStore")
567
+ }), ot = (e, t, n) => `${e}\u0000${t}\u0000${n}`, st = () => {
535
568
  let e = /* @__PURE__ */ new Map();
536
569
  return {
537
- used: (t, n, r) => p.sync(() => e.get(U(t, n, r))?.used ?? 0),
538
- consume: (t, n, r, i, a) => p.sync(() => {
539
- let o = U(t, n, r), s = e.get(o), c = s?.used ?? 0, l = c + i <= a;
570
+ used: (t, n, r) => v.sync(() => e.get(ot(t, n, r))?.used ?? 0),
571
+ consume: (t, n, r, i, a) => v.sync(() => {
572
+ let o = ot(t, n, r), s = e.get(o), c = s?.used ?? 0, l = c + i <= a;
540
573
  return l && e.set(o, {
541
574
  tenantId: t,
542
575
  entitlementKey: n,
@@ -551,8 +584,8 @@ var k = (e, t, n, r) => ({
551
584
  cost: i
552
585
  };
553
586
  }),
554
- increment: (t, n, r, i) => p.sync(() => {
555
- let a = U(t, n, r), o = e.get(a), s = (o?.used ?? 0) + i;
587
+ increment: (t, n, r, i) => v.sync(() => {
588
+ let a = ot(t, n, r), o = e.get(a), s = (o?.used ?? 0) + i;
556
589
  return e.set(a, {
557
590
  tenantId: t,
558
591
  entitlementKey: n,
@@ -561,34 +594,34 @@ var k = (e, t, n, r) => ({
561
594
  reportedToProvider: o?.reportedToProvider ?? 0
562
595
  }), s;
563
596
  }),
564
- pending: () => p.sync(() => [...e.values()].filter((e) => e.used > e.reportedToProvider)),
565
- markReported: (t, n, r, i) => p.sync(() => {
566
- let a = U(t, n, r), o = e.get(a);
597
+ pending: () => v.sync(() => [...e.values()].filter((e) => e.used > e.reportedToProvider)),
598
+ markReported: (t, n, r, i) => v.sync(() => {
599
+ let a = ot(t, n, r), o = e.get(a);
567
600
  o && e.set(a, {
568
601
  ...o,
569
602
  reportedToProvider: i
570
603
  });
571
604
  })
572
605
  };
573
- }, Ye = (e) => ({
606
+ }, ct = (e) => ({
574
607
  tenantId: String(e.tenantId),
575
608
  entitlementKey: String(e.entitlementKey),
576
609
  period: String(e.period),
577
610
  used: Number(e.used ?? 0),
578
611
  reportedToProvider: Number(e.reportedToProvider ?? 0)
579
- }), W = async (e, t, n, r) => (await e.query(V({
612
+ }), lt = async (e, t, n, r) => (await e.query(q({
580
613
  table: "_voltro_billing_usage",
581
- predicate: _(v("tenantId", t), v("entitlementKey", n), v("period", r)),
614
+ predicate: E(O("tenantId", t), O("entitlementKey", n), O("period", r)),
582
615
  take: 1
583
- })))[0] ?? null, Xe = (e) => ({
584
- used: (t, n, r) => B(async () => {
585
- let i = await W(e, t, n, r);
616
+ })))[0] ?? null, ut = (e) => ({
617
+ used: (t, n, r) => K(async () => {
618
+ let i = await lt(e, t, n, r);
586
619
  return i ? Number(i.used ?? 0) : 0;
587
620
  }, "usageStore"),
588
- consume: (t, n, r, i, a) => B(async () => {
621
+ consume: (t, n, r, i, a) => K(async () => {
589
622
  for (let o = 0; o < 32; o++) {
590
623
  o > 0 && await new Promise((e) => setTimeout(e, o));
591
- let s = await W(e, t, n, r), c = s ? Number(s.used ?? 0) : 0;
624
+ let s = await lt(e, t, n, r), c = s ? Number(s.used ?? 0) : 0;
592
625
  if (c + i > a) return {
593
626
  allowed: !1,
594
627
  entitlement: n,
@@ -600,7 +633,7 @@ var k = (e, t, n, r) => ({
600
633
  if (await e.updateMany("_voltro_billing_usage", {
601
634
  used: c + i,
602
635
  updatedAt: /* @__PURE__ */ new Date()
603
- }, { where: _(v("id", String(s.id)), v("used", c)) }) === 1) return {
636
+ }, { where: E(O("id", String(s.id)), O("used", c)) }) === 1) return {
604
637
  allowed: !0,
605
638
  entitlement: n,
606
639
  limit: a,
@@ -608,7 +641,7 @@ var k = (e, t, n, r) => ({
608
641
  cost: i
609
642
  };
610
643
  } else {
611
- let o = z("buse"), s = await e.insertIgnore(P, {
644
+ let o = He("buse"), s = await e.insertIgnore(G, {
612
645
  id: o,
613
646
  tenantId: t,
614
647
  entitlementKey: n,
@@ -632,18 +665,18 @@ var k = (e, t, n, r) => ({
632
665
  }
633
666
  throw Error(`billing usage consume: contention on (${t}, ${n}, ${r}) exceeded 32 attempts`);
634
667
  }, "usageStore"),
635
- increment: (t, n, r, i) => B(async () => {
668
+ increment: (t, n, r, i) => K(async () => {
636
669
  for (let a = 0; a < 32; a++) {
637
670
  a > 0 && await new Promise((e) => setTimeout(e, a));
638
- let o = await W(e, t, n, r);
671
+ let o = await lt(e, t, n, r);
639
672
  if (o) {
640
673
  let t = Number(o.used ?? 0);
641
674
  if (await e.updateMany("_voltro_billing_usage", {
642
675
  used: t + i,
643
676
  updatedAt: /* @__PURE__ */ new Date()
644
- }, { where: _(v("id", String(o.id)), v("used", t)) }) === 1) return t + i;
677
+ }, { where: E(O("id", String(o.id)), O("used", t)) }) === 1) return t + i;
645
678
  } else {
646
- let a = z("buse"), o = await e.insertIgnore(P, {
679
+ let a = He("buse"), o = await e.insertIgnore(G, {
647
680
  id: a,
648
681
  tenantId: t,
649
682
  entitlementKey: n,
@@ -661,107 +694,183 @@ var k = (e, t, n, r) => ({
661
694
  }
662
695
  throw Error(`billing usage increment: contention on (${t}, ${n}, ${r}) exceeded 32 attempts`);
663
696
  }, "usageStore"),
664
- pending: () => B(async () => (await e.query(V({ table: P }))).map(Ye).filter((e) => e.used > e.reportedToProvider), "usageStore"),
665
- markReported: (t, n, r, i) => B(async () => {
666
- let a = await W(e, t, n, r);
667
- a && await e.update(P, String(a.id), {
697
+ pending: () => K(async () => (await e.query(q({ table: G }))).map(ct).filter((e) => e.used > e.reportedToProvider), "usageStore"),
698
+ markReported: (t, n, r, i) => K(async () => {
699
+ let a = await lt(e, t, n, r);
700
+ a && await e.update(G, String(a.id), {
668
701
  reportedToProvider: i,
669
702
  updatedAt: /* @__PURE__ */ new Date()
670
703
  });
671
704
  }, "usageStore")
672
- }), Ze = () => ({
673
- customers: Re(),
674
- subscriptions: Ve(),
675
- invoices: Ge(),
676
- usage: Je(),
677
- dunningNotices: Fe()
678
- }), Qe = (e) => ({
679
- customers: Be(e),
680
- subscriptions: We(e),
681
- invoices: qe(e),
682
- usage: Xe(e),
683
- dunningNotices: Le(e)
684
- }), G = E({ scope: "@voltro/plugin-billing" }), K = (e = /* @__PURE__ */ new Date()) => `${e.getUTCFullYear()}-${String(e.getUTCMonth() + 1).padStart(2, "0")}`, $e = (e) => ({ stores: e ?? Ze() }), q = (e, t) => e.pipe(p.retry({
685
- schedule: h.recurs(Math.max(0, t - 1)),
705
+ }), dt = () => ({
706
+ customers: We(),
707
+ subscriptions: Qe(),
708
+ invoices: rt(),
709
+ usage: st(),
710
+ dunningNotices: ze()
711
+ }), ft = (e) => ({
712
+ customers: Ke(e),
713
+ subscriptions: nt(e),
714
+ invoices: at(e),
715
+ usage: ut(e),
716
+ dunningNotices: Ve(e)
717
+ }), J = P({ scope: "@voltro/plugin-billing" }), pt = 3, Y = /* @__PURE__ */ new Set([
718
+ "active",
719
+ "trialing",
720
+ "pastDue"
721
+ ]), mt = (e = /* @__PURE__ */ new Date()) => `${e.getUTCFullYear()}-${String(e.getUTCMonth() + 1).padStart(2, "0")}`, ht = (e) => ({ stores: e ?? dt() }), X = (e, t) => e.pipe(v.retry({
722
+ schedule: b.recurs(Math.max(0, t - 1)),
686
723
  while: (e) => e.transient
687
- })), et = (t) => {
688
- let { provider: n, plans: i } = t, a = t.attempts ?? 3, o = t.checkout, s = t.now ?? (() => /* @__PURE__ */ new Date()), c = $e(t.stores), l = t.dunning ?? L(void 0, {}), u = (e) => c.stores.subscriptions.getByTenant(e).pipe(p.map((t) => R(t, l, s(), e))), d = (e) => u(e).pipe(p.map((e) => e.plan)), f = (e) => c.stores.subscriptions.getByTenant(e), ee = (e, n) => p.gen(function* () {
724
+ })), gt = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,99}$/, _t = "usage key must be 1–100 characters of A–Z, a–z, 0–9, \"_\", \".\", \":\" or \"-\", starting with a letter or digit", vt = (t) => {
725
+ let { provider: n, plans: i } = t, a = t.attempts ?? 3, o = t.checkout, s = t.now ?? (() => /* @__PURE__ */ new Date()), c = (n, r) => n.length === 0 ? v.fail(new e({
726
+ source: "billing",
727
+ message: "no tenant on this call",
728
+ transient: !1
729
+ })) : t.admit === void 0 ? v.void : t.admit(n, r), l = t.redirectOrigins?.map((e) => new URL(e).origin), u = (t, n) => {
730
+ let r = (t) => v.fail(new e({
731
+ source: "billing",
732
+ message: `${n} ${t}`,
733
+ transient: !1
734
+ })), i;
735
+ try {
736
+ i = new URL(t);
737
+ } catch {
738
+ return r("is not an absolute URL");
739
+ }
740
+ return i.protocol !== "https:" && i.protocol !== "http:" ? r("must be an http(s) URL") : l !== void 0 && !l.includes(i.origin) ? r(`must point at one of the configured redirect origins, not ${i.origin}`) : v.void;
741
+ }, d = ht(t.stores), f = t.dunning ?? ke(void 0, {}), p = (e) => d.stores.subscriptions.getByTenant(e).pipe(v.map((t) => Pe(t, f, s(), e))), m = (e) => p(e).pipe(v.map((e) => e.plan)), h = (e) => d.stores.subscriptions.getByTenant(e), g = (e, n) => v.gen(function* () {
689
742
  if (t.resolveEntitlementLimit !== void 0) {
690
743
  let r = yield* t.resolveEntitlementLimit(e, n);
691
744
  if (r !== null) return r;
692
745
  }
693
- let r = yield* d(e);
746
+ let r = yield* m(e);
694
747
  return i.entitlementLimit(r, n);
695
- }), te = (e, t, n) => p.gen(function* () {
696
- return k(t, yield* ee(e, t), yield* c.stores.usage.used(e, t, K(s())), n);
697
- }), m = (e, t, n) => p.gen(function* () {
698
- let i = yield* ee(e, t);
748
+ }), _ = (e, t, n) => v.gen(function* () {
749
+ return fe(t, yield* g(e, t), yield* d.stores.usage.used(e, t, mt(s())), n);
750
+ }), y = (e, t, n) => v.gen(function* () {
751
+ let i = yield* g(e, t);
699
752
  if (!Number.isFinite(i) || n <= 0) return;
700
- let a = K(s()), o = yield* c.stores.usage.consume(e, t, a, n, i);
701
- if (!o.allowed) return yield* p.fail(new r({
753
+ let a = mt(s()), o = yield* d.stores.usage.consume(e, t, a, n, i);
754
+ if (!o.allowed) return yield* v.fail(new r({
702
755
  entitlement: t,
703
756
  limit: o.limit,
704
757
  used: o.used,
705
758
  cost: n
706
759
  }));
707
- }), h = (e, t, n) => n <= 0 ? p.void : c.stores.usage.increment(e, t, K(s()), n).pipe(p.asVoid), ne = () => p.gen(function* () {
760
+ }), b = (t, n, r) => gt.test(n) ? Number.isFinite(r) ? r <= 0 ? v.void : d.stores.usage.increment(t, n, mt(s()), r).pipe(v.asVoid) : v.fail(new e({
761
+ source: "billing",
762
+ message: `usage quantity must be a finite number, got ${r}`,
763
+ transient: !1
764
+ })) : v.fail(new e({
765
+ source: "billing",
766
+ message: _t,
767
+ transient: !1
768
+ })), x = () => v.gen(function* () {
708
769
  if (!n.supportsMeteredUsage) return;
709
- let e = yield* c.stores.usage.pending();
770
+ let e = yield* d.stores.usage.pending();
710
771
  for (let t of e) {
711
772
  let e = t.used - t.reportedToProvider;
712
- e <= 0 || (yield* q(n.reportUsage({
773
+ if (e <= 0) continue;
774
+ let r = yield* d.stores.customers.getByTenant(t.tenantId);
775
+ if (r === null && n.usageRequiresCustomer === !0) {
776
+ yield* v.logWarning("billing usage not flushed: no provider customer linked").pipe(v.annotateLogs({
777
+ tenantId: t.tenantId,
778
+ key: t.entitlementKey,
779
+ pending: e
780
+ }));
781
+ continue;
782
+ }
783
+ yield* X(n.reportUsage({
713
784
  tenantId: t.tenantId,
714
785
  entitlementKey: t.entitlementKey,
715
- quantity: e
716
- }), a), yield* c.stores.usage.markReported(t.tenantId, t.entitlementKey, t.period, t.used));
786
+ quantity: e,
787
+ idempotencyKey: `${t.tenantId}:${t.entitlementKey}:${t.period}:${t.used}`,
788
+ ...r === null ? {} : { providerCustomerId: r.providerCustomerId }
789
+ }), a), yield* d.stores.usage.markReported(t.tenantId, t.entitlementKey, t.period, t.used);
717
790
  }
718
- }), re = (t) => p.gen(function* () {
791
+ }), S = (t) => v.fail(new e({
792
+ source: "billing",
793
+ message: `tenant '${t}' already has a paying subscription — change its plan instead of checking out again`,
794
+ transient: !1
795
+ })), C = (e, t, r) => t.status === "canceled" ? d.stores.subscriptions.markCanceled(t.providerSubscriptionId) : d.stores.subscriptions.upsert({
796
+ tenantId: e,
797
+ provider: n.name,
798
+ providerSubscriptionId: t.providerSubscriptionId,
799
+ plan: t.plan ?? "free",
800
+ status: t.status,
801
+ quantity: t.quantity,
802
+ currentPeriodStart: t.currentPeriodStart,
803
+ currentPeriodEnd: t.currentPeriodEnd,
804
+ cancelAt: t.cancelAt,
805
+ pastDueSince: null,
806
+ statusEventAt: r
807
+ }), ee = (t) => v.gen(function* () {
719
808
  let r = i.priceIdFor(t.plan);
720
- if (r === null) return yield* p.fail(new e({
809
+ if (r === null) return yield* v.fail(new e({
721
810
  source: "billing",
722
811
  message: `plan '${t.plan}' has no provider priceId — not a paid plan`,
723
812
  transient: !1
724
813
  }));
725
- let s = yield* c.stores.customers.getByTenant(t.tenantId);
726
- return yield* q(n.createCheckoutSession({
814
+ if (yield* c(t.tenantId, "checkout"), t.quantity !== void 0 && (!Number.isSafeInteger(t.quantity) || t.quantity <= 0)) return yield* v.fail(new e({
815
+ source: "billing",
816
+ message: `seat quantity must be a positive integer, got ${t.quantity}`,
817
+ transient: !1
818
+ }));
819
+ yield* u(t.successUrl, "successUrl"), yield* u(t.cancelUrl, "cancelUrl");
820
+ let l = yield* d.stores.subscriptions.getByTenant(t.tenantId);
821
+ if (l !== null && Y.has(l.status)) return yield* S(t.tenantId);
822
+ let f = yield* d.stores.customers.getByTenant(t.tenantId), p = f?.providerCustomerId ?? (yield* X(n.ensureCustomer({ tenantId: t.tenantId }), a));
823
+ f === null && (yield* d.stores.customers.upsert({
824
+ tenantId: t.tenantId,
825
+ provider: n.name,
826
+ providerCustomerId: p
827
+ }));
828
+ let m = (yield* X(n.listSubscriptions({ providerCustomerId: p }), a)).filter((e) => Y.has(e.status));
829
+ if (m.length > 0) {
830
+ for (let e of m) yield* C(t.tenantId, e, s());
831
+ return yield* S(t.tenantId);
832
+ }
833
+ return yield* X(n.expireOpenCheckouts({ providerCustomerId: p }), a), yield* X(n.createCheckoutSession({
727
834
  tenantId: t.tenantId,
728
835
  priceId: r,
836
+ meteredPriceIds: i.meteredPriceIdsFor(t.plan),
729
837
  successUrl: t.successUrl,
730
838
  cancelUrl: t.cancelUrl,
731
- ...s === null ? {} : { providerCustomerId: s.providerCustomerId },
839
+ providerCustomerId: p,
732
840
  ...t.quantity === void 0 ? {} : { quantity: t.quantity },
733
841
  ...o?.adjustableQuantity === void 0 ? {} : { adjustableQuantity: o.adjustableQuantity },
734
842
  ...o?.trialDays === void 0 ? {} : { trialDays: o.trialDays }
735
843
  }), a);
736
- }), ie = (t, r) => p.gen(function* () {
737
- let i = yield* c.stores.customers.getByTenant(t);
738
- return i === null ? yield* p.fail(new e({
844
+ }), w = (t, r) => v.gen(function* () {
845
+ yield* c(t, "portal"), yield* u(r, "returnUrl");
846
+ let i = yield* d.stores.customers.getByTenant(t);
847
+ return i === null ? yield* v.fail(new e({
739
848
  source: "billing",
740
849
  message: `no provider customer linked for tenant '${t}'`,
741
850
  transient: !1
742
- })) : yield* q(n.createPortalSession({
851
+ })) : yield* X(n.createPortalSession({
743
852
  tenantId: t,
744
853
  providerCustomerId: i.providerCustomerId,
745
854
  returnUrl: r
746
855
  }), a);
747
- }), ae = (e) => p.gen(function* () {
748
- if (l.resolveRecipient !== void 0) {
749
- let t = yield* l.resolveRecipient(e);
856
+ }), T = (e) => v.gen(function* () {
857
+ if (f.resolveRecipient !== void 0) {
858
+ let t = yield* f.resolveRecipient(e);
750
859
  if (t !== null) return t;
751
860
  }
752
861
  if (n.customerEmail === void 0) return null;
753
- let t = yield* c.stores.customers.getByTenant(e);
862
+ let t = yield* d.stores.customers.getByTenant(e);
754
863
  return t === null ? null : yield* n.customerEmail(t.providerCustomerId);
755
- }), oe = (e) => {
756
- let t = l.portalReturnUrl;
757
- return t === void 0 ? p.succeed(null) : ie(e, t).pipe(p.map((e) => e.url), p.catchAll(() => p.succeed(null)));
758
- }, g = (e, t, n, r, i) => p.gen(function* () {
759
- let a = yield* ae(e.tenantId).pipe(p.catchAll(() => p.succeed(null))), o = yield* oe(e.tenantId), s = t.lockedSince !== null, c = Me({
864
+ }), E = (e) => {
865
+ let t = f.portalReturnUrl;
866
+ return t === void 0 ? v.succeed(null) : w(e, t).pipe(v.map((e) => e.url), v.catchAll(() => v.succeed(null)));
867
+ }, te = (e, t, n, r, i) => v.gen(function* () {
868
+ let a = yield* T(e.tenantId).pipe(v.catchAll(() => v.succeed(null))), o = yield* E(e.tenantId), s = t.lockedSince !== null, c = Ie({
760
869
  stepId: n,
761
870
  locked: s,
762
871
  graceEndsAt: i,
763
872
  portalUrl: o
764
- }), u = {
873
+ }), l = {
765
874
  stepId: n,
766
875
  tenantId: e.tenantId,
767
876
  to: a,
@@ -770,36 +879,36 @@ var k = (e, t, n, r) => ({
770
879
  pastDueSince: r,
771
880
  graceEndsAt: i,
772
881
  locked: s,
773
- lockout: l.lockout,
882
+ lockout: f.lockout,
774
883
  portalUrl: o,
775
884
  ...c
776
885
  };
777
- G.info("billing dunning notice", {
886
+ J.info("billing dunning notice", {
778
887
  tenant: e.tenantId,
779
888
  step: n,
780
889
  locked: s,
781
890
  graceEndsAt: i.toISOString(),
782
- delivered: l.notify !== void 0 && a !== null
891
+ delivered: f.notify !== void 0 && a !== null
783
892
  });
784
- let d = l.notify;
785
- d !== void 0 && (yield* d(u).pipe(p.catchAllCause((t) => p.sync(() => {
786
- G.warn("billing dunning notify failed", {
893
+ let u = f.notify;
894
+ u !== void 0 && (yield* u(l).pipe(v.catchAllCause((t) => v.sync(() => {
895
+ J.warn("billing dunning notify failed", {
787
896
  tenant: e.tenantId,
788
897
  step: n,
789
898
  cause: String(t)
790
899
  });
791
900
  }))));
792
- }), _ = (e) => p.gen(function* () {
793
- let t = yield* c.stores.subscriptions.getByTenant(e);
794
- if (t === null) return R(null, l, s(), e);
795
- let r = yield* n.fetchSubscription(t.providerSubscriptionId).pipe(p.catchAll((t) => p.sync(() => (G.warn("billing dunning reconcile: provider unreachable, not escalating", {
901
+ }), D = (e) => v.gen(function* () {
902
+ let t = yield* d.stores.subscriptions.getByTenant(e);
903
+ if (t === null) return Pe(null, f, s(), e);
904
+ let r = yield* n.fetchSubscription(t.providerSubscriptionId).pipe(v.catchAll((t) => v.sync(() => (J.warn("billing dunning reconcile: provider unreachable, not escalating", {
796
905
  tenant: e,
797
906
  source: t.source,
798
907
  message: t.message
799
908
  }), null)))), i = t;
800
909
  if (r !== null) {
801
910
  let n = s(), a = r.status === "pastDue" ? t.status === "pastDue" && t.pastDueSince !== null ? t.pastDueSince : n : null;
802
- yield* c.stores.subscriptions.patchByTenant(e, {
911
+ yield* d.stores.subscriptions.patchByTenant(e, {
803
912
  status: r.status,
804
913
  ...r.plan === null ? {} : { plan: r.plan },
805
914
  quantity: r.quantity,
@@ -818,30 +927,30 @@ var k = (e, t, n, r) => ({
818
927
  statusEventAt: n
819
928
  };
820
929
  }
821
- let a = R(i, l, s(), e);
822
- if (!l.enabled || i.status !== "pastDue" || i.pastDueSince === null) return a;
823
- let o = i.pastDueSince, u = Oe(o), d = a.graceEndsAt ?? o, f = Ae(l.steps, o, s()).map((e) => e.id);
824
- a.lockedSince !== null && f.push(I);
825
- for (let t of f) (yield* c.stores.dunningNotices.claim(e, u, t, s())) && (yield* g(i, a, t, o, d));
930
+ let a = Pe(i, f, s(), e);
931
+ if (!f.enabled || i.status !== "pastDue" || i.pastDueSince === null) return a;
932
+ let o = i.pastDueSince, c = je(o), l = a.graceEndsAt ?? o, u = Ne(f.steps, o, s()).map((e) => e.id);
933
+ a.lockedSince !== null && u.push(we);
934
+ for (let t of u) (yield* d.stores.dunningNotices.claim(e, c, t, s())) && (yield* te(i, a, t, o, l));
826
935
  return a;
827
- }), se = () => p.gen(function* () {
828
- let e = yield* c.stores.subscriptions.listByStatus("pastDue"), t = 0;
829
- for (let n of e) yield* _(n.tenantId).pipe(p.catchAll((e) => p.sync(() => {
830
- G.warn("billing dunning sweep: tenant failed", {
936
+ }), O = () => v.gen(function* () {
937
+ let e = yield* d.stores.subscriptions.listByStatus("pastDue"), t = 0;
938
+ for (let n of e) yield* D(n.tenantId).pipe(v.catchAll((e) => v.sync(() => {
939
+ J.warn("billing dunning sweep: tenant failed", {
831
940
  tenant: n.tenantId,
832
941
  message: e.message
833
942
  });
834
943
  }))), t += 1;
835
944
  return t;
836
- }), ce = (e) => {
837
- if (!l.enabled) return p.void;
838
- if (e._tag === "invoicePaymentFailed") return _(e.tenantId).pipe(p.asVoid);
839
- if (e._tag !== "invoicePaid" && e._tag !== "subscriptionUpserted") return p.void;
945
+ }), ne = (e) => {
946
+ if (!f.enabled) return v.void;
947
+ if (e._tag === "invoicePaymentFailed") return D(e.tenantId).pipe(v.asVoid);
948
+ if (e._tag !== "invoicePaid" && e._tag !== "subscriptionUpserted") return v.void;
840
949
  let t = e.tenantId;
841
- return c.stores.subscriptions.getByTenant(t).pipe(p.flatMap((e) => e !== null && (e.status === "pastDue" || e.pastDueSince !== null) ? _(t).pipe(p.asVoid) : p.void));
842
- }, v = (e) => {
950
+ return d.stores.subscriptions.getByTenant(t).pipe(v.flatMap((e) => e !== null && (e.status === "pastDue" || e.pastDueSince !== null) ? D(t).pipe(v.asVoid) : v.void));
951
+ }, k = (e) => {
843
952
  switch (e._tag) {
844
- case "subscriptionUpserted": return c.stores.subscriptions.upsert({
953
+ case "subscriptionUpserted": return d.stores.subscriptions.upsert({
845
954
  tenantId: e.tenantId,
846
955
  provider: n.name,
847
956
  providerSubscriptionId: e.providerSubscriptionId,
@@ -854,8 +963,8 @@ var k = (e, t, n, r) => ({
854
963
  pastDueSince: null,
855
964
  statusEventAt: e.occurredAt
856
965
  });
857
- case "subscriptionCanceled": return c.stores.subscriptions.markCanceled(e.providerSubscriptionId);
858
- case "invoicePaid": return c.stores.invoices.upsert({
966
+ case "subscriptionCanceled": return d.stores.subscriptions.markCanceled(e.providerSubscriptionId);
967
+ case "invoicePaid": return d.stores.invoices.upsert({
859
968
  tenantId: e.tenantId,
860
969
  provider: n.name,
861
970
  providerInvoiceId: e.providerInvoiceId,
@@ -864,7 +973,7 @@ var k = (e, t, n, r) => ({
864
973
  status: "paid",
865
974
  statusEventAt: e.occurredAt
866
975
  });
867
- case "invoicePaymentFailed": return c.stores.invoices.upsert({
976
+ case "invoicePaymentFailed": return d.stores.invoices.upsert({
868
977
  tenantId: e.tenantId,
869
978
  provider: n.name,
870
979
  providerInvoiceId: e.providerInvoiceId,
@@ -873,37 +982,39 @@ var k = (e, t, n, r) => ({
873
982
  status: "open",
874
983
  statusEventAt: e.occurredAt
875
984
  });
876
- case "customerLinked": return c.stores.customers.upsert({
985
+ case "customerLinked": return d.stores.customers.upsert({
877
986
  tenantId: e.tenantId,
878
987
  provider: n.name,
879
988
  providerCustomerId: e.providerCustomerId
880
989
  });
990
+ case "customerUnlinked": return d.stores.customers.removeByProviderCustomerId(e.providerCustomerId).pipe(v.asVoid);
881
991
  }
882
- }, le = (e) => v(e).pipe(p.zipRight(ce(e))), y = (t, r) => p.gen(function* () {
883
- let o = yield* c.stores.subscriptions.getByTenant(t);
884
- if (o === null) return yield* p.fail(new e({
992
+ }, A = (e) => k(e).pipe(v.zipRight(ne(e))), re = (t, r) => v.gen(function* () {
993
+ yield* c(t, "change"), yield* R(t);
994
+ let o = yield* d.stores.subscriptions.getByTenant(t);
995
+ if (o === null) return yield* v.fail(new e({
885
996
  source: "billing",
886
997
  message: `no subscription for tenant '${t}' to change`,
887
998
  transient: !1
888
999
  }));
889
1000
  let s = r.plan ?? o.plan, l = r.quantity ?? o.quantity;
890
- if (r.plan !== void 0 && !i.has(s)) return yield* p.fail(new e({
1001
+ if (r.plan !== void 0 && !i.has(s)) return yield* v.fail(new e({
891
1002
  source: "billing",
892
1003
  message: `unknown plan '${s}' — not in the plan registry`,
893
1004
  transient: !1
894
1005
  }));
895
- if (!Number.isInteger(l) || l <= 0) return yield* p.fail(new e({
1006
+ if (!Number.isInteger(l) || l <= 0) return yield* v.fail(new e({
896
1007
  source: "billing",
897
1008
  message: `seat quantity must be a positive integer, got ${l}`,
898
1009
  transient: !1
899
1010
  }));
900
- let u = yield* q(n.updateSubscription({
1011
+ let u = yield* X(n.updateSubscription({
901
1012
  providerSubscriptionId: o.providerSubscriptionId,
902
1013
  ...r.plan === void 0 ? {} : { priceId: i.priceIdFor(s) ?? s },
903
1014
  ...r.quantity === void 0 ? {} : { quantity: l },
904
1015
  proration: "prorate"
905
1016
  }), a);
906
- return yield* c.stores.subscriptions.patchByTenant(t, {
1017
+ return yield* d.stores.subscriptions.patchByTenant(t, {
907
1018
  ...r.plan === void 0 ? {} : { plan: s },
908
1019
  quantity: u.quantity,
909
1020
  ...u.currentPeriodStart === null ? {} : { currentPeriodStart: u.currentPeriodStart },
@@ -915,52 +1026,303 @@ var k = (e, t, n, r) => ({
915
1026
  prorationMinor: u.prorationMinor,
916
1027
  currency: u.currency
917
1028
  };
918
- });
1029
+ }), ie = (t, r) => v.gen(function* () {
1030
+ let o = yield* d.stores.subscriptions.getByTenant(t);
1031
+ if (o === null) return yield* v.fail(new e({
1032
+ source: "billing",
1033
+ message: `no subscription for tenant '${t}' to preview`,
1034
+ transient: !1
1035
+ }));
1036
+ let s = r.plan ?? o.plan, c = yield* X(n.previewSubscriptionChange({
1037
+ providerSubscriptionId: o.providerSubscriptionId,
1038
+ ...r.plan === void 0 ? {} : { priceId: i.priceIdFor(s) ?? s },
1039
+ ...r.quantity === void 0 ? {} : { quantity: r.quantity },
1040
+ proration: "prorate"
1041
+ }), a);
1042
+ return {
1043
+ tenantId: t,
1044
+ plan: s,
1045
+ quantity: c.quantity,
1046
+ prorationMinor: c.prorationMinor,
1047
+ currency: c.currency
1048
+ };
1049
+ }), ae = (e) => v.gen(function* () {
1050
+ let t = yield* d.stores.customers.getByTenant(e);
1051
+ return t === null ? [] : yield* X(n.listInvoices({ providerCustomerId: t.providerCustomerId }), a);
1052
+ }), j = (t, n) => v.gen(function* () {
1053
+ if (n === "free") {
1054
+ let e = yield* oe(t);
1055
+ return {
1056
+ tenantId: t,
1057
+ plan: e.plan,
1058
+ quantity: e.quantity,
1059
+ prorationMinor: 0,
1060
+ currency: i.currencyFor(e.plan)
1061
+ };
1062
+ }
1063
+ return i.has(n) && i.priceIdFor(n) === null ? yield* v.fail(new e({
1064
+ source: "billing",
1065
+ message: `plan '${n}' has no provider price — it is not a self-serve plan`,
1066
+ transient: !1
1067
+ })) : yield* re(t, { plan: n });
1068
+ }), M = (t, n) => d.stores.subscriptions.getByTenant(t).pipe(v.flatMap((r) => r !== null && Ye(r.status) ? v.succeed(r) : v.fail(new e({
1069
+ source: "billing",
1070
+ message: `no live subscription for tenant '${t}' to ${n}`,
1071
+ transient: !1
1072
+ })))), N = (e, t) => v.gen(function* () {
1073
+ let n = s();
1074
+ return yield* C(e.tenantId, {
1075
+ ...t,
1076
+ providerSubscriptionId: e.providerSubscriptionId
1077
+ }, n), {
1078
+ ...e,
1079
+ ...t,
1080
+ plan: t.plan ?? e.plan,
1081
+ statusEventAt: n
1082
+ };
1083
+ }), oe = (e) => v.gen(function* () {
1084
+ yield* c(e, "cancel"), yield* R(e);
1085
+ let t = yield* M(e, "cancel"), r = yield* X(n.cancelSubscription({
1086
+ providerSubscriptionId: t.providerSubscriptionId,
1087
+ mode: "periodEnd"
1088
+ }), a);
1089
+ return yield* N(t, r);
1090
+ }), P = (e) => v.gen(function* () {
1091
+ yield* c(e, "resume"), yield* R(e);
1092
+ let t = yield* M(e, "resume"), r = yield* X(n.resumeSubscription(t.providerSubscriptionId), a);
1093
+ return yield* N(t, r);
1094
+ }), F = (e) => v.gen(function* () {
1095
+ yield* x();
1096
+ let t = yield* d.stores.customers.getByTenant(e), r = yield* d.stores.subscriptions.listByTenant(e), i = new Set(r.filter((e) => Ye(e.status)).map((e) => e.providerSubscriptionId));
1097
+ if (t !== null) {
1098
+ let e = yield* X(n.listSubscriptions({ providerCustomerId: t.providerCustomerId }), a);
1099
+ for (let t of e) i.add(t.providerSubscriptionId);
1100
+ }
1101
+ let o = [];
1102
+ for (let t of i) {
1103
+ let r = yield* X(n.cancelSubscription({
1104
+ providerSubscriptionId: t,
1105
+ mode: "close"
1106
+ }), a);
1107
+ yield* C(e, {
1108
+ ...r,
1109
+ providerSubscriptionId: t
1110
+ }, s()), o.push(t);
1111
+ }
1112
+ return J.info("billing account closed", {
1113
+ tenant: e,
1114
+ canceled: o.length
1115
+ }), {
1116
+ tenantId: e,
1117
+ canceled: o
1118
+ };
1119
+ }), I = (e) => {
1120
+ let n = t.isTenantClosed;
1121
+ return n === void 0 ? v.succeed(!1) : n(e).pipe(v.catchAll((t) => v.sync(() => (J.warn("billing: could not tell whether a tenant is closed — leaving it", {
1122
+ tenant: e,
1123
+ message: t.message
1124
+ }), !1))));
1125
+ }, L = (e, t) => v.gen(function* () {
1126
+ let r = s(), o = yield* X(n.listSubscriptions({ providerCustomerId: e }), a), c = yield* d.stores.customers.getByProviderCustomerId(e), l = c?.tenantId ?? t ?? o.find((e) => e.tenantId !== null)?.tenantId ?? null;
1127
+ if (l === null) return J.warn("billing sync: customer names no tenant — nothing to attribute it to", { customer: e }), {
1128
+ tenantId: null,
1129
+ events: []
1130
+ };
1131
+ let u = [];
1132
+ c === null && (yield* d.stores.customers.upsert({
1133
+ tenantId: l,
1134
+ provider: n.name,
1135
+ providerCustomerId: e
1136
+ }), u.push({
1137
+ _tag: "customerLinked",
1138
+ tenantId: l,
1139
+ providerCustomerId: e,
1140
+ occurredAt: r
1141
+ })), o.some((e) => Y.has(e.status) && e.cancelAt === null) && (yield* I(l)) && (J.error("billing sync: a closed tenant still has a paying subscription — closing it", { tenant: l }), yield* F(l), o = yield* X(n.listSubscriptions({ providerCustomerId: e }), a));
1142
+ for (let e of o) if (yield* C(l, e, r), u.push(B(l, e, r)), e.plan !== null && Y.has(e.status)) {
1143
+ let t = i.meteredPriceIdsFor(e.plan).filter((t) => !e.priceIds.includes(t));
1144
+ t.length > 0 && (yield* X(n.addSubscriptionPrices({
1145
+ providerSubscriptionId: e.providerSubscriptionId,
1146
+ priceIds: t
1147
+ }), a));
1148
+ }
1149
+ let f = new Set(o.map((e) => e.providerSubscriptionId));
1150
+ for (let e of yield* d.stores.subscriptions.listByTenant(l)) {
1151
+ if (!Ye(e.status) || f.has(e.providerSubscriptionId)) continue;
1152
+ let t = yield* X(n.fetchSubscription(e.providerSubscriptionId), a), i = t === null ? {
1153
+ ...e,
1154
+ status: "canceled",
1155
+ providerSubscriptionId: e.providerSubscriptionId
1156
+ } : {
1157
+ ...t,
1158
+ providerSubscriptionId: e.providerSubscriptionId
1159
+ };
1160
+ yield* C(l, i, r), u.push(B(l, i, r));
1161
+ }
1162
+ for (let e of u) yield* ne(e);
1163
+ return {
1164
+ tenantId: l,
1165
+ events: u
1166
+ };
1167
+ }), se = (e) => v.forEach(e, (e) => {
1168
+ let n = t.onEvent?.[e._tag];
1169
+ return n === void 0 ? v.void : n(e).pipe(v.catchAllCause((t) => v.sync(() => {
1170
+ J.error("billing: onEvent failed for a correction — the next sync of this tenant retries it", {
1171
+ event: e._tag,
1172
+ cause: String(t)
1173
+ });
1174
+ })));
1175
+ }, { discard: !0 }), R = (e) => v.gen(function* () {
1176
+ let t = yield* d.stores.customers.getByTenant(e);
1177
+ if (t !== null) {
1178
+ let n = yield* L(t.providerCustomerId, e);
1179
+ yield* se(n.events);
1180
+ }
1181
+ return yield* d.stores.subscriptions.getByTenant(e);
1182
+ }), z = (e, t) => e !== void 0 && e.status === t.status && e.plan === (t.plan ?? "free") && e.quantity === t.quantity && (e.cancelAt?.getTime() ?? null) === (t.cancelAt?.getTime() ?? null), B = (e, t, n) => t.status === "canceled" ? {
1183
+ _tag: "subscriptionCanceled",
1184
+ tenantId: e,
1185
+ providerSubscriptionId: t.providerSubscriptionId,
1186
+ occurredAt: n
1187
+ } : {
1188
+ _tag: "subscriptionUpserted",
1189
+ tenantId: e,
1190
+ providerSubscriptionId: t.providerSubscriptionId,
1191
+ plan: t.plan ?? "free",
1192
+ status: t.status,
1193
+ quantity: t.quantity,
1194
+ currentPeriodStart: t.currentPeriodStart,
1195
+ currentPeriodEnd: t.currentPeriodEnd,
1196
+ cancelAt: t.cancelAt,
1197
+ occurredAt: n
1198
+ };
919
1199
  return {
920
1200
  service: {
921
- subscription: f,
922
- plan: d,
923
- entitlementStatus: u,
924
- reconcileDunning: _,
925
- dunningSweep: se,
926
- checkEntitlement: te,
927
- consumeEntitlement: m,
928
- reportUsage: h,
929
- flushUsage: ne,
930
- startCheckout: re,
931
- portalUrl: ie,
932
- applyEvent: le,
933
- changePlan: (e, t) => y(e, { plan: t }),
934
- changeSeats: (e, t) => y(e, { quantity: t }),
935
- previewChange: (t, r) => p.gen(function* () {
936
- let o = yield* c.stores.subscriptions.getByTenant(t);
937
- if (o === null) return yield* p.fail(new e({
938
- source: "billing",
939
- message: `no subscription for tenant '${t}' to preview`,
940
- transient: !1
941
- }));
942
- let s = r.plan ?? o.plan, l = yield* q(n.previewSubscriptionChange({
943
- providerSubscriptionId: o.providerSubscriptionId,
944
- ...r.plan === void 0 ? {} : { priceId: i.priceIdFor(s) ?? s },
945
- ...r.quantity === void 0 ? {} : { quantity: r.quantity },
946
- proration: "prorate"
947
- }), a);
948
- return {
949
- tenantId: t,
950
- plan: s,
951
- quantity: l.quantity,
952
- prorationMinor: l.prorationMinor,
953
- currency: l.currency
1201
+ subscription: h,
1202
+ plan: m,
1203
+ entitlementStatus: p,
1204
+ reconcileDunning: D,
1205
+ dunningSweep: O,
1206
+ checkEntitlement: _,
1207
+ consumeEntitlement: y,
1208
+ reportUsage: b,
1209
+ flushUsage: x,
1210
+ startCheckout: ee,
1211
+ portalUrl: w,
1212
+ applyEvent: A,
1213
+ changePlan: j,
1214
+ changeSeats: (e, t) => re(e, { quantity: t }),
1215
+ previewChange: ie,
1216
+ invoices: ae,
1217
+ cancelSubscription: oe,
1218
+ resumeSubscription: P,
1219
+ closeAccount: F,
1220
+ reconcileFromProvider: () => v.gen(function* () {
1221
+ let e = s(), t = yield* X(n.listSubscriptions({}), a), r = yield* d.stores.customers.list(), o = new Map(r.map((e) => [e.providerCustomerId, e.tenantId])), c = /* @__PURE__ */ new Set(), l = /* @__PURE__ */ new Map(), u = /* @__PURE__ */ new Set(), f = [], p = /* @__PURE__ */ new Set(), m = /* @__PURE__ */ new Map(), h = 0, g = 0, _ = (e) => {
1222
+ let t = m.get(e);
1223
+ return t === void 0 ? I(e).pipe(v.tap((t) => v.sync(() => {
1224
+ m.set(e, t);
1225
+ }))) : v.succeed(t);
1226
+ };
1227
+ for (let r of t) {
1228
+ u.add(r.providerSubscriptionId);
1229
+ let t = r.tenantId ?? (r.providerCustomerId === null ? null : o.get(r.providerCustomerId) ?? null);
1230
+ if (t === null) {
1231
+ h += 1, J.warn("billing reconcile: provider subscription names no tenant", { subscription: r.providerSubscriptionId });
1232
+ continue;
1233
+ }
1234
+ if (Y.has(r.status) && r.cancelAt === null && !p.has(t) && (yield* _(t))) {
1235
+ J.error("billing reconcile: a closed tenant still has a paying subscription — closing it", {
1236
+ tenant: t,
1237
+ subscription: r.providerSubscriptionId
1238
+ }), (yield* F(t).pipe(v.map(() => !0), v.catchAll((e) => v.sync(() => (J.error("billing reconcile: closing a closed tenant failed — retried next run", {
1239
+ tenant: t,
1240
+ message: e.message
1241
+ }), !1))))) && p.add(t), c.add(t);
1242
+ continue;
1243
+ }
1244
+ if (p.has(t)) continue;
1245
+ Y.has(r.status) && l.set(t, (l.get(t) ?? 0) + 1);
1246
+ let s = yield* d.stores.subscriptions.listByTenant(t);
1247
+ z(s.find((e) => e.providerSubscriptionId === r.providerSubscriptionId), r) || (yield* C(t, r, e), f.push(B(t, r, e)), c.add(t)), r.providerCustomerId !== null && o.get(r.providerCustomerId) !== t && (yield* d.stores.customers.upsert({
1248
+ tenantId: t,
1249
+ provider: n.name,
1250
+ providerCustomerId: r.providerCustomerId
1251
+ }), o.set(r.providerCustomerId, t), f.push({
1252
+ _tag: "customerLinked",
1253
+ tenantId: t,
1254
+ providerCustomerId: r.providerCustomerId,
1255
+ occurredAt: e
1256
+ }), c.add(t));
1257
+ let m = r.plan;
1258
+ if (m !== null && Y.has(r.status)) {
1259
+ let e = i.meteredPriceIdsFor(m).filter((e) => !r.priceIds.includes(e));
1260
+ e.length > 0 && (yield* X(n.addSubscriptionPrices({
1261
+ providerSubscriptionId: r.providerSubscriptionId,
1262
+ priceIds: e
1263
+ }), a), g += 1);
1264
+ }
1265
+ }
1266
+ let y = 0, b = [];
1267
+ for (let e of [
1268
+ "active",
1269
+ "trialing",
1270
+ "pastDue",
1271
+ "incomplete"
1272
+ ]) b.push(...yield* d.stores.subscriptions.listByStatus(e));
1273
+ let x = [];
1274
+ for (let t of b) {
1275
+ if (u.has(t.providerSubscriptionId)) continue;
1276
+ y += 1;
1277
+ let r = yield* X(n.fetchSubscription(t.providerSubscriptionId), a);
1278
+ if (r === null) {
1279
+ x.push(t);
1280
+ continue;
1281
+ }
1282
+ let i = {
1283
+ ...r,
1284
+ providerSubscriptionId: t.providerSubscriptionId
1285
+ };
1286
+ yield* C(t.tenantId, i, e), f.push(B(t.tenantId, i, e)), c.add(t.tenantId);
1287
+ }
1288
+ let S = x.length >= pt && x.length * 2 >= b.length;
1289
+ if (S) J.error("billing reconcile: the provider knows none of these live subscriptions — wrong account or provider? Nothing was canceled", {
1290
+ unknown: x.length,
1291
+ live: b.length,
1292
+ provider: n.name
1293
+ });
1294
+ else for (let t of x) yield* d.stores.subscriptions.markCanceled(t.providerSubscriptionId), f.push(B(t.tenantId, {
1295
+ ...t,
1296
+ status: "canceled",
1297
+ providerSubscriptionId: t.providerSubscriptionId
1298
+ }, e)), c.add(t.tenantId);
1299
+ let ee = [...l].filter(([, e]) => e > 1).map(([e]) => e);
1300
+ for (let e of ee) J.error("billing reconcile: tenant has more than one paying subscription — billed twice, resolve by hand", { tenant: e });
1301
+ let w = {
1302
+ checked: t.length + y,
1303
+ corrected: [...c],
1304
+ duplicates: ee,
1305
+ unattributed: h,
1306
+ meteredItemsRestored: g,
1307
+ closedTenants: [...p],
1308
+ refusedUnknown: S ? x.length : 0,
1309
+ events: f
954
1310
  };
1311
+ return J.info("billing reconcile from provider", {
1312
+ checked: w.checked,
1313
+ corrected: w.corrected.length,
1314
+ duplicates: ee.length,
1315
+ unattributed: h,
1316
+ meteredItemsRestored: g,
1317
+ closedTenants: w.closedTenants.length
1318
+ }), yield* se(f), w;
955
1319
  }),
956
- invoices: (e) => p.gen(function* () {
957
- let t = yield* c.stores.customers.getByTenant(e);
958
- return t === null ? [] : yield* q(n.listInvoices({ providerCustomerId: t.providerCustomerId }), a);
959
- })
1320
+ syncCustomer: L,
1321
+ syncTenant: R
960
1322
  },
961
- holder: c
1323
+ holder: d
962
1324
  };
963
- }, tt = (e) => m.succeed(t, e), nt = "_voltro_ai_usage", rt = (e) => {
1325
+ }, yt = (e) => y.succeed(t, e), bt = "_voltro_ai_usage", xt = (e) => {
964
1326
  for (let [t, n] of Object.entries(e)) {
965
1327
  if (t.length === 0) throw Error("billing metering: a meter key must be non-empty");
966
1328
  if (n.from === "rpc") {
@@ -969,49 +1331,49 @@ var k = (e, t, n, r) => ({
969
1331
  if (typeof n.table != "string" || n.table.length === 0) throw Error(`billing metering: cdc meter '${t}' needs a 'table'`);
970
1332
  } else if (n.from !== "ai") throw Error(`billing metering: meter '${t}' has unknown source '${String(n.from)}'`);
971
1333
  }
972
- }, it = /* @__PURE__ */ new WeakMap(), at = (e) => {
1334
+ }, St = /* @__PURE__ */ new WeakMap(), Ct = (e) => {
973
1335
  if (!e.global && !e.sticky) return e;
974
- let t = it.get(e);
975
- return t === void 0 && (t = new RegExp(e.source, e.flags.replace(/[gy]/g, "")), it.set(e, t)), t;
976
- }, ot = (e, t) => typeof e == "string" ? t === e : at(e).test(t), st = (e, t, n) => Object.entries(e).filter(([, e]) => e.from === "rpc" && (e.kind === void 0 || e.kind === n) && ot(e.match, t)).map(([e]) => e), ct = (e, t, n) => Object.entries(e).filter(([, e]) => e.from === "cdc" && e.table === t && (e.op ?? "insert") === n).map(([e]) => e), lt = (e) => Object.entries(e).filter(([, e]) => e.from === "ai").map(([e, t]) => [e, t.metric ?? "tokens"]), ut = (e) => e.tenantId, dt = (e, t, n, r) => p.forEach(n, (n) => e.reportUsage(t, n, r).pipe(p.catchAll(() => p.void)), { discard: !0 }), ft = (e, t) => (n, r) => {
977
- let i = st(t, r.tag, r.kind);
1336
+ let t = St.get(e);
1337
+ return t === void 0 && (t = new RegExp(e.source, e.flags.replace(/[gy]/g, "")), St.set(e, t)), t;
1338
+ }, wt = (e, t) => typeof e == "string" ? t === e : Ct(e).test(t), Tt = (e, t, n) => Object.entries(e).filter(([, e]) => e.from === "rpc" && (e.kind === void 0 || e.kind === n) && wt(e.match, t)).map(([e]) => e), Et = (e, t, n) => Object.entries(e).filter(([, e]) => e.from === "cdc" && e.table === t && (e.op ?? "insert") === n).map(([e]) => e), Dt = (e) => Object.entries(e).filter(([, e]) => e.from === "ai").map(([e, t]) => [e, t.metric ?? "tokens"]), Ot = (e) => e.tenantId, kt = (e, t, n, r) => v.forEach(n, (n) => e.reportUsage(t, n, r).pipe(v.catchAll(() => v.void)), { discard: !0 }), At = (e, t) => (n, r) => {
1339
+ let i = Tt(t, r.tag, r.kind);
978
1340
  if (i.length === 0) return n;
979
- let a = ut(r.subject);
980
- return a === null ? n : n.pipe(p.tap(() => dt(e, a, i, 1)));
981
- }, pt = "billing-meter", mt = 5e4, ht = (e, t, n = {}) => {
982
- let r = new g({
983
- windowMs: ue(),
984
- maxEntries: mt
1341
+ let a = Ot(r.subject);
1342
+ return a === null ? n : n.pipe(v.tap(() => kt(e, a, i, 1)));
1343
+ }, jt = "billing-meter", Mt = 5e4, Nt = (e, t, n = {}) => {
1344
+ let r = new T({
1345
+ windowMs: re(),
1346
+ maxEntries: Mt
985
1347
  }), i = n.now ?? (() => Date.now()), a = !1;
986
1348
  return (o) => {
987
- let s = ct(t, o.table, o.op);
988
- if (s.length === 0) return p.void;
1349
+ let s = Et(t, o.table, o.op);
1350
+ if (s.length === 0) return v.void;
989
1351
  let c = o.new?.tenantId ?? o.old?.tenantId ?? null;
990
- if (c === null) return p.void;
1352
+ if (c === null) return v.void;
991
1353
  let l = n.claimChange?.();
992
- if (l === void 0) return a || (a = !0, n.onUngated?.()), dt(e, c, s, 1);
993
- let u = String(o.new?.id ?? o.old?.id ?? ""), d = se({
994
- pipe: pt,
1354
+ if (l === void 0) return a || (a = !0, n.onUngated?.()), kt(e, c, s, 1);
1355
+ let u = String(o.new?.id ?? o.old?.id ?? ""), d = te({
1356
+ pipe: jt,
995
1357
  op: o.op,
996
1358
  key: u,
997
1359
  next: o.new ?? null,
998
1360
  prev: o.old ?? null
999
- }), f = ce(d, r.next(d, i()));
1000
- return p.promise(() => l(pt, f)).pipe(p.flatMap((t) => t ? dt(e, c, s, 1) : p.void));
1361
+ }), f = D(d, r.next(d, i()));
1362
+ return v.promise(() => l(jt, f)).pipe(v.flatMap((t) => t ? kt(e, c, s, 1) : v.void));
1001
1363
  };
1002
- }, gt = (e) => new Date(Date.UTC(e.getUTCFullYear(), e.getUTCMonth(), 1, 0, 0, 0, 0)), _t = (e, t, n, r = () => /* @__PURE__ */ new Date()) => p.gen(function* () {
1003
- let i = lt(n);
1364
+ }, Pt = (e) => new Date(Date.UTC(e.getUTCFullYear(), e.getUTCMonth(), 1, 0, 0, 0, 0)), Ft = (e, t, n, r = () => /* @__PURE__ */ new Date()) => v.gen(function* () {
1365
+ let i = Dt(n);
1004
1366
  if (i.length === 0) return;
1005
1367
  let a = r();
1006
- K(a);
1368
+ mt(a);
1007
1369
  let o = {
1008
- table: nt,
1009
- predicate: le("calledAt", gt(a)),
1370
+ table: bt,
1371
+ predicate: ne("calledAt", Pt(a)),
1010
1372
  order: [],
1011
1373
  take: void 0,
1012
1374
  skip: void 0,
1013
1375
  projection: void 0
1014
- }, s = yield* p.promise(() => Promise.resolve(t.query(o)).then((e) => e).catch(() => [])), c = /* @__PURE__ */ new Map();
1376
+ }, s = yield* v.promise(() => Promise.resolve(t.query(o)).then((e) => e).catch(() => [])), c = /* @__PURE__ */ new Map();
1015
1377
  for (let e of s) {
1016
1378
  let t = e.tenantId ?? null;
1017
1379
  if (t === null) continue;
@@ -1024,20 +1386,21 @@ var k = (e, t, n, r) => ({
1024
1386
  for (let [t, n] of i) for (let [r, i] of c) {
1025
1387
  let a = n === "costMicroUsd" ? i.cost : i.tokens;
1026
1388
  if (a <= 0) continue;
1027
- let o = a - (yield* e.checkEntitlement(r, t, 0).pipe(p.map((e) => e.used), p.catchAll(() => p.succeed(0))));
1028
- o > 0 && (yield* e.reportUsage(r, t, o).pipe(p.catchAll(() => p.void)));
1389
+ let o = a - (yield* e.checkEntitlement(r, t, 0).pipe(v.map((e) => e.used), v.catchAll(() => v.succeed(0))));
1390
+ o > 0 && (yield* e.reportUsage(r, t, o).pipe(v.catchAll(() => v.void)));
1029
1391
  }
1030
- }), vt = (e, t, n, r) => _t(e, t, n, r).pipe(p.andThen(e.flushUsage().pipe(p.catchAll(() => p.void)))), yt = (e) => Object.values(e).some((e) => e.from === "cdc"), bt = (e) => e === void 0 ? 0 : e === "unlimited" ? Infinity : e, xt = (e) => {
1392
+ }), It = (e, t, n, r) => Ft(e, t, n, r).pipe(v.andThen(e.flushUsage().pipe(v.catchAll(() => v.void)))), Lt = (e) => Object.values(e).some((e) => e.from === "cdc"), Rt = (e) => e === void 0 ? 0 : e === "unlimited" ? Infinity : e, zt = (e) => {
1031
1393
  let t = /* @__PURE__ */ new Map();
1032
1394
  for (let [n, r] of Object.entries(e)) r.priceId !== void 0 && t.set(r.priceId, n);
1033
1395
  return {
1034
1396
  has: (t) => Object.prototype.hasOwnProperty.call(e, t),
1035
1397
  entitlementLimit: (t, n) => {
1036
1398
  let r = e[t];
1037
- return r === void 0 ? 0 : bt(r.entitlements[n]);
1399
+ return r === void 0 ? 0 : Rt(r.entitlements[n]);
1038
1400
  },
1039
1401
  planForPriceId: (e) => t.get(e) ?? null,
1040
1402
  priceIdFor: (t) => e[t]?.priceId ?? null,
1403
+ meteredPriceIdsFor: (t) => e[t]?.meteredPriceIds ?? [],
1041
1404
  unitAmountFor: (t) => {
1042
1405
  let n = e[t]?.unitAmountMinor;
1043
1406
  return n !== void 0 && Number.isFinite(n) ? n : 0;
@@ -1045,14 +1408,14 @@ var k = (e, t, n, r) => ({
1045
1408
  currencyFor: (t) => (e[t]?.currency ?? "usd").toLowerCase(),
1046
1409
  planIds: () => Object.keys(e)
1047
1410
  };
1048
- }, St = () => xt({ [n]: { entitlements: {} } }), J = "_voltro_billing_flush_claims", Ct = C(J, {
1049
- id: y({ scheme: "numeric" }),
1050
- windowKey: w(),
1051
- claimedBy: w(),
1052
- claimedAt: T().default("now")
1053
- }).unique("billingFlushClaimWindow", ["windowKey"]), wt = (e, t) => `billing-flush:${Math.floor(e / Math.max(1, t))}`, Tt = async (e, t, n, r) => {
1411
+ }, Bt = () => zt({ [n]: { entitlements: {} } }), Vt = "_voltro_billing_flush_claims", Ht = j(Vt, {
1412
+ id: k({ scheme: "numeric" }),
1413
+ windowKey: M(),
1414
+ claimedBy: M(),
1415
+ claimedAt: N().default("now")
1416
+ }).unique("billingFlushClaimWindow", ["windowKey"]), Ut = (e, t) => `billing-flush:${Math.floor(e / Math.max(1, t))}`, Wt = async (e, t, n, r) => {
1054
1417
  try {
1055
- return (await e.insertIgnore(J, {
1418
+ return (await e.insertIgnore(Vt, {
1056
1419
  windowKey: t,
1057
1420
  claimedBy: n,
1058
1421
  claimedAt: new Date(r)
@@ -1060,38 +1423,572 @@ var k = (e, t, n, r) => ({
1060
1423
  } catch {
1061
1424
  return !0;
1062
1425
  }
1063
- }, Et = 3, Dt = 2e4, Ot = (e) => e.client ?? new D(e.apiKey, {
1064
- maxNetworkRetries: Et,
1065
- timeout: Dt,
1426
+ }, Gt = F.API_VERSION, Kt = [
1427
+ "Checkout → \"Limit customers to one subscription\" (with the customer portal login link enabled) — Stripe's own refusal of a second subscription",
1428
+ "Smart Retries + failed-payment emails (Billing → Revenue recovery)",
1429
+ "invoice branding and email settings",
1430
+ "a restricted key (rk_) for the app, separate from the provisioning key"
1431
+ ], qt = (e) => typeof e.tax_code == "string" ? e.tax_code : e.tax_code?.id ?? null, Jt = (e) => ({
1432
+ name: e.name,
1433
+ description: e.description,
1434
+ unit_label: e.unitLabel,
1435
+ tax_code: e.taxCode,
1436
+ metadata: {
1437
+ ...e.metadata,
1438
+ voltro_key: e.key
1439
+ }
1440
+ }), Yt = (e, t) => {
1441
+ let n = [];
1442
+ t.active || n.push("active"), t.name !== e.name && n.push("name"), (t.description ?? "") !== e.description && n.push("description"), (t.unit_label ?? "") !== e.unitLabel && n.push("unit_label"), qt(t) !== e.taxCode && n.push("tax_code");
1443
+ for (let [r, i] of Object.entries({
1444
+ ...e.metadata,
1445
+ voltro_key: e.key
1446
+ })) t.metadata[r] !== i && n.push(`metadata.${r}`);
1447
+ return n;
1448
+ }, Xt = (e, t) => {
1449
+ let n = `product ${e.key}`, r = t.filter((t) => t.metadata.voltro_key === e.key);
1450
+ if (r.length > 1) return {
1451
+ finding: {
1452
+ kind: "manual",
1453
+ object: n,
1454
+ detail: `${r.length} products carry this key — archive the extra ones`
1455
+ },
1456
+ id: null
1457
+ };
1458
+ let i = r[0];
1459
+ if (i !== void 0) {
1460
+ let t = Yt(e, i);
1461
+ return t.length === 0 ? {
1462
+ finding: {
1463
+ kind: "ok",
1464
+ object: n
1465
+ },
1466
+ id: i.id
1467
+ } : {
1468
+ finding: {
1469
+ kind: "update",
1470
+ object: n,
1471
+ detail: t.join(", "),
1472
+ apply: {
1473
+ method: "POST",
1474
+ path: `/v1/products/${i.id}`,
1475
+ params: {
1476
+ ...Jt(e),
1477
+ active: !0
1478
+ }
1479
+ }
1480
+ },
1481
+ id: i.id
1482
+ };
1483
+ }
1484
+ let a = t.filter((t) => t.active && t.metadata.voltro_key === void 0 && Object.keys(e.metadata).length > 0 && Object.entries(e.metadata).every(([e, n]) => t.metadata[e] === n));
1485
+ if (a.length === 1) {
1486
+ let t = a[0];
1487
+ return {
1488
+ finding: {
1489
+ kind: "adopt",
1490
+ object: n,
1491
+ detail: "existing product gets the catalog key",
1492
+ apply: {
1493
+ method: "POST",
1494
+ path: `/v1/products/${t.id}`,
1495
+ params: Jt(e)
1496
+ }
1497
+ },
1498
+ id: t.id
1499
+ };
1500
+ }
1501
+ return a.length > 1 ? {
1502
+ finding: {
1503
+ kind: "manual",
1504
+ object: n,
1505
+ detail: `${a.length} products could be this one — archive all but one`
1506
+ },
1507
+ id: null
1508
+ } : {
1509
+ finding: {
1510
+ kind: "create",
1511
+ object: n,
1512
+ detail: e.name,
1513
+ apply: {
1514
+ method: "POST",
1515
+ path: "/v1/products",
1516
+ params: Jt(e)
1517
+ }
1518
+ },
1519
+ id: null
1520
+ };
1521
+ }, Zt = (e, t, n, r) => {
1522
+ let i = [];
1523
+ return r.product !== t && i.push("product"), r.currency !== e.currency && i.push("currency"), r.unit_amount !== e.unitAmount && i.push("unit_amount"), r.recurring?.interval !== e.interval && i.push("interval"), r.recurring?.usage_type !== e.usageType && i.push("usage_type"), e.usageType === "metered" && r.recurring?.meter !== n && i.push("meter"), r.tax_behavior !== e.taxBehavior && i.push("tax_behavior"), i;
1524
+ }, Qt = (e, t, n, r) => ({
1525
+ product: t,
1526
+ currency: e.currency,
1527
+ unit_amount: e.unitAmount,
1528
+ recurring: {
1529
+ interval: e.interval,
1530
+ usage_type: e.usageType,
1531
+ ...e.usageType === "metered" && n !== null ? { meter: n } : {}
1532
+ },
1533
+ tax_behavior: e.taxBehavior,
1534
+ lookup_key: e.lookupKey,
1535
+ ...r ? { transfer_lookup_key: !0 } : {},
1536
+ metadata: { voltro_key: e.lookupKey }
1537
+ }), $t = (e, t, n, r, i, a) => {
1538
+ let o = `price ${e.lookupKey}`;
1539
+ if (t === null) return {
1540
+ finding: {
1541
+ kind: "manual",
1542
+ object: o,
1543
+ detail: "its product is unresolved"
1544
+ },
1545
+ id: null
1546
+ };
1547
+ if (e.usageType === "metered" && n === null) return {
1548
+ finding: {
1549
+ kind: "manual",
1550
+ object: o,
1551
+ detail: "its meter is unresolved"
1552
+ },
1553
+ id: null
1554
+ };
1555
+ if (r !== null) {
1556
+ let i = Zt(e, t, n, r);
1557
+ if (i.length > 0) {
1558
+ let s = {
1559
+ method: "POST",
1560
+ path: "/v1/prices",
1561
+ params: Qt(e, t, n, !0)
1562
+ };
1563
+ return a ? {
1564
+ finding: {
1565
+ kind: "drift",
1566
+ object: o,
1567
+ detail: `replaced (${i.join(", ")}); existing subscriptions keep the old price`,
1568
+ apply: s
1569
+ },
1570
+ id: null
1571
+ } : {
1572
+ finding: {
1573
+ kind: "drift",
1574
+ object: o,
1575
+ detail: `${i.join(", ")} differ — a price change; rerun with --allow-price-change to replace it`
1576
+ },
1577
+ id: r.id
1578
+ };
1579
+ }
1580
+ return r.active ? {
1581
+ finding: {
1582
+ kind: "ok",
1583
+ object: o
1584
+ },
1585
+ id: r.id
1586
+ } : {
1587
+ finding: {
1588
+ kind: "update",
1589
+ object: o,
1590
+ detail: "inactive",
1591
+ apply: {
1592
+ method: "POST",
1593
+ path: `/v1/prices/${r.id}`,
1594
+ params: { active: !0 }
1595
+ }
1596
+ },
1597
+ id: r.id
1598
+ };
1599
+ }
1600
+ let s = i.filter((r) => r.active && r.lookup_key === null && Zt(e, t, n, r).length === 0)[0];
1601
+ return s === void 0 ? {
1602
+ finding: {
1603
+ kind: "create",
1604
+ object: o,
1605
+ detail: `${e.unitAmount} ${e.currency} / ${e.interval}`,
1606
+ apply: {
1607
+ method: "POST",
1608
+ path: "/v1/prices",
1609
+ params: Qt(e, t, n, !1)
1610
+ }
1611
+ },
1612
+ id: null
1613
+ } : {
1614
+ finding: {
1615
+ kind: "adopt",
1616
+ object: o,
1617
+ detail: "existing price gets the lookup key",
1618
+ apply: {
1619
+ method: "POST",
1620
+ path: `/v1/prices/${s.id}`,
1621
+ params: {
1622
+ lookup_key: e.lookupKey,
1623
+ metadata: { voltro_key: e.lookupKey }
1624
+ }
1625
+ }
1626
+ },
1627
+ id: s.id
1628
+ };
1629
+ }, en = (e, t) => {
1630
+ let n = `meter ${e.eventName}`, r = t.find((t) => t.event_name === e.eventName);
1631
+ if (r === void 0) return {
1632
+ finding: {
1633
+ kind: "create",
1634
+ object: n,
1635
+ detail: e.displayName,
1636
+ apply: {
1637
+ method: "POST",
1638
+ path: "/v1/billing/meters",
1639
+ params: {
1640
+ display_name: e.displayName,
1641
+ event_name: e.eventName,
1642
+ default_aggregation: { formula: e.formula },
1643
+ customer_mapping: {
1644
+ type: "by_id",
1645
+ event_payload_key: e.customerPayloadKey
1646
+ },
1647
+ value_settings: { event_payload_key: e.valuePayloadKey }
1648
+ }
1649
+ }
1650
+ },
1651
+ id: null
1652
+ };
1653
+ let i = [];
1654
+ return r.default_aggregation.formula !== e.formula && i.push("aggregation"), (r.customer_mapping.type !== "by_id" || r.customer_mapping.event_payload_key !== e.customerPayloadKey) && i.push("customer mapping"), r.value_settings.event_payload_key !== e.valuePayloadKey && i.push("value key"), i.length > 0 ? {
1655
+ finding: {
1656
+ kind: "drift",
1657
+ object: n,
1658
+ detail: `${i.join(", ")} differ and cannot be edited — a new meter needs a new event name`
1659
+ },
1660
+ id: r.id
1661
+ } : r.status === "active" ? r.display_name === e.displayName ? {
1662
+ finding: {
1663
+ kind: "ok",
1664
+ object: n
1665
+ },
1666
+ id: r.id
1667
+ } : {
1668
+ finding: {
1669
+ kind: "update",
1670
+ object: n,
1671
+ detail: "display name",
1672
+ apply: {
1673
+ method: "POST",
1674
+ path: `/v1/billing/meters/${r.id}`,
1675
+ params: { display_name: e.displayName }
1676
+ }
1677
+ },
1678
+ id: r.id
1679
+ } : {
1680
+ finding: {
1681
+ kind: "update",
1682
+ object: n,
1683
+ detail: "inactive",
1684
+ apply: {
1685
+ method: "POST",
1686
+ path: `/v1/billing/meters/${r.id}/reactivate`,
1687
+ params: {}
1688
+ }
1689
+ },
1690
+ id: r.id
1691
+ };
1692
+ }, tn = (e, t) => {
1693
+ let n = "tax settings", r = t.head_office?.address ?? {}, i = [];
1694
+ for (let [t, n] of Object.entries(e.headOffice)) r[t] !== n && i.push(`head_office.${t}`);
1695
+ return t.defaults.tax_code !== e.defaultTaxCode && i.push("defaults.tax_code"), t.defaults.tax_behavior !== e.defaultTaxBehavior && i.push("defaults.tax_behavior"), i.length === 0 ? t.status === "active" ? {
1696
+ kind: "ok",
1697
+ object: n
1698
+ } : {
1699
+ kind: "manual",
1700
+ object: n,
1701
+ detail: `status ${t.status}`
1702
+ } : {
1703
+ kind: "update",
1704
+ object: n,
1705
+ detail: i.join(", "),
1706
+ apply: {
1707
+ method: "POST",
1708
+ path: "/v1/tax/settings",
1709
+ params: {
1710
+ head_office: { address: e.headOffice },
1711
+ defaults: {
1712
+ tax_code: e.defaultTaxCode,
1713
+ tax_behavior: e.defaultTaxBehavior
1714
+ }
1715
+ }
1716
+ }
1717
+ };
1718
+ }, nn = (e, t, n) => ({
1719
+ business_profile: {
1720
+ headline: e.headline,
1721
+ privacy_policy_url: e.privacyPolicyUrl,
1722
+ terms_of_service_url: e.termsOfServiceUrl
1723
+ },
1724
+ default_return_url: t.portalReturnUrl,
1725
+ features: {
1726
+ customer_update: {
1727
+ enabled: !0,
1728
+ allowed_updates: [
1729
+ "email",
1730
+ "address",
1731
+ "tax_id",
1732
+ "name"
1733
+ ]
1734
+ },
1735
+ invoice_history: { enabled: !0 },
1736
+ payment_method_update: { enabled: !0 },
1737
+ subscription_cancel: {
1738
+ enabled: !0,
1739
+ mode: "at_period_end"
1740
+ },
1741
+ subscription_update: {
1742
+ enabled: !0,
1743
+ default_allowed_updates: ["price", "quantity"],
1744
+ proration_behavior: "create_prorations",
1745
+ products: n.map((e) => ({
1746
+ product: e.productId,
1747
+ prices: [e.priceId]
1748
+ }))
1749
+ }
1750
+ },
1751
+ metadata: { voltro_key: e.key }
1752
+ }), rn = (e) => JSON.stringify([...e ?? []].sort()), an = (e, t) => {
1753
+ let n = [], r = t.features, i = e.features;
1754
+ t.default_return_url !== e.default_return_url && n.push("default_return_url");
1755
+ let a = e.business_profile;
1756
+ for (let e of Object.keys(a)) t.business_profile[e] !== a[e] && n.push(`business_profile.${e}`);
1757
+ for (let e of [
1758
+ "customer_update",
1759
+ "invoice_history",
1760
+ "payment_method_update",
1761
+ "subscription_cancel"
1762
+ ]) r[e]?.enabled !== !0 && n.push(`${e}.enabled`);
1763
+ rn(r.customer_update?.allowed_updates) !== rn(i.customer_update.allowed_updates) && n.push("customer_update.allowed_updates"), r.subscription_cancel?.mode !== "at_period_end" && n.push("subscription_cancel.mode");
1764
+ let o = r.subscription_update ?? {}, s = i.subscription_update;
1765
+ o.enabled !== !0 && n.push("subscription_update.enabled"), o.proration_behavior !== s.proration_behavior && n.push("subscription_update.proration_behavior");
1766
+ let c = (e) => JSON.stringify((e ?? []).map((e) => `${e.product}:${[...e.prices].sort().join("|")}`).sort());
1767
+ return c(o.products) !== c(s.products) && n.push("subscription_update.products"), rn(o.default_allowed_updates) !== rn(s.default_allowed_updates) && n.push("subscription_update.default_allowed_updates"), n;
1768
+ }, on = (e, t, n, r) => {
1769
+ let i = "customer portal";
1770
+ if (n === null) return [{
1771
+ kind: "manual",
1772
+ object: i,
1773
+ detail: "its plan prices are unresolved"
1774
+ }];
1775
+ let a = nn(e, t, n), o = r.find((t) => t.metadata.voltro_key === e.key) ?? r.find((t) => t.active && t.name === e.name);
1776
+ if (o === void 0) return [{
1777
+ kind: "create",
1778
+ object: i,
1779
+ detail: "self-service configuration",
1780
+ apply: {
1781
+ method: "POST",
1782
+ path: "/v1/billing_portal/configurations",
1783
+ params: {
1784
+ ...a,
1785
+ name: e.name
1786
+ }
1787
+ }
1788
+ }, ...r.some((e) => e.is_default) ? [{
1789
+ kind: "manual",
1790
+ object: i,
1791
+ detail: "another configuration is the default — make this one the default in the Dashboard"
1792
+ }] : []];
1793
+ let s = [], c = an(a, o), l = o.metadata.voltro_key !== e.key;
1794
+ return c.length > 0 || l || !o.active ? s.push({
1795
+ kind: l ? "adopt" : "update",
1796
+ object: i,
1797
+ detail: [...c, ...o.active ? [] : ["active"]].join(", ") || "catalog key",
1798
+ apply: {
1799
+ method: "POST",
1800
+ path: `/v1/billing_portal/configurations/${o.id}`,
1801
+ params: {
1802
+ ...a,
1803
+ active: !0
1804
+ }
1805
+ }
1806
+ }) : s.push({
1807
+ kind: "ok",
1808
+ object: i
1809
+ }), o.is_default || s.push({
1810
+ kind: "manual",
1811
+ object: i,
1812
+ detail: "not the account default — portal sessions would use another configuration"
1813
+ }), s;
1814
+ }, sn = (e, t) => {
1815
+ if (e.webhookUrl === null) return {
1816
+ kind: "ok",
1817
+ object: "webhook endpoint (none: stripe listen forwards locally)"
1818
+ };
1819
+ let n = `webhook endpoint ${e.webhookUrl}`, r = t.filter((t) => t.url === e.webhookUrl);
1820
+ if (r.length > 1) return {
1821
+ kind: "manual",
1822
+ object: n,
1823
+ detail: `${r.length} endpoints for this url — delete all but one`
1824
+ };
1825
+ let i = r[0], a = [...On].sort();
1826
+ return i === void 0 ? {
1827
+ kind: "create",
1828
+ object: n,
1829
+ detail: `${a.length} events`,
1830
+ apply: {
1831
+ method: "POST",
1832
+ path: "/v1/webhook_endpoints",
1833
+ params: {
1834
+ url: e.webhookUrl,
1835
+ enabled_events: a,
1836
+ api_version: Gt,
1837
+ description: "Voltro billing"
1838
+ }
1839
+ }
1840
+ } : i.api_version !== null && i.api_version !== Gt ? {
1841
+ kind: "drift",
1842
+ object: n,
1843
+ detail: `api version ${i.api_version} — payloads would not match the SDK; recreate it (and rotate STRIPE_WEBHOOK_SECRET)`
1844
+ } : JSON.stringify([...i.enabled_events].sort()) !== JSON.stringify(a) || i.status !== "enabled" ? {
1845
+ kind: "update",
1846
+ object: n,
1847
+ detail: i.status === "enabled" ? "events" : "disabled",
1848
+ apply: {
1849
+ method: "POST",
1850
+ path: `/v1/webhook_endpoints/${i.id}`,
1851
+ params: {
1852
+ enabled_events: a,
1853
+ disabled: !1
1854
+ }
1855
+ }
1856
+ } : {
1857
+ kind: "ok",
1858
+ object: n
1859
+ };
1860
+ }, cn = (e) => {
1861
+ let t = [];
1862
+ for (let n of e.catalog.tax.registrationCountries) e.activeRegistrations.includes(n) || t.push({
1863
+ kind: "manual",
1864
+ object: `tax registration ${n}`,
1865
+ detail: "none active — automatic tax collects nothing until it is registered (Dashboard → Tax → Registrations)"
1866
+ });
1867
+ if (!e.env.requireSellingReadiness) return t;
1868
+ e.chargesEnabled === !1 && t.push({
1869
+ kind: "manual",
1870
+ object: "account",
1871
+ detail: "charges not enabled — finish activation (business details, bank account) in the Dashboard"
1872
+ }), e.chargesEnabled === null && t.push({
1873
+ kind: "manual",
1874
+ object: "account",
1875
+ detail: "activation not readable with this key — check it in the Dashboard"
1876
+ }), e.accountTaxIds === 0 && t.push({
1877
+ kind: "manual",
1878
+ object: "account tax id",
1879
+ detail: "no VAT id on the account — invoices lack the issuer id"
1880
+ }), e.accountTaxIds === null && t.push({
1881
+ kind: "manual",
1882
+ object: "account tax id",
1883
+ detail: "not readable with this key — check Dashboard → Settings → Tax ids"
1884
+ });
1885
+ for (let n of e.catalog.dashboardReminders ?? Kt) t.push({
1886
+ kind: "manual",
1887
+ object: "dashboard",
1888
+ detail: `${n} — not checkable through the API`
1889
+ });
1890
+ return t;
1891
+ }, ln = (e) => {
1892
+ let t = [], n = (e, r) => {
1893
+ if (r !== void 0) {
1894
+ if (r === null) {
1895
+ t.push(`${encodeURIComponent(e)}=`);
1896
+ return;
1897
+ }
1898
+ if (Array.isArray(r)) {
1899
+ r.forEach((t, r) => n(`${e}[${r}]`, t));
1900
+ return;
1901
+ }
1902
+ if (typeof r == "object") {
1903
+ for (let [t, i] of Object.entries(r)) n(`${e}[${t}]`, i);
1904
+ return;
1905
+ }
1906
+ t.push(`${encodeURIComponent(e)}=${encodeURIComponent(String(r))}`);
1907
+ }
1908
+ };
1909
+ for (let [t, r] of Object.entries(e)) n(t, r);
1910
+ return t.join("&");
1911
+ }, un = (e) => e.replace(/\b(sk|rk|rkcs|pk)_(test|live)_[A-Za-z0-9*]+/g, "$1_$2_[REDACTED]").replace(/\bwhsec_[A-Za-z0-9]+/g, "whsec_[REDACTED]"), dn = class extends Error {
1912
+ status;
1913
+ constructor(e, t) {
1914
+ super(t), this.status = e;
1915
+ }
1916
+ }, fn = (e, t = fetch) => {
1917
+ let n = async (n, r, i = {}) => {
1918
+ if (!/^\/v[12]\/[A-Za-z0-9_/.-]+$/.test(r)) throw new dn(0, `refusing request path ${JSON.stringify(r)}`);
1919
+ let a = ln(i), o = await t(`https://api.stripe.com${r}${n === "GET" && a.length > 0 ? `?${a}` : ""}`, {
1920
+ method: n,
1921
+ headers: {
1922
+ authorization: `Bearer ${e}`,
1923
+ "stripe-version": Gt,
1924
+ ...n === "POST" ? { "content-type": "application/x-www-form-urlencoded" } : {}
1925
+ },
1926
+ ...n === "POST" ? { body: a } : {}
1927
+ }), s = await o.json();
1928
+ if (!o.ok) throw new dn(o.status, un(s.error?.message ?? `HTTP ${o.status}`));
1929
+ return s;
1930
+ };
1931
+ return {
1932
+ get: (e, t) => n("GET", e, t),
1933
+ post: (e, t) => n("POST", e, t),
1934
+ list: async (e, t = {}) => {
1935
+ let r = [], i;
1936
+ for (;;) {
1937
+ let a = await n("GET", e, {
1938
+ limit: 100,
1939
+ ...t,
1940
+ ...i === void 0 ? {} : { starting_after: i }
1941
+ });
1942
+ if (r.push(...a.data), !a.has_more || a.data.length === 0) return r;
1943
+ i = a.data[a.data.length - 1].id;
1944
+ }
1945
+ }
1946
+ };
1947
+ }, pn = P({ scope: "@voltro/plugin-billing" }), mn = 3, hn = 2e4, gn = (e) => e.client ?? new F(e.apiKey, {
1948
+ maxNetworkRetries: mn,
1949
+ timeout: hn,
1066
1950
  appInfo: {
1067
1951
  name: "voltro",
1068
1952
  url: "https://voltro.dev"
1069
1953
  }
1070
- }), kt = (e) => e instanceof D.errors.StripeConnectionError || e instanceof D.errors.StripeAPIError || e instanceof D.errors.StripeRateLimitError, At = (e) => e instanceof D.errors.StripeError ? e.code ?? e.type : void 0, Y = (t, n) => p.tryPromise({
1954
+ }), _n = (e) => e instanceof F.errors.StripeConnectionError || e instanceof F.errors.StripeAPIError || e instanceof F.errors.StripeRateLimitError, vn = (e) => e instanceof F.errors.StripeError ? e.code ?? e.type : void 0, Z = (t, n) => v.tryPromise({
1071
1955
  try: n,
1072
- catch: (n) => new e({
1073
- source: t,
1074
- message: jt(n),
1075
- transient: kt(n)
1076
- })
1077
- }), jt = (e) => {
1078
- if (e instanceof D.errors.StripeError) {
1079
- let t = At(e);
1956
+ catch: (n) => {
1957
+ let r = _n(n);
1958
+ return pn.warn("stripe call failed", {
1959
+ source: t,
1960
+ transient: r,
1961
+ detail: un(yn(n))
1962
+ }), new e({
1963
+ source: t,
1964
+ message: bn(n),
1965
+ transient: r
1966
+ });
1967
+ }
1968
+ }), yn = (e) => {
1969
+ if (e instanceof F.errors.StripeError) {
1970
+ let t = vn(e);
1080
1971
  return t === void 0 ? e.message : `${e.message} (${t})`;
1081
1972
  }
1082
1973
  return String(e?.message ?? e);
1083
- }, Mt = (e) => {
1974
+ }, bn = (e) => {
1975
+ if (e instanceof F.errors.StripeError) {
1976
+ let t = [e.code, e.decline_code].filter((e) => typeof e == "string" && e.length > 0);
1977
+ return t.length > 0 ? `${e.type} (${t.join(", ")})` : e.type;
1978
+ }
1979
+ return "provider call failed";
1980
+ }, xn = (e) => {
1084
1981
  let t = e.items?.data?.[0];
1085
1982
  return {
1086
- start: X(t?.current_period_start),
1087
- end: X(t?.current_period_end)
1983
+ start: Q(t?.current_period_start),
1984
+ end: Q(t?.current_period_end)
1088
1985
  };
1089
- }, X = (e) => typeof e == "number" && Number.isFinite(e) ? /* @__PURE__ */ new Date(e * 1e3) : null, Nt = (e) => {
1986
+ }, Q = (e) => typeof e == "number" && Number.isFinite(e) ? /* @__PURE__ */ new Date(e * 1e3) : null, Sn = (e) => {
1090
1987
  let t = e.items?.data ?? [];
1091
1988
  if (t.length === 0) return 1;
1092
1989
  let n = t.reduce((e, t) => e + (t.quantity ?? 0), 0);
1093
1990
  return n > 0 ? n : 1;
1094
- }, Pt = {
1991
+ }, Cn = {
1095
1992
  active: "active",
1096
1993
  trialing: "trialing",
1097
1994
  past_due: "pastDue",
@@ -1100,44 +1997,58 @@ var k = (e, t, n, r) => ({
1100
1997
  incomplete: "incomplete",
1101
1998
  incomplete_expired: "canceled",
1102
1999
  paused: "pastDue"
1103
- }, Z = (e) => {
2000
+ }, $ = (e) => {
1104
2001
  let t = e.metadata?.tenantId;
1105
2002
  if (typeof t == "string" && t.length > 0) return t;
1106
2003
  let n = e.client_reference_id;
1107
2004
  return typeof n == "string" && n.length > 0 ? n : null;
1108
- }, Ft = (e) => {
1109
- let t = e.items?.data?.[0]?.price;
2005
+ }, wn = (e) => {
2006
+ let t = e.items?.data ?? [];
2007
+ return t.find((e) => e.price?.recurring?.usage_type !== "metered") ?? t[0];
2008
+ }, Tn = (e) => {
2009
+ let t = wn(e)?.price;
1110
2010
  return typeof t?.id == "string" ? t.id : null;
1111
- }, Q = (e, t) => {
1112
- let n = e.metadata?.plan;
1113
- if (typeof n == "string" && n.length > 0) return n;
1114
- let r = Ft(e);
1115
- return r !== null && t ? t(r) : null;
1116
- }, It = (e, t) => {
1117
- let n = X(e.created);
2011
+ }, En = (e) => typeof e == "string" ? e : typeof e?.id == "string" ? e.id : null, Dn = (e, t) => {
2012
+ let n = Tn(e), r = n !== null && t ? t(n) : null;
2013
+ if (r !== null) return r;
2014
+ let i = e.metadata?.plan;
2015
+ return typeof i == "string" && i.length > 0 ? i : null;
2016
+ }, On = [
2017
+ "checkout.session.completed",
2018
+ "customer.created",
2019
+ "customer.deleted",
2020
+ "customer.subscription.created",
2021
+ "customer.subscription.deleted",
2022
+ "customer.subscription.paused",
2023
+ "customer.subscription.resumed",
2024
+ "customer.subscription.updated",
2025
+ "invoice.paid",
2026
+ "invoice.payment_failed"
2027
+ ], kn = (e, t) => {
2028
+ let n = Q(e.created);
1118
2029
  switch (e.type) {
1119
2030
  case "customer.subscription.created":
1120
2031
  case "customer.subscription.updated":
1121
2032
  case "customer.subscription.resumed":
1122
2033
  case "customer.subscription.paused": {
1123
- let r = e.data.object, i = Z(r), a = Q(r, t), o = Pt[r.status];
2034
+ let r = e.data.object, i = $(r), a = Dn(r, t), o = Cn[r.status];
1124
2035
  if (i === null || a === null || o === void 0) return null;
1125
- let s = Mt(r);
2036
+ let s = xn(r);
1126
2037
  return {
1127
2038
  _tag: "subscriptionUpserted",
1128
2039
  tenantId: i,
1129
2040
  providerSubscriptionId: r.id,
1130
2041
  plan: a,
1131
2042
  status: o,
1132
- quantity: Nt(r),
2043
+ quantity: Sn(r),
1133
2044
  currentPeriodStart: s.start,
1134
2045
  currentPeriodEnd: s.end,
1135
- cancelAt: X(r.cancel_at),
2046
+ cancelAt: Q(r.cancel_at),
1136
2047
  occurredAt: n
1137
2048
  };
1138
2049
  }
1139
2050
  case "customer.subscription.deleted": {
1140
- let t = e.data.object, r = Z(t);
2051
+ let t = e.data.object, r = $(t);
1141
2052
  return r === null ? null : {
1142
2053
  _tag: "subscriptionCanceled",
1143
2054
  tenantId: r,
@@ -1147,7 +2058,7 @@ var k = (e, t, n, r) => ({
1147
2058
  }
1148
2059
  case "invoice.paid":
1149
2060
  case "invoice.payment_failed": {
1150
- let t = e.data.object, r = Z(t);
2061
+ let t = e.data.object, r = $(t) ?? $({ metadata: t.parent?.subscription_details?.metadata ?? null });
1151
2062
  return r === null || typeof t.id != "string" ? null : {
1152
2063
  _tag: e.type === "invoice.paid" ? "invoicePaid" : "invoicePaymentFailed",
1153
2064
  tenantId: r,
@@ -1157,8 +2068,26 @@ var k = (e, t, n, r) => ({
1157
2068
  occurredAt: n
1158
2069
  };
1159
2070
  }
2071
+ case "checkout.session.completed": {
2072
+ let t = e.data.object, r = $(t), i = En(t.customer);
2073
+ return r === null || i === null ? null : {
2074
+ _tag: "customerLinked",
2075
+ tenantId: r,
2076
+ providerCustomerId: i,
2077
+ occurredAt: n
2078
+ };
2079
+ }
2080
+ case "customer.deleted": {
2081
+ let t = e.data.object;
2082
+ return {
2083
+ _tag: "customerUnlinked",
2084
+ tenantId: $(t),
2085
+ providerCustomerId: t.id,
2086
+ occurredAt: n
2087
+ };
2088
+ }
1160
2089
  case "customer.created": {
1161
- let t = e.data.object, r = Z(t);
2090
+ let t = e.data.object, r = $(t);
1162
2091
  return r === null ? null : {
1163
2092
  _tag: "customerLinked",
1164
2093
  tenantId: r,
@@ -1168,30 +2097,56 @@ var k = (e, t, n, r) => ({
1168
2097
  }
1169
2098
  default: return null;
1170
2099
  }
1171
- }, Lt = {
2100
+ }, An = {
1172
2101
  prorate: "create_prorations",
1173
2102
  none: "none",
1174
2103
  always_invoice: "always_invoice"
1175
- }, Rt = (t) => {
1176
- let n = Ot(t), r = t.planForPriceId, i = (e) => n.subscriptions.retrieve(e, { expand: ["items.data.price"] }), a = (e, t) => {
1177
- let n = Mt(e), i = e.items?.data?.[0]?.price;
2104
+ }, jn = (t) => {
2105
+ let n = gn(t), r = t.planForPriceId, i = (e) => {
2106
+ let t = xn(e);
1178
2107
  return {
1179
- plan: Q(e, r) ?? "free",
1180
- quantity: Nt(e),
2108
+ status: Cn[e.status] ?? "incomplete",
2109
+ plan: Dn(e, r),
2110
+ quantity: Sn(e),
2111
+ currentPeriodStart: t.start,
2112
+ currentPeriodEnd: t.end,
2113
+ cancelAt: Q(e.cancel_at)
2114
+ };
2115
+ }, a = (e) => ({
2116
+ ...i(e),
2117
+ providerSubscriptionId: e.id,
2118
+ providerCustomerId: En(e.customer),
2119
+ tenantId: $(e),
2120
+ priceIds: (e.items?.data ?? []).map((e) => e.price?.id).filter((e) => typeof e == "string")
2121
+ }), o = t.closeSettleSeconds ?? 600, s = {
2122
+ periodEnd: (e) => n.subscriptions.update(e, {
2123
+ cancel_at_period_end: !0,
2124
+ expand: ["items.data.price"]
2125
+ }),
2126
+ close: (e) => n.subscriptions.update(e, {
2127
+ cancel_at: Math.ceil(Date.now() / 1e3) + o,
2128
+ proration_behavior: "create_prorations",
2129
+ expand: ["items.data.price"]
2130
+ })
2131
+ }, c = (e) => n.subscriptions.retrieve(e, { expand: ["items.data.price"] }), l = (e, t) => {
2132
+ let n = xn(e), i = wn(e)?.price;
2133
+ return {
2134
+ plan: Dn(e, r) ?? "free",
2135
+ quantity: Sn(e),
1181
2136
  prorationMinor: t,
1182
2137
  currency: (i?.currency ?? "usd").toLowerCase(),
1183
2138
  currentPeriodStart: n.start,
1184
2139
  currentPeriodEnd: n.end
1185
2140
  };
1186
- }, o = (e, t) => {
1187
- let n = e.items.data[0]?.id;
2141
+ }, u = (e, t) => {
2142
+ let n = wn(e)?.id;
1188
2143
  return {
1189
2144
  items: [{
1190
2145
  ...n === void 0 ? {} : { id: n },
1191
2146
  ...t.priceId === void 0 ? {} : { price: t.priceId },
1192
2147
  ...t.quantity === void 0 ? {} : { quantity: t.quantity }
1193
2148
  }],
1194
- proration_behavior: Lt[t.proration ?? "prorate"],
2149
+ proration_behavior: An[t.proration ?? "prorate"],
1195
2150
  ...t.priceId !== void 0 && r ? { metadata: {
1196
2151
  ...e.metadata,
1197
2152
  plan: r(t.priceId) ?? ""
@@ -1201,7 +2156,8 @@ var k = (e, t, n, r) => ({
1201
2156
  return {
1202
2157
  name: "stripe",
1203
2158
  supportsMeteredUsage: t.supportsMeteredUsage ?? !0,
1204
- createCheckoutSession: (e) => Y("stripe.checkout.sessions.create", async () => {
2159
+ usageRequiresCustomer: !0,
2160
+ createCheckoutSession: (e) => Z("stripe.checkout.sessions.create", async () => {
1205
2161
  let t = await n.checkout.sessions.create({
1206
2162
  mode: "subscription",
1207
2163
  line_items: [{
@@ -1212,7 +2168,7 @@ var k = (e, t, n, r) => ({
1212
2168
  minimum: e.adjustableQuantity.min,
1213
2169
  maximum: e.adjustableQuantity.max
1214
2170
  } } : {}
1215
- }],
2171
+ }, ...(e.meteredPriceIds ?? []).map((e) => ({ price: e }))],
1216
2172
  success_url: e.successUrl,
1217
2173
  cancel_url: e.cancelUrl,
1218
2174
  client_reference_id: e.tenantId,
@@ -1233,59 +2189,100 @@ var k = (e, t, n, r) => ({
1233
2189
  if (typeof t.url != "string") throw Error("checkout session has no url");
1234
2190
  return { url: t.url };
1235
2191
  }),
1236
- createPortalSession: (e) => Y("stripe.billingPortal.sessions.create", async () => ({ url: (await n.billingPortal.sessions.create({
2192
+ createPortalSession: (e) => Z("stripe.billingPortal.sessions.create", async () => ({ url: (await n.billingPortal.sessions.create({
1237
2193
  customer: e.providerCustomerId,
1238
2194
  return_url: e.returnUrl
1239
2195
  })).url })),
1240
- reportUsage: (e) => Y("stripe.billing.meterEvents.create", async () => {
1241
- await n.billing.meterEvents.create({
1242
- event_name: e.entitlementKey,
1243
- identifier: `${e.tenantId}:${e.entitlementKey}:${e.periodEnd?.toISOString() ?? "current"}`,
1244
- payload: {
1245
- value: String(e.quantity),
1246
- stripe_customer_id: e.tenantId
2196
+ reportUsage: (t) => {
2197
+ let r = t.providerCustomerId;
2198
+ return r === void 0 ? v.fail(new e({
2199
+ source: "stripe.billing.meterEvents.create",
2200
+ message: `no Stripe customer linked for tenant '${t.tenantId}'`,
2201
+ transient: !1
2202
+ })) : Z("stripe.billing.meterEvents.create", async () => {
2203
+ try {
2204
+ await n.billing.meterEvents.create({
2205
+ event_name: t.entitlementKey,
2206
+ identifier: t.idempotencyKey ?? `${t.tenantId}:${t.entitlementKey}:${t.periodEnd?.toISOString() ?? "current"}`,
2207
+ payload: {
2208
+ value: String(t.quantity),
2209
+ stripe_customer_id: r
2210
+ }
2211
+ });
2212
+ } catch (e) {
2213
+ if (Mn(e)) return;
2214
+ throw e;
1247
2215
  }
1248
2216
  });
2217
+ },
2218
+ updateSubscription: (e) => Z("stripe.subscriptions.update", async () => {
2219
+ let t = await c(e.providerSubscriptionId), r = await n.subscriptions.update(e.providerSubscriptionId, u(t, e)), i = await Fn(n, r);
2220
+ return l(r, i);
1249
2221
  }),
1250
- updateSubscription: (e) => Y("stripe.subscriptions.update", async () => {
1251
- let t = await i(e.providerSubscriptionId), r = await n.subscriptions.update(e.providerSubscriptionId, o(t, e)), s = await Bt(n, r);
1252
- return a(r, s);
1253
- }),
1254
- previewSubscriptionChange: (e) => Y("stripe.invoices.createPreview", async () => {
1255
- let t = await i(e.providerSubscriptionId), s = o(t, e), c = (await n.invoices.createPreview({
2222
+ previewSubscriptionChange: (e) => Z("stripe.invoices.createPreview", async () => {
2223
+ let t = await c(e.providerSubscriptionId), i = u(t, e), a = (await n.invoices.createPreview({
1256
2224
  subscription: e.providerSubscriptionId,
1257
2225
  subscription_details: {
1258
- items: s.items,
1259
- ...s.proration_behavior === void 0 ? {} : { proration_behavior: s.proration_behavior }
2226
+ items: i.items,
2227
+ ...i.proration_behavior === void 0 ? {} : { proration_behavior: i.proration_behavior }
1260
2228
  }
1261
2229
  })).lines.data.filter((e) => e.parent?.subscription_item_details?.proration === !0).reduce((e, t) => e + t.amount, 0);
1262
2230
  return {
1263
- ...a(t, c),
2231
+ ...l(t, a),
1264
2232
  ...e.quantity === void 0 ? {} : { quantity: e.quantity },
1265
2233
  ...e.priceId !== void 0 && r ? { plan: r(e.priceId) ?? "free" } : {}
1266
2234
  };
1267
2235
  }),
1268
- fetchSubscription: (e) => Y("stripe.subscriptions.retrieve", async () => {
2236
+ fetchSubscription: (e) => Z("stripe.subscriptions.retrieve", async () => {
1269
2237
  try {
1270
- let t = await i(e), n = Mt(t);
1271
- return {
1272
- status: Pt[t.status] ?? "incomplete",
1273
- plan: Q(t, r),
1274
- quantity: Nt(t),
1275
- currentPeriodStart: n.start,
1276
- currentPeriodEnd: n.end,
1277
- cancelAt: X(t.cancel_at)
1278
- };
2238
+ return i(await c(e));
1279
2239
  } catch (e) {
1280
- if (e instanceof D.errors.StripeInvalidRequestError && e.statusCode === 404) return null;
2240
+ if (e instanceof F.errors.StripeInvalidRequestError && e.statusCode === 404) return null;
1281
2241
  throw e;
1282
2242
  }
1283
2243
  }),
1284
- customerEmail: (e) => Y("stripe.customers.retrieve", async () => {
2244
+ listSubscriptions: (e) => Z("stripe.subscriptions.list", async () => {
2245
+ let t = [];
2246
+ for await (let r of n.subscriptions.list({
2247
+ limit: 100,
2248
+ expand: ["data.items.data.price"],
2249
+ ...e.providerCustomerId === void 0 ? {} : { customer: e.providerCustomerId }
2250
+ })) t.push(a(r));
2251
+ return t;
2252
+ }),
2253
+ cancelSubscription: (e) => Z("stripe.subscriptions.cancel", async () => i(await s[e.mode](e.providerSubscriptionId))),
2254
+ resumeSubscription: (e) => Z("stripe.subscriptions.update", async () => i(await n.subscriptions.update(e, {
2255
+ cancel_at_period_end: !1,
2256
+ expand: ["items.data.price"]
2257
+ }))),
2258
+ ensureCustomer: (e) => Z("stripe.customers.create", async () => {
2259
+ let t = (await n.customers.search({
2260
+ query: `metadata['tenantId']:'${e.tenantId.replace(/['\\]/g, "")}'`,
2261
+ limit: 2
2262
+ })).data.find((t) => t.metadata?.tenantId === e.tenantId);
2263
+ return t === void 0 ? (await n.customers.create({ metadata: { tenantId: e.tenantId } }, { idempotencyKey: `voltro-billing-customer:${e.tenantId}` })).id : t.id;
2264
+ }),
2265
+ expireOpenCheckouts: (e) => Z("stripe.checkout.sessions.expire", async () => {
2266
+ let t = 0;
2267
+ for await (let r of n.checkout.sessions.list({
2268
+ customer: e.providerCustomerId,
2269
+ status: "open",
2270
+ limit: 100
2271
+ })) await n.checkout.sessions.expire(r.id), t += 1;
2272
+ return t;
2273
+ }),
2274
+ addSubscriptionPrices: (e) => Z("stripe.subscriptionItems.create", async () => {
2275
+ for (let t of e.priceIds) await n.subscriptionItems.create({
2276
+ subscription: e.providerSubscriptionId,
2277
+ price: t,
2278
+ proration_behavior: "none"
2279
+ });
2280
+ }),
2281
+ customerEmail: (e) => Z("stripe.customers.retrieve", async () => {
1285
2282
  let t = await n.customers.retrieve(e);
1286
2283
  return t.deleted === !0 ? null : typeof t.email == "string" && t.email.length > 0 ? t.email : null;
1287
2284
  }),
1288
- listInvoices: (e) => Y("stripe.invoices.list", async () => (await n.invoices.list({
2285
+ listInvoices: (e) => Z("stripe.invoices.list", async () => (await n.invoices.list({
1289
2286
  customer: e.providerCustomerId,
1290
2287
  limit: e.limit ?? 20
1291
2288
  })).data.map((e) => ({
@@ -1293,11 +2290,11 @@ var k = (e, t, n, r) => ({
1293
2290
  amountMinor: e.amount_due,
1294
2291
  currency: e.currency,
1295
2292
  status: e.status ?? "unknown",
1296
- createdAt: X(e.created),
2293
+ createdAt: Q(e.created),
1297
2294
  hostedUrl: e.hosted_invoice_url ?? null,
1298
2295
  pdfUrl: e.invoice_pdf ?? null
1299
2296
  }))),
1300
- verifyWebhook: (t) => p.try({
2297
+ verifyWebhook: (t) => v.try({
1301
2298
  try: () => n.webhooks.constructEvent(Buffer.from(t.rawBody), t.signatureHeader, t.secret),
1302
2299
  catch: (t) => new e({
1303
2300
  source: "stripe.webhooks.constructEvent",
@@ -1305,36 +2302,116 @@ var k = (e, t, n, r) => ({
1305
2302
  transient: !1
1306
2303
  })
1307
2304
  }),
1308
- normalizeEvent: (e) => p.succeed(zt(e) ? It(e, r) : null)
2305
+ eventContext: (e) => Pn(e) ? {
2306
+ providerCustomerId: Nn(e.data.object),
2307
+ createdAt: Q(e.created)
2308
+ } : {
2309
+ providerCustomerId: null,
2310
+ createdAt: null
2311
+ },
2312
+ listUndeliveredEvents: () => Z("stripe.events.list", async () => {
2313
+ let e = [];
2314
+ for await (let t of n.events.list({
2315
+ delivery_success: !1,
2316
+ types: [...On],
2317
+ limit: 100
2318
+ })) e.push({
2319
+ id: t.id,
2320
+ providerCustomerId: Nn(t.data.object),
2321
+ createdAt: Q(t.created)
2322
+ });
2323
+ return e;
2324
+ }),
2325
+ normalizeEvent: (e) => v.succeed(Pn(e) ? kn(e, r) : null)
1309
2326
  };
1310
- }, zt = (e) => typeof e == "object" && !!e && typeof e.type == "string" && typeof e.data == "object", Bt = async (e, t) => {
2327
+ }, Mn = (e) => e instanceof F.errors.StripeInvalidRequestError && typeof e.message == "string" && e.message.startsWith("An event already exists with identifier"), Nn = (e) => {
2328
+ let t = e;
2329
+ return t.object === "customer" ? typeof t.id == "string" ? t.id : null : En(t.customer ?? null);
2330
+ }, Pn = (e) => typeof e == "object" && !!e && typeof e.type == "string" && typeof e.data == "object", Fn = async (e, t) => {
1311
2331
  try {
1312
2332
  return (await e.invoices.createPreview({ subscription: t.id })).lines.data.filter((e) => e.parent?.subscription_item_details?.proration === !0).reduce((e, t) => e + t.amount, 0);
1313
2333
  } catch {
1314
2334
  return 0;
1315
2335
  }
1316
- }, Vt = (e) => {
1317
- let t = e?.baseUrl ?? "https://mock.billing.local", n = [], r = [], i = [], a = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Map(), s = (e) => typeof e == "object" && !!e && typeof e._tag == "string";
2336
+ }, In = (e) => {
2337
+ let t = e?.baseUrl ?? "https://mock.billing.local", n = [], r = [], i = [], a = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map(), c = [], l = [], u = [], d = [], f = [], p = [], m = /* @__PURE__ */ new Map(), h = [], g = 0, _ = (e, t) => {
2338
+ let n = {
2339
+ ...a.get(e) ?? {
2340
+ status: "active",
2341
+ plan: null,
2342
+ quantity: 1,
2343
+ currentPeriodStart: null,
2344
+ currentPeriodEnd: null,
2345
+ cancelAt: null
2346
+ },
2347
+ ...t
2348
+ };
2349
+ a.set(e, n);
2350
+ let r = o.get(e);
2351
+ return r !== void 0 && o.set(e, {
2352
+ ...r,
2353
+ ...t
2354
+ }), n;
2355
+ }, y = (e) => typeof e == "object" && !!e && typeof e._tag == "string";
1318
2356
  return {
1319
2357
  name: "mock",
1320
2358
  supportsMeteredUsage: e?.supportsMeteredUsage ?? !0,
1321
2359
  pushed: n,
1322
2360
  updates: r,
1323
2361
  fetched: i,
2362
+ cancels: c,
2363
+ resumes: l,
2364
+ priceAdds: u,
2365
+ checkouts: d,
2366
+ customersCreated: f,
2367
+ expiredFor: p,
2368
+ setUndelivered: (e) => {
2369
+ h = e;
2370
+ },
2371
+ undeliveredReads: () => g,
2372
+ listUndeliveredEvents: () => v.sync(() => (g += 1, h)),
2373
+ ensureCustomer: (e) => v.sync(() => {
2374
+ let t = m.get(e.tenantId);
2375
+ if (t !== void 0) return t;
2376
+ let n = `mock_cus_${e.tenantId}`;
2377
+ return m.set(e.tenantId, n), f.push(e.tenantId), n;
2378
+ }),
2379
+ expireOpenCheckouts: (e) => v.sync(() => (p.push(e.providerCustomerId), 0)),
2380
+ listSubscription: (e) => {
2381
+ o.set(e.providerSubscriptionId, e), a.set(e.providerSubscriptionId, e);
2382
+ },
1324
2383
  setSubscription: (e, t) => {
1325
2384
  t === null ? a.delete(e) : a.set(e, t);
1326
2385
  },
1327
2386
  setCustomerEmail: (e, t) => {
1328
- t === null ? o.delete(e) : o.set(e, t);
2387
+ t === null ? s.delete(e) : s.set(e, t);
1329
2388
  },
1330
- fetchSubscription: (e) => p.sync(() => (i.push(e), a.get(e) ?? null)),
1331
- customerEmail: (e) => p.sync(() => o.get(e) ?? null),
1332
- createCheckoutSession: (e) => p.succeed({ url: `${t}/checkout?tenant=${encodeURIComponent(e.tenantId)}&price=${encodeURIComponent(e.priceId)}` }),
1333
- createPortalSession: (e) => p.succeed({ url: `${t}/portal?customer=${encodeURIComponent(e.providerCustomerId)}` }),
1334
- reportUsage: (e) => p.sync(() => {
2389
+ fetchSubscription: (e) => v.sync(() => (i.push(e), a.get(e) ?? null)),
2390
+ listSubscriptions: (e) => v.sync(() => [...o.values()].filter((t) => t.status !== "canceled" && (e.providerCustomerId === void 0 || t.providerCustomerId === e.providerCustomerId))),
2391
+ cancelSubscription: (e) => v.sync(() => {
2392
+ c.push({
2393
+ providerSubscriptionId: e.providerSubscriptionId,
2394
+ mode: e.mode
2395
+ });
2396
+ let t = a.get(e.providerSubscriptionId);
2397
+ return e.mode === "close" ? _(e.providerSubscriptionId, { cancelAt: new Date(Date.now() + 6e5) }) : _(e.providerSubscriptionId, { cancelAt: t?.currentPeriodEnd ?? /* @__PURE__ */ new Date(0) });
2398
+ }),
2399
+ resumeSubscription: (e) => v.sync(() => (l.push(e), _(e, { cancelAt: null }))),
2400
+ addSubscriptionPrices: (e) => v.sync(() => {
2401
+ u.push({
2402
+ providerSubscriptionId: e.providerSubscriptionId,
2403
+ priceIds: e.priceIds
2404
+ });
2405
+ let t = o.get(e.providerSubscriptionId);
2406
+ t !== void 0 && _(e.providerSubscriptionId, { priceIds: [...t.priceIds, ...e.priceIds] });
2407
+ }),
2408
+ customerEmail: (e) => v.sync(() => s.get(e) ?? null),
2409
+ createCheckoutSession: (e) => v.sync(() => (d.push(e), { url: `${t}/checkout?tenant=${encodeURIComponent(e.tenantId)}&price=${encodeURIComponent(e.priceId)}` })),
2410
+ createPortalSession: (e) => v.succeed({ url: `${t}/portal?customer=${encodeURIComponent(e.providerCustomerId)}` }),
2411
+ reportUsage: (e) => v.sync(() => {
1335
2412
  n.push(e);
1336
2413
  }),
1337
- updateSubscription: (e) => p.sync(() => (r.push(e), {
2414
+ updateSubscription: (e) => v.sync(() => (r.push(e), {
1338
2415
  plan: e.priceId ?? "mock-plan",
1339
2416
  quantity: e.quantity ?? 1,
1340
2417
  prorationMinor: 0,
@@ -1342,7 +2419,7 @@ var k = (e, t, n, r) => ({
1342
2419
  currentPeriodStart: null,
1343
2420
  currentPeriodEnd: null
1344
2421
  })),
1345
- previewSubscriptionChange: (e) => p.succeed({
2422
+ previewSubscriptionChange: (e) => v.succeed({
1346
2423
  plan: e.priceId ?? "mock-plan",
1347
2424
  quantity: e.quantity ?? 1,
1348
2425
  prorationMinor: 0,
@@ -1350,56 +2427,227 @@ var k = (e, t, n, r) => ({
1350
2427
  currentPeriodStart: null,
1351
2428
  currentPeriodEnd: null
1352
2429
  }),
1353
- listInvoices: () => p.succeed([]),
1354
- normalizeEvent: (e) => p.succeed(s(e) ? e : null)
2430
+ listInvoices: () => v.succeed([]),
2431
+ normalizeEvent: (e) => v.succeed(y(e) ? e : null)
1355
2432
  };
1356
- }, Ht = (e) => {
2433
+ }, Ln = /* @__PURE__ */ new Set([
2434
+ "",
2435
+ "development",
2436
+ "dev",
2437
+ "test"
2438
+ ]), Rn = (e) => !Ln.has((e ?? "").trim().toLowerCase()), zn = (e) => {
2439
+ if (Rn(e.env.NODE_ENV)) {
2440
+ if (e.resolvedName === "mock" && e.providerOption === void 0) throw Error("billingPlugin: no STRIPE_SECRET_KEY in a production boot — refusing to run the MOCK billing provider, which answers every charge and subscription call successfully and reaches nobody. Set STRIPE_SECRET_KEY, or declare `billingPlugin({ provider: 'mock' })` if this deployment does not sell.");
2441
+ if (e.resolvedName === "stripe") {
2442
+ if (e.webhookSecret === null || e.webhookSecret.trim() === "") throw Error("billingPlugin: Stripe in a production boot needs STRIPE_WEBHOOK_SECRET — without it no provider event can be verified, so subscriptions, cancellations and payments would never reach this app.");
2443
+ if (e.redirectOrigins === void 0 || e.redirectOrigins.length === 0) throw Error("billingPlugin: Stripe in a production boot needs `redirectOrigins` — the origins checkout and the customer portal may send a customer back to. Without it any URL a caller names is accepted.");
2444
+ }
2445
+ }
2446
+ }, Bn = (e) => {
1357
2447
  let { provider: t, env: n } = e;
1358
2448
  if (t !== void 0 && typeof t == "object") return t;
1359
- let r = t ?? (n.STRIPE_SECRET_KEY ? "stripe" : "mock");
1360
- if (r === "mock" && t === void 0 && n.NODE_ENV === "production" && e.log?.warn("billing: no STRIPE_SECRET_KEY and no explicit `provider` — running the MOCK billing provider. Every charge, subscription and invoice call will SUCCEED and reach nobody. Set STRIPE_SECRET_KEY, or declare `billingPlugin({ provider: 'mock' })` to say this is deliberate."), r === "stripe") {
2449
+ if ((t ?? (n.STRIPE_SECRET_KEY ? "stripe" : "mock")) === "stripe") {
1361
2450
  let t = e.apiKey ?? n.STRIPE_SECRET_KEY;
1362
2451
  if (!t) throw Error("billingPlugin: provider 'stripe' needs an apiKey (option or STRIPE_SECRET_KEY env)");
1363
- return Rt({
2452
+ return jn({
1364
2453
  apiKey: t,
1365
2454
  ...e.planForPriceId ? { planForPriceId: e.planForPriceId } : {}
1366
2455
  });
1367
2456
  }
1368
- return Vt();
1369
- }, Ut = E({ scope: "@voltro/plugin-billing" }), Wt = "billing", $ = "/billing/webhook", Gt = (e) => fe({
1370
- id: Wt,
1371
- path: $,
1372
- provider: O(),
1373
- payload: ne.Unknown,
2457
+ return In();
2458
+ }, Vn = (e) => e.name === "mock" ? [] : [{
2459
+ destination: e.name === "stripe" ? "api.stripe.com" : `runtime:the '${e.name}' billing provider`,
2460
+ purpose: "customers, subscriptions, checkout and metered usage",
2461
+ data: ["personal", "metadata"],
2462
+ disable: "without STRIPE_SECRET_KEY the mock provider answers, and nothing leaves (production refuses to boot on it)"
2463
+ }], Hn = P({ scope: "@voltro/plugin-billing" }), Un = "billing", Wn = "/billing/webhook", Gn = (e) => I({
2464
+ id: Un,
2465
+ path: Wn,
2466
+ provider: se(),
2467
+ payload: x.Unknown,
1374
2468
  handler: async (t) => {
1375
- let n = e.provider.verifyWebhook !== void 0 && e.webhookSecret !== null && e.webhookSecret !== void 0 ? await p.runPromise(e.provider.verifyWebhook({
2469
+ let n = e.provider.verifyWebhook !== void 0 && e.webhookSecret !== null && e.webhookSecret !== void 0 ? await v.runPromise(e.provider.verifyWebhook({
1376
2470
  rawBody: t.rawBody,
1377
2471
  signatureHeader: t.headers["stripe-signature"] ?? "",
1378
2472
  secret: e.webhookSecret ?? ""
1379
- })) : t.body, r = await p.runPromise(e.provider.normalizeEvent(n));
1380
- if (r === null) return;
1381
- await p.runPromise(e.service.applyEvent(r));
1382
- let i = e.onEvent?.[r._tag];
1383
- i !== void 0 && await p.runPromise(i(r).pipe(p.catchAllCause((e) => p.sync(() => {
1384
- Ut.warn("billing onEvent side effect failed", {
1385
- event: r._tag,
1386
- cause: String(e)
1387
- });
1388
- }))));
2473
+ })) : t.body, r = await v.runPromise(e.provider.normalizeEvent(n)), i = e.provider.eventContext?.(n) ?? {
2474
+ providerCustomerId: null,
2475
+ createdAt: null
2476
+ };
2477
+ if (r === null && i.providerCustomerId === null) return;
2478
+ let a = r?._tag === "subscriptionUpserted" || r?._tag === "subscriptionCanceled", o = i.providerCustomerId !== null && r?._tag !== "customerUnlinked", s = [];
2479
+ if (r !== null && !(a && o) && (await v.runPromise(e.service.applyEvent(r)), s.push(r)), o) {
2480
+ let t = await v.runPromise(e.service.syncCustomer(i.providerCustomerId, r !== null && "tenantId" in r && typeof r.tenantId == "string" ? r.tenantId : void 0));
2481
+ s.push(...t.events);
2482
+ }
2483
+ i.createdAt !== null && Date.now() - i.createdAt.getTime() > 3e5 && e.onLateDelivery?.();
2484
+ for (let t of s) {
2485
+ let n = e.onEvent?.[t._tag];
2486
+ n !== void 0 && await v.runPromise(n(t).pipe(v.tapErrorCause((e) => v.sync(() => {
2487
+ Hn.error("billing onEvent side effect failed — answering 500 so the provider redelivers", {
2488
+ event: t._tag,
2489
+ cause: String(e)
2490
+ });
2491
+ }))));
2492
+ }
1389
2493
  }
1390
- }), Kt = (e) => pe(Gt(e), {
2494
+ }), Kn = (e) => L(Gn(e), {
1391
2495
  resolveSecret: () => Promise.resolve(e.webhookSecret),
1392
2496
  ...e.idempotencyCache === void 0 ? {} : { idempotencyCache: e.idempotencyCache },
1393
2497
  log: (e) => {
1394
- e.status >= 400 && Ut.warn("billing webhook rejected", { ...e });
2498
+ e.status >= 400 && Hn.warn("billing webhook rejected", { ...e });
2499
+ }
2500
+ }), qn = (e) => e.filter((e) => e.applied !== !0 && (e.kind === "create" || e.kind === "update" || e.kind === "adopt" || e.kind === "drift")), Jn = async (e, t, n) => {
2501
+ let r = t.environments[n.env], i = await e.get("/v1/tax/settings"), a = n.env === "live";
2502
+ if (i.livemode !== a) throw Error(`the key reaches a ${i.livemode ? "LIVE" : "test"} account, not ${n.env}`);
2503
+ let o = [], s = {}, c = async (t, r) => {
2504
+ let i = "apply" in t ? t.apply : void 0;
2505
+ if (n.apply && i !== void 0) {
2506
+ let n = await e.post(i.path, i.params);
2507
+ r?.(n), o.push({
2508
+ ...t,
2509
+ applied: !0
2510
+ });
2511
+ } else o.push(t);
2512
+ }, l = await e.list("/v1/billing/meters"), u = /* @__PURE__ */ new Map();
2513
+ for (let e of t.meters) {
2514
+ let t = en(e, l);
2515
+ u.set(e.eventName, t.id), await c(t.finding, (t) => {
2516
+ u.set(e.eventName, t.id);
2517
+ });
2518
+ }
2519
+ let d = await e.list("/v1/products"), f = [];
2520
+ for (let e of t.products) {
2521
+ let t = Xt(e, d), n = {
2522
+ inPortal: e.inPortal,
2523
+ productId: t.id,
2524
+ priceId: null
2525
+ };
2526
+ await c(t.finding, (e) => {
2527
+ n.productId = e.id;
2528
+ }), f.push(n);
2529
+ }
2530
+ for (let [r, i] of t.products.entries()) {
2531
+ let t = f[r], a = i.price.meter;
2532
+ if (i.price.usageType === "metered" && (a === void 0 || !u.has(a))) {
2533
+ o.push({
2534
+ kind: "manual",
2535
+ object: `price ${i.price.lookupKey}`,
2536
+ detail: `metered, but names no catalog meter (${a ?? "none"})`
2537
+ });
2538
+ continue;
2539
+ }
2540
+ let l = a === void 0 ? null : u.get(a) ?? null, d = t.productId ?? (n.apply ? null : `pending:${i.key}`), p = l ?? (n.apply || i.price.usageType !== "metered" ? l : `pending:${a}`), m = (await e.list("/v1/prices", { lookup_keys: [i.price.lookupKey] }))[0] ?? null, h = t.productId === null ? [] : await e.list("/v1/prices", { product: t.productId }), g = $t(i.price, d, p, m, h, n.allowPriceChange);
2541
+ t.priceId = g.id, await c(g.finding, (e) => {
2542
+ t.priceId = e.id;
2543
+ }), t.priceId !== null && (s[i.price.envName] = t.priceId);
2544
+ }
2545
+ await c(tn(t.tax, i));
2546
+ let p = f.filter((e) => e.inPortal), m = p.every((e) => e.productId !== null && e.priceId !== null) ? p.map((e) => ({
2547
+ productId: e.productId,
2548
+ priceId: e.priceId
2549
+ })) : null, h = await e.list("/v1/billing_portal/configurations", { expand: ["data.features.subscription_update.products"] });
2550
+ for (let e of on(t.portal, r, m, h)) await c(e);
2551
+ let g = sn(r, await e.list("/v1/webhook_endpoints"));
2552
+ g.kind === "create" && n.apply && n.envOut === null ? o.push({
2553
+ kind: "manual",
2554
+ object: g.object,
2555
+ detail: "not created: pass --env-out so its signing secret is captured (Stripe shows it once)"
2556
+ }) : await c(g, (e) => {
2557
+ typeof e.secret == "string" && (s.STRIPE_WEBHOOK_SECRET = e.secret);
2558
+ });
2559
+ let _ = await e.get("/v1/account").catch(() => null), v = await e.list("/v1/tax_ids", { owner: { type: "account" } }).catch(() => null), y = await e.list("/v1/tax/registrations", { status: "active" }).catch(() => []);
2560
+ return o.push(...cn({
2561
+ catalog: t,
2562
+ env: r,
2563
+ chargesEnabled: _ === null ? null : _.charges_enabled === !0,
2564
+ accountTaxIds: v === null ? null : v.length,
2565
+ activeRegistrations: y.map((e) => e.country)
2566
+ })), {
2567
+ livemode: i.livemode,
2568
+ findings: o,
2569
+ envOut: s
2570
+ };
2571
+ }, Yn = (e, t) => {
2572
+ let n = new Set(Object.keys(t)), r = e.split("\n").filter((e) => !n.has(e.split("=")[0] ?? ""));
2573
+ for (; r.length > 0 && r[r.length - 1] === "";) r.pop();
2574
+ return [
2575
+ ...r,
2576
+ ...Object.entries(t).map(([e, t]) => `${e}=${t}`),
2577
+ ""
2578
+ ].join("\n");
2579
+ }, Xn = (e, t) => {
2580
+ try {
2581
+ return R("git", [...e], {
2582
+ cwd: t,
2583
+ stdio: "ignore"
2584
+ }), !0;
2585
+ } catch {
2586
+ return !1;
2587
+ }
2588
+ }, Zn = (e, t = Xn, n = (e) => B(e) ? le(e) : e) => {
2589
+ for (let r of /* @__PURE__ */ new Set([V(e), V(n(V(e)))])) {
2590
+ let n = de(r);
2591
+ if (t(["rev-parse", "--is-inside-work-tree"], n) && !t([
2592
+ "check-ignore",
2593
+ "-q",
2594
+ r
2595
+ ], n)) return `${e} is inside a git work tree and not ignored — write it to a gitignored file or outside the repo`;
2596
+ }
2597
+ return null;
2598
+ }, Qn = (e) => {
2599
+ let t = (t) => {
2600
+ let n = e.indexOf(t);
2601
+ return n < 0 ? null : e[n + 1];
2602
+ }, n = t("--env");
2603
+ if (n !== "sandbox" && n !== "live") return "--env sandbox|live is required";
2604
+ let r = t("--env-out");
2605
+ if (r === void 0 || r?.startsWith("--")) return "--env-out needs a file path";
2606
+ let i = {
2607
+ env: n,
2608
+ apply: e.includes("--apply"),
2609
+ allowPriceChange: e.includes("--allow-price-change"),
2610
+ confirmLive: e.includes("--confirm-live"),
2611
+ strict: e.includes("--strict"),
2612
+ envOut: r
2613
+ };
2614
+ return i.env === "live" && i.apply && !i.confirmLive ? "writing to LIVE needs --confirm-live (run without --apply first to see the plan)" : i;
2615
+ }, $n = (e, t, n) => {
2616
+ let r = t.STRIPE_PROVISION_KEY?.trim() || (e === "sandbox" ? n?.trim() : void 0);
2617
+ return r === void 0 || r.length === 0 ? { refusal: "set STRIPE_PROVISION_KEY (a sandbox run also reads STRIPE_SECRET_KEY from the dotenv file)" } : e === "live" && !/^(sk|rk)_live_/.test(r) ? { refusal: "--env live needs a live key (sk_live_/rk_live_)" } : e === "sandbox" && !/^(sk|rk)_test_/.test(r) ? { refusal: "--env sandbox needs a test key (sk_test_/rk_test_)" } : { key: r };
2618
+ }, er = (e, t) => B(e) ? ce(e, "utf8").split("\n").find((e) => e.startsWith(`${t}=`))?.slice(t.length + 1).trim() : void 0, tr = async (e, t, n = {}) => {
2619
+ let r = n.vars ?? process.env, i = (e) => (console.error(`stripe provision: ${e}`), 2), a = Qn(t);
2620
+ if (typeof a == "string") return i(a);
2621
+ if (a.envOut !== null) {
2622
+ let e = Zn(a.envOut);
2623
+ if (e !== null) return i(e);
2624
+ }
2625
+ let o = $n(a.env, r, n.dotenv === void 0 ? void 0 : er(n.dotenv, "STRIPE_SECRET_KEY"));
2626
+ if ("refusal" in o) return i(o.refusal);
2627
+ let s;
2628
+ try {
2629
+ s = await Jn((n.rest ?? fn)(o.key), e, a);
2630
+ } catch (e) {
2631
+ return console.error(`stripe provision: ${un(e instanceof Error ? e.message : String(e))}`), 1;
2632
+ }
2633
+ console.log(`Stripe ${a.env} (${s.livemode ? "LIVE" : "test mode"}) — ${a.apply ? "apply" : "check"}`);
2634
+ for (let e of s.findings) {
2635
+ let t = e.applied === !0 ? `${e.kind} ✓` : e.kind;
2636
+ console.log(` ${t.padEnd(10)} ${e.object}${"detail" in e ? ` — ${e.detail}` : ""}`);
1395
2637
  }
1396
- }), qt = (e) => e === null ? null : {
2638
+ if (a.envOut !== null) {
2639
+ let e = a.envOut;
2640
+ ue(e, Yn(B(e) ? ce(e, "utf8") : "", s.envOut), { mode: 384 }), z(e, 384), console.log(` wrote ${Object.keys(s.envOut).sort().join(", ")} to ${e}`);
2641
+ }
2642
+ let c = qn(s.findings), l = s.findings.filter((e) => e.kind === "manual");
2643
+ return console.log(` ${c.length} open, ${l.length} manual`), c.length > 0 || a.strict && l.length > 0 ? 1 : 0;
2644
+ }, nr = (e) => e === null ? null : {
1397
2645
  plan: e.plan,
1398
2646
  status: e.status,
1399
2647
  quantity: e.quantity,
1400
2648
  currentPeriodEnd: e.currentPeriodEnd ? e.currentPeriodEnd.toISOString() : null,
1401
2649
  cancelAt: e.cancelAt ? e.cancelAt.toISOString() : null
1402
- }, Jt = re([{
2650
+ }, rr = S([{
1403
2651
  name: "STRIPE_SECRET_KEY",
1404
2652
  required: !1,
1405
2653
  secret: !0,
@@ -1409,53 +2657,65 @@ var k = (e, t, n, r) => ({
1409
2657
  required: !1,
1410
2658
  secret: !0,
1411
2659
  description: "Stripe webhook signing secret (whsec_…) for verifying webhook payloads."
1412
- }]), Yt = "@voltro/plugin-billing", Xt = (t = {}) => {
1413
- let n = process.env, m = xt(t.plans ?? {}), h = Ht({
2660
+ }]), ir = "@voltro/plugin-billing", ar = (t = {}) => {
2661
+ let n = process.env, y = zt(t.plans ?? {}), b = Bn({
1414
2662
  ...t.provider === void 0 ? {} : { provider: t.provider },
1415
2663
  ...t.apiKey === void 0 ? {} : { apiKey: t.apiKey },
1416
- planForPriceId: m.planForPriceId,
1417
- log: { warn: (e) => {
1418
- console.warn(e);
1419
- } },
2664
+ planForPriceId: y.planForPriceId,
1420
2665
  env: n
1421
- }), ne = t.webhookSecret ?? Jt.read("STRIPE_WEBHOOK_SECRET") ?? null, re = L(t.dunning, n);
1422
- x({
2666
+ }), x = t.webhookSecret ?? rr.read("STRIPE_WEBHOOK_SECRET") ?? null;
2667
+ zn({
2668
+ env: n,
2669
+ providerOption: t.provider,
2670
+ resolvedName: b.name,
2671
+ webhookSecret: x,
2672
+ redirectOrigins: t.redirectOrigins
2673
+ });
2674
+ let S = ke(t.dunning, n);
2675
+ ie({
1423
2676
  source: "plugin",
1424
- table: P,
2677
+ table: G,
1425
2678
  timeColumn: "updatedAt",
1426
- ttlMs: S(process.env.VOLTRO_BILLING_USAGE_TTL_HOURS, 9600),
2679
+ ttlMs: ae(process.env.VOLTRO_BILLING_USAGE_TTL_HOURS, 9600),
1427
2680
  label: "billing usage counters"
1428
- }), x({
2681
+ }), ie({
1429
2682
  source: "plugin",
1430
- table: J,
2683
+ table: Vt,
1431
2684
  timeColumn: "claimedAt",
1432
- ttlMs: S(process.env.VOLTRO_BILLING_FLUSH_CLAIM_TTL_HOURS, 1),
2685
+ ttlMs: ae(process.env.VOLTRO_BILLING_FLUSH_CLAIM_TTL_HOURS, 1),
1433
2686
  label: "billing flush claims"
1434
- }), x({
2687
+ }), ie({
1435
2688
  source: "plugin",
1436
- table: F,
2689
+ table: Se,
1437
2690
  timeColumn: "sentAt",
1438
- ttlMs: S(process.env.VOLTRO_BILLING_DUNNING_TTL_HOURS, 9600),
2691
+ ttlMs: ae(process.env.VOLTRO_BILLING_DUNNING_TTL_HOURS, 9600),
1439
2692
  label: "billing dunning notices"
1440
2693
  });
1441
- let { service: g, holder: _ } = et({
1442
- provider: h,
1443
- plans: m,
2694
+ let { service: T, holder: E } = vt({
2695
+ provider: b,
2696
+ plans: y,
1444
2697
  ...t.attempts === void 0 ? {} : { attempts: t.attempts },
1445
2698
  ...t.checkout === void 0 ? {} : { checkout: t.checkout },
2699
+ ...t.admit === void 0 ? {} : { admit: t.admit },
2700
+ ...t.redirectOrigins === void 0 ? {} : { redirectOrigins: t.redirectOrigins },
2701
+ ...t.isTenantClosed === void 0 ? {} : { isTenantClosed: t.isTenantClosed },
2702
+ ...t.onEvent === void 0 ? {} : { onEvent: t.onEvent },
1446
2703
  ...t.resolveEntitlementLimit === void 0 ? {} : { resolveEntitlementLimit: t.resolveEntitlementLimit },
1447
- dunning: re
1448
- }), se = tt(g), ce = Kt({
1449
- provider: h,
1450
- service: g,
1451
- webhookSecret: ne,
1452
- ...t.onEvent === void 0 ? {} : { onEvent: t.onEvent }
1453
- }), v = {
2704
+ dunning: S
2705
+ }), te = yt(T), D = Kn({
2706
+ provider: b,
2707
+ service: T,
2708
+ webhookSecret: x,
2709
+ ...t.onEvent === void 0 ? {} : { onEvent: t.onEvent },
2710
+ onLateDelivery: () => {
2711
+ fe();
2712
+ }
2713
+ }), O = {
1454
2714
  method: "POST",
1455
- path: $,
2715
+ path: Wn,
1456
2716
  originGuard: "exempt",
1457
2717
  handle: async (e) => {
1458
- let t = await ce({
2718
+ let t = await D({
1459
2719
  method: e.method,
1460
2720
  path: e.path,
1461
2721
  headers: e.headers,
@@ -1468,12 +2728,22 @@ var k = (e, t, n, r) => ({
1468
2728
  ...t.headers === void 0 ? {} : { headers: { ...t.headers } }
1469
2729
  };
1470
2730
  }
1471
- }, le = [
2731
+ }, ne = (t) => ({
2732
+ ...t,
2733
+ execute: (n, r) => {
2734
+ let i = r.request.subject;
2735
+ return i.type === "anonymous" || typeof i.tenantId != "string" || i.tenantId === "" ? v.fail(new e({
2736
+ source: "billing",
2737
+ message: "billing routes answer only a signed-in caller that belongs to a tenant",
2738
+ transient: !1
2739
+ })) : t.execute(n, r);
2740
+ }
2741
+ }), k = [
1472
2742
  {
1473
- ...ee,
2743
+ ...h,
1474
2744
  execute: (e, t) => {
1475
2745
  let n = e;
1476
- return g.startCheckout({
2746
+ return T.startCheckout({
1477
2747
  tenantId: t.request.subject.tenantId ?? "",
1478
2748
  plan: n.plan,
1479
2749
  successUrl: n.successUrl,
@@ -1483,19 +2753,19 @@ var k = (e, t, n, r) => ({
1483
2753
  }
1484
2754
  },
1485
2755
  {
1486
- ...u,
2756
+ ...d,
1487
2757
  execute: (e, t) => {
1488
2758
  let n = e;
1489
- return g.portalUrl(t.request.subject.tenantId ?? "", n.returnUrl);
2759
+ return T.portalUrl(t.request.subject.tenantId ?? "", n.returnUrl);
1490
2760
  }
1491
2761
  },
1492
2762
  {
1493
- ...te,
1494
- execute: (e, t) => g.subscription(t.request.subject.tenantId ?? "").pipe(p.map((e) => ({ subscription: qt(e) })))
2763
+ ...g,
2764
+ execute: (e, t) => T.subscription(t.request.subject.tenantId ?? "").pipe(v.map((e) => ({ subscription: nr(e) })))
1495
2765
  },
1496
2766
  {
1497
- ...c,
1498
- execute: (e, t) => g.entitlementStatus(t.request.subject.tenantId ?? "").pipe(p.map((e) => ({
2767
+ ...l,
2768
+ execute: (e, t) => T.entitlementStatus(t.request.subject.tenantId ?? "").pipe(v.map((e) => ({
1499
2769
  plan: e.plan,
1500
2770
  billedPlan: e.billedPlan,
1501
2771
  status: e.status,
@@ -1507,13 +2777,13 @@ var k = (e, t, n, r) => ({
1507
2777
  })))
1508
2778
  },
1509
2779
  {
1510
- ...d,
2780
+ ...f,
1511
2781
  execute: (e, t) => {
1512
2782
  let n = e;
1513
- return g.previewChange(t.request.subject.tenantId ?? "", {
2783
+ return T.previewChange(t.request.subject.tenantId ?? "", {
1514
2784
  ...n.plan === void 0 ? {} : { plan: n.plan },
1515
2785
  ...n.quantity === void 0 ? {} : { quantity: n.quantity }
1516
- }).pipe(p.map((e) => ({
2786
+ }).pipe(v.map((e) => ({
1517
2787
  plan: e.plan,
1518
2788
  quantity: e.quantity,
1519
2789
  prorationMinor: e.prorationMinor,
@@ -1522,24 +2792,24 @@ var k = (e, t, n, r) => ({
1522
2792
  }
1523
2793
  },
1524
2794
  {
1525
- ...l,
1526
- execute: (e, t) => g.invoices(t.request.subject.tenantId ?? "").pipe(p.map((e) => ({ invoices: e.map((e) => ({
2795
+ ...u,
2796
+ execute: (e, t) => T.invoices(t.request.subject.tenantId ?? "").pipe(v.map((e) => ({ invoices: e.map((e) => ({
1527
2797
  ...e,
1528
2798
  createdAt: e.createdAt?.toISOString() ?? null
1529
2799
  })) })))
1530
2800
  },
1531
2801
  {
1532
- ...f,
2802
+ ...p,
1533
2803
  execute: (e, t) => {
1534
2804
  let n = e;
1535
- return g.reportUsage(t.request.subject.tenantId ?? "", n.key, n.quantity).pipe(p.map(() => ({ ok: !0 })));
2805
+ return T.reportUsage(t.request.subject.tenantId ?? "", n.key, n.quantity).pipe(v.map(() => ({ ok: !0 })));
1536
2806
  }
1537
2807
  },
1538
2808
  {
1539
- ...o,
2809
+ ...s,
1540
2810
  execute: (e, t) => {
1541
2811
  let n = e;
1542
- return g.changePlan(t.request.subject.tenantId ?? "", n.plan).pipe(p.map((e) => ({
2812
+ return T.changePlan(t.request.subject.tenantId ?? "", n.plan).pipe(v.map((e) => ({
1543
2813
  plan: e.plan,
1544
2814
  quantity: e.quantity,
1545
2815
  prorationMinor: e.prorationMinor,
@@ -1548,34 +2818,46 @@ var k = (e, t, n, r) => ({
1548
2818
  }
1549
2819
  },
1550
2820
  {
1551
- ...s,
2821
+ ...c,
1552
2822
  execute: (e, t) => {
1553
2823
  let n = e;
1554
- return g.changeSeats(t.request.subject.tenantId ?? "", n.quantity).pipe(p.map((e) => ({
2824
+ return T.changeSeats(t.request.subject.tenantId ?? "", n.quantity).pipe(v.map((e) => ({
1555
2825
  plan: e.plan,
1556
2826
  quantity: e.quantity,
1557
2827
  prorationMinor: e.prorationMinor,
1558
2828
  currency: e.currency
1559
2829
  })));
1560
2830
  }
2831
+ },
2832
+ {
2833
+ ...o,
2834
+ execute: (e, t) => T.cancelSubscription(t.request.subject.tenantId ?? "").pipe(v.map((e) => ({ subscription: nr(e) })))
2835
+ },
2836
+ {
2837
+ ..._,
2838
+ execute: (e, t) => T.syncTenant(t.request.subject.tenantId ?? "").pipe(v.map((e) => ({ subscription: e === null ? null : nr(e) })))
2839
+ },
2840
+ {
2841
+ ...m,
2842
+ execute: (e, t) => T.resumeSubscription(t.request.subject.tenantId ?? "").pipe(v.map((e) => ({ subscription: nr(e) })))
1561
2843
  }
1562
- ], y = (e, t) => ({
2844
+ ].map(ne), A = (e, t) => ({
1563
2845
  kind: "json",
1564
2846
  status: e,
1565
2847
  data: { error: t }
1566
- }), b = [{
2848
+ }), re = [{
1567
2849
  method: "GET",
1568
2850
  path: "/stats",
1569
2851
  scope: "declaration",
1570
2852
  description: "Billing config: provider, declared plans, enforced tags",
1571
- handler: () => p.succeed({
2853
+ handler: () => v.succeed({
1572
2854
  kind: "json",
1573
2855
  data: {
1574
- provider: h.name,
1575
- supportsMeteredUsage: h.supportsMeteredUsage,
1576
- plans: m.planIds(),
2856
+ provider: b.name,
2857
+ supportsMeteredUsage: b.supportsMeteredUsage,
2858
+ plans: y.planIds(),
1577
2859
  enforce: t.enforce ? Object.keys(t.enforce) : [],
1578
- webhookPath: $
2860
+ webhookPath: Wn
1579
2861
  }
1580
2862
  })
1581
2863
  }, {
@@ -1583,11 +2865,11 @@ var k = (e, t, n, r) => ({
1583
2865
  path: "/usage",
1584
2866
  scope: "process",
1585
2867
  description: "Pending (un-flushed) metered usage counters",
1586
- handler: () => _.stores.usage.pending().pipe(p.map((e) => ({
2868
+ handler: () => E.stores.usage.pending().pipe(v.map((e) => ({
1587
2869
  kind: "json",
1588
2870
  data: { usage: e }
1589
- })), p.catchAll((e) => p.succeed(y(500, e.message))))
1590
- }], ue = [
2871
+ })), v.catchAll((e) => v.succeed(A(500, `${e.source} failed`))))
2872
+ }], j = [
1591
2873
  {
1592
2874
  schema: e,
1593
2875
  import: {
@@ -1609,82 +2891,126 @@ var k = (e, t, n, r) => ({
1609
2891
  name: "SubscriptionLocked"
1610
2892
  }
1611
2893
  }
1612
- ], C = t.enforce, w = C !== void 0 && Object.keys(C).length > 0 ? ge(C, g) : void 0, T = t.metering;
1613
- T !== void 0 && rt(T);
1614
- let E = T !== void 0 && Object.keys(T).length > 0 ? ft(g, T) : void 0, D = T !== void 0 && yt(T), fe = { claim: void 0 }, pe = T === void 0 ? void 0 : ht(g, T, {
1615
- claimChange: () => fe.claim,
2894
+ ], M = t.enforce, N = M !== void 0 && Object.keys(M).length > 0 ? ge(M, T) : void 0, P = t.metering;
2895
+ P !== void 0 && xt(P);
2896
+ let F = P !== void 0 && Object.keys(P).length > 0 ? At(T, P) : void 0, I = P !== void 0 && Lt(P), L = { claim: void 0 }, se = P === void 0 ? void 0 : Nt(T, P, {
2897
+ claimChange: () => L.claim,
1616
2898
  onUngated: () => {
1617
- p.runSync(p.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(p.annotateLogs({ scope: "voltro:billing" })));
2899
+ v.runSync(v.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(v.annotateLogs({ scope: "voltro:billing" })));
1618
2900
  }
1619
- }), O = ie([E, w].filter((e) => e !== void 0)), k, A, me = [
2901
+ }), R = C([F, N].filter((e) => e !== void 0)), z, B, ce = !1, le = !1, ue = !1, de = 0, V = {
2902
+ info: (e, t) => {
2903
+ console.info(e, t ?? {});
2904
+ },
2905
+ error: (e, t) => {
2906
+ console.error(e, t ?? {});
2907
+ }
2908
+ }, fe = async () => {
2909
+ let e = z, n = b.listUndeliveredEvents;
2910
+ if (!(e === void 0 || n === void 0 || ue) && !(Date.now() - de < 6e4)) {
2911
+ ue = !0;
2912
+ try {
2913
+ let r = await v.runPromise(n()), i = oe(), a = /* @__PURE__ */ new Set();
2914
+ for (let t of r) t.providerCustomerId !== null && await Wt(e, `event:${t.id}`, i, Date.now()) && a.add(t.providerCustomerId);
2915
+ let o = 0;
2916
+ for (let e of a) await v.runPromise(T.syncCustomer(e).pipe(v.flatMap((e) => v.forEach(e.events, (e) => {
2917
+ let n = t.onEvent?.[e._tag];
2918
+ return n === void 0 ? v.void : n(e).pipe(v.catchAllCause((t) => v.sync(() => {
2919
+ V.error("billing catch-up: onEvent failed for a correction", {
2920
+ event: e._tag,
2921
+ cause: String(t)
2922
+ });
2923
+ })));
2924
+ }, { discard: !0 })), v.tap(() => v.sync(() => {
2925
+ o += 1;
2926
+ })), v.catchAll((e) => v.sync(() => {
2927
+ V.error("billing catch-up: syncing a customer failed — the provider redelivers its events", { message: e.message });
2928
+ }))));
2929
+ V.info("billing catch-up", {
2930
+ undelivered: r.length,
2931
+ customers: a.size,
2932
+ synced: o
2933
+ });
2934
+ } catch (e) {
2935
+ V.error("billing catch-up failed — the next late delivery or restart retries it", { message: String(e?.message ?? e) });
2936
+ } finally {
2937
+ de = Date.now(), ue = !1;
2938
+ }
2939
+ }
2940
+ }, pe = (e) => {
2941
+ e !== void 0 && (V = e), !le && ce && z !== void 0 && (le = !0, fe());
2942
+ }, me = [
1620
2943
  "store:write",
1621
2944
  "inspect:read",
1622
2945
  "inspect:write",
1623
2946
  "network:outbound:*",
1624
- ...O === void 0 ? [] : [
2947
+ ...R === void 0 ? [] : [
1625
2948
  "rpc:intercept:mutation",
1626
2949
  "rpc:intercept:action",
1627
2950
  "rpc:intercept:workflow"
1628
2951
  ],
1629
- ...E === void 0 ? [] : ["rpc:intercept:query"],
1630
- ...D ? ["store:changes:read"] : []
2952
+ ...F === void 0 ? [] : ["rpc:intercept:query"],
2953
+ ...I ? ["store:changes:read"] : []
1631
2954
  ];
1632
- return ae({
1633
- name: oe({
1634
- base: Yt,
2955
+ return ee({
2956
+ name: w({
2957
+ base: ir,
1635
2958
  alias: t.alias,
1636
2959
  instance: t.name
1637
2960
  }),
1638
- baseName: Yt,
2961
+ baseName: ir,
1639
2962
  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.",
1640
2963
  permissions: me,
1641
- declaredEnv: Jt.declared,
1642
- services: se,
1643
- routes: le,
2964
+ declaredEnv: rr.declared,
2965
+ egress: Vn(b),
2966
+ services: te,
2967
+ routes: k,
1644
2968
  rpcClientDescriptors: a,
1645
- httpRoutes: [v],
1646
- inspectEndpoints: b,
2969
+ httpRoutes: [O],
2970
+ inspectEndpoints: re,
1647
2971
  extendSchema: { tables: [
1648
2972
  ...xe(),
1649
- Ct,
1650
- Se
2973
+ Ht,
2974
+ Ce
1651
2975
  ] },
1652
- errorSchemas: ue,
1653
- ...O === void 0 ? {} : {
1654
- interceptMutation: O,
1655
- interceptAction: O,
1656
- interceptWorkflow: O
2976
+ errorSchemas: j,
2977
+ ...R === void 0 ? {} : {
2978
+ interceptMutation: R,
2979
+ interceptAction: R,
2980
+ interceptWorkflow: R
1657
2981
  },
1658
- ...E === void 0 ? {} : { interceptQuery: E },
1659
- ...D ? { onChangeEvent: pe } : {},
2982
+ ...F === void 0 ? {} : { interceptQuery: F },
2983
+ ...I ? { onChangeEvent: se } : {},
1660
2984
  bindDataStore: (e, t) => {
1661
- _.stores = Qe(e), k = e, fe.claim = t?.claimChange;
2985
+ E.stores = ft(e), z = e, pe(), L.claim = t?.claimChange;
1662
2986
  },
1663
- onActivate: (e) => p.sync(() => {
2987
+ onActivate: (e) => v.sync(() => {
1664
2988
  e.logger.info("billing active", {
1665
- provider: h.name,
1666
- plans: m.planIds().length,
1667
- enforce: C ? Object.keys(C).length : 0,
1668
- metering: T ? Object.keys(T).length : 0,
1669
- webhook: $
1670
- });
1671
- let n = t.flushIntervalMs ?? 6e4, r = T, i = r !== void 0 && Object.keys(r).length > 0;
2989
+ provider: b.name,
2990
+ plans: y.planIds().length,
2991
+ enforce: M ? Object.keys(M).length : 0,
2992
+ metering: P ? Object.keys(P).length : 0,
2993
+ webhook: Wn,
2994
+ redirectOrigins: t.redirectOrigins?.length ?? "any"
2995
+ }), b.name === "stripe" && t.redirectOrigins === void 0 && e.logger.warn("billing: no redirectOrigins — checkout and portal accept any return URL; set billingPlugin({ redirectOrigins })", {});
2996
+ let n = t.flushIntervalMs ?? 6e4, r = P, i = r !== void 0 && Object.keys(r).length > 0;
1672
2997
  if (n > 0) {
1673
- let e = de();
1674
- A = setInterval(() => {
1675
- let t = k;
2998
+ let e = oe();
2999
+ B = setInterval(() => {
3000
+ let t = z;
1676
3001
  if (t === void 0) return;
1677
3002
  let a = Date.now();
1678
3003
  (async () => {
1679
- await Tt(t, wt(a, n), e, a) && i && await p.runPromise(vt(g, t, r).pipe(p.catchAll(() => p.void)));
3004
+ await Wt(t, Ut(a, n), e, a) && await v.runPromise((i ? It(T, t, r) : T.flushUsage()).pipe(v.catchAll(() => v.void)));
1680
3005
  })();
1681
- }, n), A.unref?.();
3006
+ }, n), B.unref?.();
1682
3007
  }
3008
+ ce = !0, pe(e.logger);
1683
3009
  }),
1684
- onDeactivate: () => p.sync(() => {
1685
- A !== void 0 && (clearInterval(A), A = void 0);
3010
+ onDeactivate: () => v.sync(() => {
3011
+ B !== void 0 && (clearInterval(B), B = void 0);
1686
3012
  })
1687
3013
  });
1688
3014
  };
1689
3015
  //#endregion
1690
- export { j as BILLING_CUSTOMERS_TABLE, F as BILLING_DUNNING_NOTICES_TABLE, J as BILLING_FLUSH_CLAIMS_TABLE, N as BILLING_INVOICES_TABLE, M as BILLING_SUBSCRIPTIONS_TABLE, P as BILLING_USAGE_TABLE, Wt as BILLING_WEBHOOK_ID, $ as BILLING_WEBHOOK_PATH, e as BillingError, t as BillingService, Ce as DEFAULT_DUNNING_STEPS, we as DEFAULT_GRACE_HOURS, Te as DEFAULT_LOCKOUT, n as DEFAULT_PLAN, I as DUNNING_LOCK_STEP_ID, r as EntitlementExceeded, i as SubscriptionLocked, _e as billingCustomersTable, Se as billingDunningNoticesTable, Ct as billingFlushClaimsTable, ye as billingInvoicesTable, Xt as billingPlugin, tt as billingServiceLayer, ve as billingSubscriptionsTable, xe as billingTables, be as billingUsageTable, Gt as billingWebhookDescriptor, et as buildBillingService, ge as buildEnforceInterceptor, xt as buildPlanRegistry, Tt as claimFlushWindow, Le as dataStoreDunningNoticeStore, Qe as dataStoreStores, Me as defaultDunningEmail, Ae as dueSteps, Ne as dunningMailNotifier, St as emptyPlanRegistry, Oe as episodeKeyOf, R as evaluateBillingEntitlement, k as evaluateEntitlement, wt as flushWindowKey, ke as graceEndOf, H as isStaleStatusEvent, Fe as memoryDunningNoticeStore, Ze as memoryStores, Vt as mockProvider, Kt as mountBillingWebhook, It as normalizeStripeEvent, K as periodKey, he as requireEntitled, me as requireEntitlement, L as resolveDunningConfig, Ht as resolveProvider, Rt as stripeProvider };
3016
+ export { H as BILLING_CUSTOMERS_TABLE, Se as BILLING_DUNNING_NOTICES_TABLE, Vt as BILLING_FLUSH_CLAIMS_TABLE, W as BILLING_INVOICES_TABLE, U as BILLING_SUBSCRIPTIONS_TABLE, G as BILLING_USAGE_TABLE, Un as BILLING_WEBHOOK_ID, Wn as BILLING_WEBHOOK_PATH, e as BillingError, t as BillingService, Te as DEFAULT_DUNNING_STEPS, Ee as DEFAULT_GRACE_HOURS, De as DEFAULT_LOCKOUT, n as DEFAULT_PLAN, we as DUNNING_LOCK_STEP_ID, r as EntitlementExceeded, Gt as STRIPE_API_VERSION, Kt as STRIPE_DASHBOARD_REMINDERS, On as STRIPE_WEBHOOK_EVENTS, dn as StripeRestError, i as SubscriptionLocked, zn as assertBillingProductionReady, _e as billingCustomersTable, Ce as billingDunningNoticesTable, Vn as billingEgressOf, Ht as billingFlushClaimsTable, ye as billingInvoicesTable, ar as billingPlugin, yt as billingServiceLayer, ve as billingSubscriptionsTable, xe as billingTables, be as billingUsageTable, Gn as billingWebhookDescriptor, vt as buildBillingService, ge as buildEnforceInterceptor, zt as buildPlanRegistry, Wt as claimFlushWindow, Ve as dataStoreDunningNoticeStore, ft as dataStoreStores, Ie as defaultDunningEmail, en as diffMeter, on as diffPortal, $t as diffPrice, Xt as diffProduct, tn as diffTaxSettings, sn as diffWebhook, Ne as dueSteps, Le as dunningMailNotifier, Bt as emptyPlanRegistry, Zn as envOutRefusal, je as episodeKeyOf, Pe as evaluateBillingEntitlement, fe as evaluateEntitlement, Ut as flushWindowKey, ln as formEncode, Me as graceEndOf, Rn as isProductionBoot, Ze as isStaleStatusEvent, ze as memoryDunningNoticeStore, dt as memoryStores, Yn as mergeEnvFile, In as mockProvider, Kn as mountBillingWebhook, kn as normalizeStripeEvent, qn as openFindings, Qn as parseProvisionArgs, mt as periodKey, nn as portalParams, Jn as provisionStripeCatalog, un as redactStripeKeys, he as requireEntitled, me as requireEntitlement, ke as resolveDunningConfig, Bn as resolveProvider, $n as resolveProvisionKey, tr as runStripeProvision, cn as sellingReadiness, jn as stripeProvider, fn as stripeRest };