@voltro/plugin-billing 0.32.0 → 0.34.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,114 +1,329 @@
1
- import { BillingError as e, BillingService as t, DEFAULT_PLAN as n, EntitlementExceeded as r } from "./types.js";
2
- import { billingRpcClientImports as i, changePlanDescriptor as a, changeSeatsDescriptor as o, invoicesDescriptor as s, portalUrlDescriptor as c, previewChangeDescriptor as l, reportUsageDescriptor as u, startCheckoutDescriptor as ee, subscriptionDescriptor as d } from "./rpc.js";
3
- import { Effect as f, Layer as p, Schedule as m, Schema as h } from "effect";
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
4
  import { pluginEnv as g } from "@voltro/env";
5
- import { composeRpcInterceptors as te, definePlugin as ne } from "@voltro/protocol";
6
- import { and as _, eq as v, gte as re, id as y, integer as b, registerRetention as x, retentionTtlMsFromEnv as ie, table as S, text as C, timestamp as w } from "@voltro/database";
7
- import T from "stripe";
8
- import { defineIncomingWebhook as E, mountIncomingWebhook as D } from "@voltro/plugin-webhooks";
9
- import { stripeProvider as O } from "@voltro/plugin-webhooks/providers";
10
- import { createLogger as k } from "@voltro/logger";
5
+ import { composeRpcInterceptors as re, definePlugin as ie, pluginInstanceName as ae } from "@voltro/protocol";
6
+ import { and as _, eq as v, gte as y, id as b, integer as x, registerRetention as S, retentionTtlMsFromEnv as C, table as w, text as T, timestamp as E } from "@voltro/database";
7
+ import { createLogger as D } from "@voltro/logger";
8
+ import O from "stripe";
9
+ import { defineIncomingWebhook as oe, mountIncomingWebhook as k } from "@voltro/plugin-webhooks";
10
+ import { stripeProvider as A } from "@voltro/plugin-webhooks/providers";
11
11
  //#region src/entitlement.ts
12
- var A = (e, t, n, r) => ({
12
+ var j = (e, t, n, r) => ({
13
13
  allowed: !Number.isFinite(t) || r <= 0 || n + r <= t,
14
14
  entitlement: e,
15
15
  limit: t,
16
16
  used: n,
17
17
  cost: r
18
- }), j = (e) => e.tenantId, M = (e, n, i) => f.gen(function* () {
19
- let a = j(e.request.subject);
20
- if (a === null) return i <= 0 ? void 0 : yield* f.fail(new r({
18
+ }), M = (e) => e.tenantId, se = (e, n, i) => p.gen(function* () {
19
+ let a = M(e.request.subject);
20
+ if (a === null) return i <= 0 ? void 0 : yield* p.fail(new r({
21
21
  entitlement: n,
22
22
  limit: 0,
23
23
  used: 0,
24
24
  cost: i
25
25
  }));
26
26
  yield* (yield* t).consumeEntitlement(a, n, i);
27
- }), N = (e, t) => (n, i) => {
27
+ }), N = (e) => p.gen(function* () {
28
+ let n = M(e.request.subject);
29
+ if (n === null) return;
30
+ let r = yield* (yield* t).entitlementStatus(n);
31
+ if (!r.entitled) return yield* p.fail(new i({
32
+ tenantId: n,
33
+ status: r.status,
34
+ lockedSince: (r.lockedSince ?? /* @__PURE__ */ new Date()).toISOString(),
35
+ lockout: r.lockout
36
+ }));
37
+ }), ce = (e, t) => (n, i) => {
28
38
  let a = e[i.tag];
29
39
  if (a === void 0) return n;
30
40
  let o = a.cost ?? 1, s = i.subject.tenantId;
31
- return s === null ? o <= 0 ? n : f.fail(new r({
41
+ return s === null ? o <= 0 ? n : p.fail(new r({
32
42
  entitlement: a.entitlement,
33
43
  limit: 0,
34
44
  used: 0,
35
45
  cost: o
36
- })) : f.flatMap(t.consumeEntitlement(s, a.entitlement, o), () => n);
37
- }, P = "_voltro_billing_customers", F = "_voltro_billing_subscriptions", I = "_voltro_billing_invoices", L = "_voltro_billing_usage", ae = S(P, {
38
- id: y({ prefix: "bcust" }),
39
- tenantId: C(),
40
- provider: C(),
41
- providerCustomerId: C(),
42
- createdAt: w(),
43
- updatedAt: w()
44
- }).index("byBillingCustomerTenant", ["tenantId"]).index("byBillingProviderCustomer", ["providerCustomerId"]), oe = S(F, {
45
- id: y({ prefix: "bsub" }),
46
- tenantId: C(),
47
- provider: C(),
48
- providerSubscriptionId: C(),
49
- plan: C(),
50
- status: C(),
51
- quantity: b().default(1),
52
- currentPeriodStart: w().nullable(),
53
- currentPeriodEnd: w().nullable(),
54
- cancelAt: w().nullable(),
55
- createdAt: w(),
56
- updatedAt: w()
57
- }).index("byBillingSubscriptionTenant", ["tenantId"]).index("byBillingProviderSubscription", ["providerSubscriptionId"]), se = S(I, {
58
- id: y({ prefix: "binv" }),
59
- tenantId: C(),
60
- provider: C(),
61
- providerInvoiceId: C(),
62
- amountMinor: b(),
63
- currency: C(),
64
- status: C(),
65
- createdAt: w()
66
- }).index("byBillingInvoiceTenant", ["tenantId"]).index("byBillingProviderInvoice", ["providerInvoiceId"]), ce = S(L, {
67
- id: y({ prefix: "buse" }),
68
- tenantId: C().maxLength(191),
69
- entitlementKey: C().maxLength(191),
70
- period: C().maxLength(16),
71
- used: b().default(0),
72
- reportedToProvider: b().default(0),
73
- updatedAt: w()
46
+ })) : p.flatMap(t.consumeEntitlement(s, a.entitlement, o), () => n);
47
+ }, P = "_voltro_billing_customers", F = "_voltro_billing_subscriptions", I = "_voltro_billing_invoices", L = "_voltro_billing_usage", le = w(P, {
48
+ id: b({ prefix: "bcust" }),
49
+ tenantId: T(),
50
+ provider: T(),
51
+ providerCustomerId: T(),
52
+ createdAt: E(),
53
+ updatedAt: E()
54
+ }).index("byBillingCustomerTenant", ["tenantId"]).index("byBillingProviderCustomer", ["providerCustomerId"]), ue = w(F, {
55
+ id: b({ prefix: "bsub" }),
56
+ tenantId: T(),
57
+ provider: T(),
58
+ providerSubscriptionId: T(),
59
+ plan: T(),
60
+ status: T(),
61
+ quantity: x().default(1),
62
+ currentPeriodStart: E().nullable(),
63
+ currentPeriodEnd: E().nullable(),
64
+ cancelAt: E().nullable(),
65
+ pastDueSince: E().nullable(),
66
+ statusEventAt: E().nullable(),
67
+ createdAt: E(),
68
+ updatedAt: E()
69
+ }).index("byBillingSubscriptionTenant", ["tenantId"]).index("byBillingProviderSubscription", ["providerSubscriptionId"]).index("byBillingSubscriptionStatus", ["status"]), de = w(I, {
70
+ id: b({ prefix: "binv" }),
71
+ tenantId: T(),
72
+ provider: T(),
73
+ providerInvoiceId: T(),
74
+ amountMinor: x(),
75
+ currency: T(),
76
+ status: T(),
77
+ statusEventAt: E().nullable(),
78
+ createdAt: E()
79
+ }).index("byBillingInvoiceTenant", ["tenantId"]).index("byBillingProviderInvoice", ["providerInvoiceId"]), fe = w(L, {
80
+ id: b({ prefix: "buse" }),
81
+ tenantId: T().maxLength(191),
82
+ entitlementKey: T().maxLength(191),
83
+ period: T().maxLength(16),
84
+ used: x().default(0),
85
+ reportedToProvider: x().default(0),
86
+ updatedAt: E()
74
87
  }).unique("byBillingUsageWindow", [
75
88
  "tenantId",
76
89
  "entitlementKey",
77
90
  "period"
78
- ]), le = () => [
79
- ae,
80
- oe,
81
- se,
82
- ce
83
- ], R = (e) => `${e}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`, z = (t, n) => f.tryPromise({
91
+ ]), pe = () => [
92
+ le,
93
+ ue,
94
+ de,
95
+ fe
96
+ ], R = "_voltro_billing_dunning_notices", me = w(R, {
97
+ id: b({ prefix: "bdun" }),
98
+ tenantId: T().maxLength(191),
99
+ episode: T().maxLength(32),
100
+ stepId: T().maxLength(64),
101
+ sentAt: E()
102
+ }).unique("byBillingDunningNotice", [
103
+ "tenantId",
104
+ "episode",
105
+ "stepId"
106
+ ]), he = "locked", ge = [
107
+ {
108
+ id: "payment-failed",
109
+ afterHours: 0
110
+ },
111
+ {
112
+ id: "reminder",
113
+ afterHours: 72
114
+ },
115
+ {
116
+ id: "final-warning",
117
+ afterHours: 144
118
+ }
119
+ ], _e = 168, ve = "hard", ye = (e, t, n) => {
120
+ if (e === void 0 || e.trim() === "") return t;
121
+ let r = Number(e);
122
+ if (!Number.isFinite(r) || r < 0) throw Error(`billingPlugin: ${n} must be a non-negative number, got '${e}'`);
123
+ return r;
124
+ }, be = (e, t) => {
125
+ let n = t.VOLTRO_BILLING_DUNNING;
126
+ if (n !== void 0 && n !== "" && n !== "on" && n !== "off") throw Error(`billingPlugin: VOLTRO_BILLING_DUNNING must be 'on' or 'off', got '${n}'`);
127
+ let r = n === "off" ? !1 : n === "on" ? !0 : e?.enabled ?? !0, i = t.VOLTRO_BILLING_LOCKOUT;
128
+ if (i !== void 0 && i !== "" && i !== "hard" && i !== "soft") throw Error(`billingPlugin: VOLTRO_BILLING_LOCKOUT must be 'hard' or 'soft', got '${i}'`);
129
+ let a = i === "hard" || i === "soft" ? i : e?.lockout ?? "hard", o = ye(t.VOLTRO_BILLING_GRACE_HOURS, e?.graceHours ?? 168, "VOLTRO_BILLING_GRACE_HOURS"), s = e?.steps ?? ge, c = t.VOLTRO_BILLING_DUNNING_STEP_HOURS, l = s;
130
+ if (c !== void 0 && c.trim() !== "") {
131
+ let e = c.split(",").map((e) => e.trim());
132
+ 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(", ")})`);
133
+ l = s.map((t, n) => ({
134
+ id: t.id,
135
+ afterHours: ye(e[n], t.afterHours, "VOLTRO_BILLING_DUNNING_STEP_HOURS")
136
+ }));
137
+ }
138
+ let u = /* @__PURE__ */ new Set();
139
+ for (let e of l) {
140
+ if (e.id === "locked") throw Error(`billingPlugin: '${he}' is the reserved lockout step id`);
141
+ if (u.has(e.id)) throw Error(`billingPlugin: duplicate dunning step id '${e.id}'`);
142
+ u.add(e.id);
143
+ }
144
+ return {
145
+ enabled: r,
146
+ graceHours: o,
147
+ lockout: a,
148
+ steps: [...l].sort((e, t) => e.afterHours - t.afterHours),
149
+ ...e?.notify === void 0 ? {} : { notify: e.notify },
150
+ ...e?.resolveRecipient === void 0 ? {} : { resolveRecipient: e.resolveRecipient },
151
+ ...e?.portalReturnUrl === void 0 ? {} : { portalReturnUrl: e.portalReturnUrl }
152
+ };
153
+ }, xe = 36e5, Se = (e) => String(e.getTime()), Ce = (e, t) => new Date(e.getTime() + t * xe), we = (e, t, n) => {
154
+ let r = (n.getTime() - t.getTime()) / xe;
155
+ return e.filter((e) => r >= e.afterHours);
156
+ }, z = (e, t, r, i) => {
157
+ let a = {
158
+ tenantId: e?.tenantId ?? i ?? "",
159
+ lockout: t.lockout
160
+ };
161
+ if (e === null) return {
162
+ ...a,
163
+ plan: n,
164
+ billedPlan: n,
165
+ status: "none",
166
+ entitled: !0,
167
+ inGrace: !1,
168
+ graceEndsAt: null,
169
+ lockedSince: null
170
+ };
171
+ if (e.status === "active" || e.status === "trialing") return {
172
+ ...a,
173
+ plan: e.plan,
174
+ billedPlan: e.plan,
175
+ status: e.status,
176
+ entitled: !0,
177
+ inGrace: !1,
178
+ graceEndsAt: null,
179
+ lockedSince: null
180
+ };
181
+ if (e.status === "pastDue") {
182
+ if (!t.enabled) return {
183
+ ...a,
184
+ plan: n,
185
+ billedPlan: e.plan,
186
+ status: e.status,
187
+ entitled: !0,
188
+ inGrace: !1,
189
+ graceEndsAt: null,
190
+ lockedSince: null
191
+ };
192
+ if (e.pastDueSince === null) return {
193
+ ...a,
194
+ plan: e.plan,
195
+ billedPlan: e.plan,
196
+ status: e.status,
197
+ entitled: !0,
198
+ inGrace: !0,
199
+ graceEndsAt: null,
200
+ lockedSince: null
201
+ };
202
+ let i = Ce(e.pastDueSince, t.graceHours), o = r.getTime() >= i.getTime();
203
+ return {
204
+ ...a,
205
+ plan: o && t.lockout === "hard" ? n : e.plan,
206
+ billedPlan: e.plan,
207
+ status: e.status,
208
+ entitled: !o,
209
+ inGrace: !o,
210
+ graceEndsAt: i,
211
+ lockedSince: o ? i : null
212
+ };
213
+ }
214
+ return {
215
+ ...a,
216
+ plan: n,
217
+ billedPlan: e.plan,
218
+ status: e.status,
219
+ entitled: !0,
220
+ inGrace: !1,
221
+ graceEndsAt: null,
222
+ lockedSince: null
223
+ };
224
+ }, Te = (e) => e.toISOString().slice(0, 10), Ee = (e) => {
225
+ 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}`;
226
+ if (e.locked) return {
227
+ subject: "Your subscription has been suspended",
228
+ html: `<p>We were unable to collect payment, and your subscription is now suspended.</p>${n}`,
229
+ text: `We were unable to collect payment, and your subscription is now suspended.\n\n${r}`
230
+ };
231
+ let i = Te(e.graceEndsAt), a = e.stepId === "payment-failed" ? "Your last payment could not be collected." : "Your payment is still outstanding.";
232
+ return {
233
+ subject: "Action needed: your payment could not be collected",
234
+ html: `<p>${a} Your subscription stays active until ${i}.</p>${n}`,
235
+ text: `${a} Your subscription stays active until ${i}.\n\n${r}`
236
+ };
237
+ }, De = (e) => (t) => t.to === null ? p.void : e.send({
238
+ to: t.to,
239
+ subject: t.subject,
240
+ html: t.html,
241
+ text: t.text
242
+ }).pipe(p.asVoid), Oe = (e, t, n) => `${e}\u0000${t}\u0000${n}`, ke = () => {
243
+ let e = /* @__PURE__ */ new Map();
244
+ return {
245
+ claim: (t, n, r, i) => p.sync(() => {
246
+ let a = Oe(t, n, r);
247
+ return e.has(a) ? !1 : (e.set(a, {
248
+ tenantId: t,
249
+ episode: n,
250
+ stepId: r,
251
+ sentAt: i
252
+ }), !0);
253
+ }),
254
+ sentSteps: (t, n) => p.sync(() => [...e.values()].filter((e) => e.tenantId === t && e.episode === n))
255
+ };
256
+ }, Ae = () => `bdun_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`, je = (t) => ({
257
+ claim: (n, r, i, a) => p.tryPromise({
258
+ try: async () => {
259
+ let e = Ae(), o = await t.insertIgnore(R, {
260
+ id: e,
261
+ tenantId: n,
262
+ episode: r,
263
+ stepId: i,
264
+ sentAt: a
265
+ }, { conflictColumns: [
266
+ "tenantId",
267
+ "episode",
268
+ "stepId"
269
+ ] });
270
+ return String(o.id) === e;
271
+ },
272
+ catch: (t) => new e({
273
+ source: "dunningNoticeStore",
274
+ message: String(t?.message ?? t),
275
+ transient: !0
276
+ })
277
+ }),
278
+ sentSteps: (n, r) => p.tryPromise({
279
+ try: async () => (await t.query({
280
+ table: R,
281
+ predicate: _(v("tenantId", n), v("episode", r)),
282
+ order: [],
283
+ take: void 0,
284
+ skip: void 0,
285
+ projection: void 0
286
+ })).map((e) => ({
287
+ tenantId: String(e.tenantId),
288
+ episode: String(e.episode),
289
+ stepId: String(e.stepId),
290
+ sentAt: e.sentAt == null ? null : new Date(e.sentAt)
291
+ })),
292
+ catch: (t) => new e({
293
+ source: "dunningNoticeStore",
294
+ message: String(t?.message ?? t),
295
+ transient: !0
296
+ })
297
+ })
298
+ }), B = (e) => `${e}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`, V = (t, n) => p.tryPromise({
84
299
  try: t,
85
300
  catch: (t) => new e({
86
301
  source: n,
87
302
  message: String(t?.message ?? t),
88
303
  transient: !0
89
304
  })
90
- }), B = (e) => ({
305
+ }), H = (e) => ({
91
306
  table: e.table,
92
307
  predicate: e.predicate,
93
308
  order: e.order ?? [],
94
309
  take: e.take,
95
310
  skip: void 0,
96
311
  projection: void 0
97
- }), ue = () => {
312
+ }), Me = () => {
98
313
  let e = /* @__PURE__ */ new Map();
99
314
  return {
100
- upsert: (t) => f.sync(() => {
315
+ upsert: (t) => p.sync(() => {
101
316
  e.set(t.tenantId, t);
102
317
  }),
103
- getByTenant: (t) => f.sync(() => e.get(t) ?? null)
318
+ getByTenant: (t) => p.sync(() => e.get(t) ?? null)
104
319
  };
105
- }, de = (e) => ({
320
+ }, Ne = (e) => ({
106
321
  tenantId: String(e.tenantId),
107
322
  provider: String(e.provider),
108
323
  providerCustomerId: String(e.providerCustomerId)
109
- }), fe = (e) => ({
110
- upsert: (t) => z(async () => {
111
- let n = await e.query(B({
324
+ }), Pe = (e) => ({
325
+ upsert: (t) => V(async () => {
326
+ let n = await e.query(H({
112
327
  table: P,
113
328
  predicate: v("tenantId", t.tenantId),
114
329
  take: 1
@@ -118,48 +333,53 @@ var A = (e, t, n, r) => ({
118
333
  providerCustomerId: t.providerCustomerId,
119
334
  updatedAt: r
120
335
  }) : await e.insert(P, {
121
- id: R("bcust"),
336
+ id: B("bcust"),
122
337
  ...t,
123
338
  createdAt: r,
124
339
  updatedAt: r
125
340
  });
126
341
  }, "customerStore"),
127
- getByTenant: (t) => z(async () => {
128
- let n = await e.query(B({
342
+ getByTenant: (t) => V(async () => {
343
+ let n = await e.query(H({
129
344
  table: P,
130
345
  predicate: v("tenantId", t),
131
346
  take: 1
132
347
  }));
133
- return n[0] ? de(n[0]) : null;
348
+ return n[0] ? Ne(n[0]) : null;
134
349
  }, "customerStore")
135
- }), pe = () => {
350
+ }), U = (e, t) => e === null ? !1 : t === null || t.getTime() < e.getTime(), Fe = () => {
136
351
  let e = /* @__PURE__ */ new Map();
137
352
  return {
138
- upsert: (t) => f.sync(() => {
139
- e.set(t.tenantId, t);
353
+ upsert: (t) => p.sync(() => {
354
+ let n = e.get(t.tenantId);
355
+ n && U(n.statusEventAt, t.statusEventAt) || e.set(t.tenantId, {
356
+ ...t,
357
+ pastDueSince: n ? n.pastDueSince : t.pastDueSince
358
+ });
140
359
  }),
141
- getByTenant: (t) => f.sync(() => e.get(t) ?? null),
142
- markCanceled: (t) => f.sync(() => {
360
+ getByTenant: (t) => p.sync(() => e.get(t) ?? null),
361
+ listByStatus: (t) => p.sync(() => [...e.values()].filter((e) => e.status === t)),
362
+ markCanceled: (t) => p.sync(() => {
143
363
  for (let [n, r] of e) r.providerSubscriptionId === t && e.set(n, {
144
364
  ...r,
145
365
  status: "canceled"
146
366
  });
147
367
  }),
148
- patchByTenant: (t, n) => f.sync(() => {
368
+ patchByTenant: (t, n) => p.sync(() => {
149
369
  let r = e.get(t);
150
370
  r && e.set(t, {
151
371
  ...r,
152
372
  ...n
153
373
  });
154
374
  }),
155
- setStatus: (t, n) => f.sync(() => {
375
+ setStatus: (t, n) => p.sync(() => {
156
376
  for (let [r, i] of e) i.providerSubscriptionId === t && e.set(r, {
157
377
  ...i,
158
378
  status: n
159
379
  });
160
380
  })
161
381
  };
162
- }, me = (e) => ({
382
+ }, Ie = (e) => ({
163
383
  tenantId: String(e.tenantId),
164
384
  provider: String(e.provider),
165
385
  providerSubscriptionId: String(e.providerSubscriptionId),
@@ -168,26 +388,32 @@ var A = (e, t, n, r) => ({
168
388
  quantity: e.quantity == null ? 1 : Number(e.quantity),
169
389
  currentPeriodStart: e.currentPeriodStart == null ? null : new Date(e.currentPeriodStart),
170
390
  currentPeriodEnd: e.currentPeriodEnd == null ? null : new Date(e.currentPeriodEnd),
171
- cancelAt: e.cancelAt == null ? null : new Date(e.cancelAt)
172
- }), V = async (e, t) => (await e.query(B({
391
+ cancelAt: e.cancelAt == null ? null : new Date(e.cancelAt),
392
+ pastDueSince: e.pastDueSince == null ? null : new Date(e.pastDueSince),
393
+ statusEventAt: e.statusEventAt == null ? null : new Date(e.statusEventAt)
394
+ }), Le = async (e, t) => (await e.query(H({
173
395
  table: "_voltro_billing_subscriptions",
174
396
  predicate: v("providerSubscriptionId", t),
175
397
  take: 1
176
- })))[0] ?? null, he = (e) => ({
177
- upsert: (t) => z(async () => {
178
- let n = await V(e, t.providerSubscriptionId), r = /* @__PURE__ */ new Date();
179
- n ? await e.update(F, String(n.id), {
180
- tenantId: t.tenantId,
181
- provider: t.provider,
182
- plan: t.plan,
183
- status: t.status,
184
- quantity: t.quantity,
185
- currentPeriodStart: t.currentPeriodStart,
186
- currentPeriodEnd: t.currentPeriodEnd,
187
- cancelAt: t.cancelAt,
188
- updatedAt: r
189
- }) : await e.insert(F, {
190
- id: R("bsub"),
398
+ })))[0] ?? null, Re = (e) => ({
399
+ upsert: (t) => V(async () => {
400
+ let n = await Le(e, t.providerSubscriptionId), r = /* @__PURE__ */ new Date();
401
+ if (n) {
402
+ if (U(n.statusEventAt == null ? null : new Date(n.statusEventAt), t.statusEventAt)) return;
403
+ await e.update(F, String(n.id), {
404
+ tenantId: t.tenantId,
405
+ provider: t.provider,
406
+ plan: t.plan,
407
+ status: t.status,
408
+ quantity: t.quantity,
409
+ currentPeriodStart: t.currentPeriodStart,
410
+ currentPeriodEnd: t.currentPeriodEnd,
411
+ cancelAt: t.cancelAt,
412
+ statusEventAt: t.statusEventAt,
413
+ updatedAt: r
414
+ });
415
+ } else await e.insert(F, {
416
+ id: B("bsub"),
191
417
  tenantId: t.tenantId,
192
418
  provider: t.provider,
193
419
  providerSubscriptionId: t.providerSubscriptionId,
@@ -197,12 +423,14 @@ var A = (e, t, n, r) => ({
197
423
  currentPeriodStart: t.currentPeriodStart,
198
424
  currentPeriodEnd: t.currentPeriodEnd,
199
425
  cancelAt: t.cancelAt,
426
+ pastDueSince: null,
427
+ statusEventAt: t.statusEventAt,
200
428
  createdAt: r,
201
429
  updatedAt: r
202
430
  });
203
431
  }, "subscriptionStore"),
204
- getByTenant: (t) => z(async () => {
205
- let n = await e.query(B({
432
+ getByTenant: (t) => V(async () => {
433
+ let n = await e.query(H({
206
434
  table: F,
207
435
  predicate: v("tenantId", t),
208
436
  order: [{
@@ -211,17 +439,25 @@ var A = (e, t, n, r) => ({
211
439
  }],
212
440
  take: 1
213
441
  }));
214
- return n[0] ? me(n[0]) : null;
442
+ return n[0] ? Ie(n[0]) : null;
215
443
  }, "subscriptionStore"),
216
- markCanceled: (t) => z(async () => {
217
- let n = await V(e, t);
444
+ listByStatus: (t) => V(async () => (await e.query(H({
445
+ table: F,
446
+ predicate: v("status", t),
447
+ order: [{
448
+ column: "updatedAt",
449
+ direction: "asc"
450
+ }]
451
+ }))).map(Ie), "subscriptionStore"),
452
+ markCanceled: (t) => V(async () => {
453
+ let n = await Le(e, t);
218
454
  n && await e.update(F, String(n.id), {
219
455
  status: "canceled",
220
456
  updatedAt: /* @__PURE__ */ new Date()
221
457
  });
222
458
  }, "subscriptionStore"),
223
- patchByTenant: (t, n) => z(async () => {
224
- let r = (await e.query(B({
459
+ patchByTenant: (t, n) => V(async () => {
460
+ let r = (await e.query(H({
225
461
  table: F,
226
462
  predicate: v("tenantId", t),
227
463
  order: [{
@@ -234,62 +470,72 @@ var A = (e, t, n, r) => ({
234
470
  ...n.plan === void 0 ? {} : { plan: n.plan },
235
471
  ...n.quantity === void 0 ? {} : { quantity: n.quantity },
236
472
  ...n.status === void 0 ? {} : { status: n.status },
473
+ ...n.currentPeriodStart === void 0 ? {} : { currentPeriodStart: n.currentPeriodStart },
474
+ ...n.currentPeriodEnd === void 0 ? {} : { currentPeriodEnd: n.currentPeriodEnd },
475
+ ...n.pastDueSince === void 0 ? {} : { pastDueSince: n.pastDueSince },
476
+ ...n.statusEventAt === void 0 ? {} : { statusEventAt: n.statusEventAt },
237
477
  updatedAt: /* @__PURE__ */ new Date()
238
478
  });
239
479
  }, "subscriptionStore"),
240
- setStatus: (t, n) => z(async () => {
241
- let r = await V(e, t);
480
+ setStatus: (t, n) => V(async () => {
481
+ let r = await Le(e, t);
242
482
  r && await e.update(F, String(r.id), {
243
483
  status: n,
244
484
  updatedAt: /* @__PURE__ */ new Date()
245
485
  });
246
486
  }, "subscriptionStore")
247
- }), ge = () => {
487
+ }), ze = () => {
248
488
  let e = /* @__PURE__ */ new Map();
249
489
  return {
250
- upsert: (t) => f.sync(() => {
251
- e.set(t.providerInvoiceId, t);
490
+ upsert: (t) => p.sync(() => {
491
+ let n = e.get(t.providerInvoiceId);
492
+ n && U(n.statusEventAt, t.statusEventAt) || e.set(t.providerInvoiceId, t);
252
493
  }),
253
- listByTenant: (t) => f.sync(() => [...e.values()].filter((e) => e.tenantId === t))
494
+ listByTenant: (t) => p.sync(() => [...e.values()].filter((e) => e.tenantId === t))
254
495
  };
255
- }, _e = (e) => ({
496
+ }, Be = (e) => ({
256
497
  tenantId: String(e.tenantId),
257
498
  provider: String(e.provider),
258
499
  providerInvoiceId: String(e.providerInvoiceId),
259
500
  amountMinor: Number(e.amountMinor),
260
501
  currency: String(e.currency),
261
- status: String(e.status)
262
- }), ve = (e) => ({
263
- upsert: (t) => z(async () => {
264
- let n = await e.query(B({
502
+ status: String(e.status),
503
+ statusEventAt: e.statusEventAt == null ? null : new Date(e.statusEventAt)
504
+ }), Ve = (e) => ({
505
+ upsert: (t) => V(async () => {
506
+ let n = (await e.query(H({
265
507
  table: I,
266
508
  predicate: v("providerInvoiceId", t.providerInvoiceId),
267
509
  take: 1
268
- }));
269
- n[0] ? await e.update(I, String(n[0].id), {
270
- amountMinor: t.amountMinor,
271
- currency: t.currency,
272
- status: t.status
273
- }) : await e.insert(I, {
274
- id: R("binv"),
510
+ })))[0];
511
+ if (n) {
512
+ if (U(n.statusEventAt == null ? null : new Date(n.statusEventAt), t.statusEventAt)) return;
513
+ await e.update(I, String(n.id), {
514
+ amountMinor: t.amountMinor,
515
+ currency: t.currency,
516
+ status: t.status,
517
+ statusEventAt: t.statusEventAt
518
+ });
519
+ } else await e.insert(I, {
520
+ id: B("binv"),
275
521
  ...t,
276
522
  createdAt: /* @__PURE__ */ new Date()
277
523
  });
278
524
  }, "invoiceStore"),
279
- listByTenant: (t) => z(async () => (await e.query(B({
525
+ listByTenant: (t) => V(async () => (await e.query(H({
280
526
  table: I,
281
527
  predicate: v("tenantId", t),
282
528
  order: [{
283
529
  column: "createdAt",
284
530
  direction: "desc"
285
531
  }]
286
- }))).map(_e), "invoiceStore")
287
- }), H = (e, t, n) => `${e}\u0000${t}\u0000${n}`, ye = () => {
532
+ }))).map(Be), "invoiceStore")
533
+ }), W = (e, t, n) => `${e}\u0000${t}\u0000${n}`, He = () => {
288
534
  let e = /* @__PURE__ */ new Map();
289
535
  return {
290
- used: (t, n, r) => f.sync(() => e.get(H(t, n, r))?.used ?? 0),
291
- consume: (t, n, r, i, a) => f.sync(() => {
292
- let o = H(t, n, r), s = e.get(o), c = s?.used ?? 0, l = c + i <= a;
536
+ used: (t, n, r) => p.sync(() => e.get(W(t, n, r))?.used ?? 0),
537
+ consume: (t, n, r, i, a) => p.sync(() => {
538
+ let o = W(t, n, r), s = e.get(o), c = s?.used ?? 0, l = c + i <= a;
293
539
  return l && e.set(o, {
294
540
  tenantId: t,
295
541
  entitlementKey: n,
@@ -304,8 +550,8 @@ var A = (e, t, n, r) => ({
304
550
  cost: i
305
551
  };
306
552
  }),
307
- increment: (t, n, r, i) => f.sync(() => {
308
- let a = H(t, n, r), o = e.get(a), s = (o?.used ?? 0) + i;
553
+ increment: (t, n, r, i) => p.sync(() => {
554
+ let a = W(t, n, r), o = e.get(a), s = (o?.used ?? 0) + i;
309
555
  return e.set(a, {
310
556
  tenantId: t,
311
557
  entitlementKey: n,
@@ -314,34 +560,34 @@ var A = (e, t, n, r) => ({
314
560
  reportedToProvider: o?.reportedToProvider ?? 0
315
561
  }), s;
316
562
  }),
317
- pending: () => f.sync(() => [...e.values()].filter((e) => e.used > e.reportedToProvider)),
318
- markReported: (t, n, r, i) => f.sync(() => {
319
- let a = H(t, n, r), o = e.get(a);
563
+ pending: () => p.sync(() => [...e.values()].filter((e) => e.used > e.reportedToProvider)),
564
+ markReported: (t, n, r, i) => p.sync(() => {
565
+ let a = W(t, n, r), o = e.get(a);
320
566
  o && e.set(a, {
321
567
  ...o,
322
568
  reportedToProvider: i
323
569
  });
324
570
  })
325
571
  };
326
- }, be = (e) => ({
572
+ }, Ue = (e) => ({
327
573
  tenantId: String(e.tenantId),
328
574
  entitlementKey: String(e.entitlementKey),
329
575
  period: String(e.period),
330
576
  used: Number(e.used ?? 0),
331
577
  reportedToProvider: Number(e.reportedToProvider ?? 0)
332
- }), U = async (e, t, n, r) => (await e.query(B({
578
+ }), G = async (e, t, n, r) => (await e.query(H({
333
579
  table: "_voltro_billing_usage",
334
580
  predicate: _(v("tenantId", t), v("entitlementKey", n), v("period", r)),
335
581
  take: 1
336
- })))[0] ?? null, xe = (e) => ({
337
- used: (t, n, r) => z(async () => {
338
- let i = await U(e, t, n, r);
582
+ })))[0] ?? null, We = (e) => ({
583
+ used: (t, n, r) => V(async () => {
584
+ let i = await G(e, t, n, r);
339
585
  return i ? Number(i.used ?? 0) : 0;
340
586
  }, "usageStore"),
341
- consume: (t, n, r, i, a) => z(async () => {
587
+ consume: (t, n, r, i, a) => V(async () => {
342
588
  for (let o = 0; o < 32; o++) {
343
589
  o > 0 && await new Promise((e) => setTimeout(e, o));
344
- let s = await U(e, t, n, r), c = s ? Number(s.used ?? 0) : 0;
590
+ let s = await G(e, t, n, r), c = s ? Number(s.used ?? 0) : 0;
345
591
  if (c + i > a) return {
346
592
  allowed: !1,
347
593
  entitlement: n,
@@ -361,7 +607,7 @@ var A = (e, t, n, r) => ({
361
607
  cost: i
362
608
  };
363
609
  } else {
364
- let o = R("buse"), s = await e.insertIgnore(L, {
610
+ let o = B("buse"), s = await e.insertIgnore(L, {
365
611
  id: o,
366
612
  tenantId: t,
367
613
  entitlementKey: n,
@@ -385,10 +631,10 @@ var A = (e, t, n, r) => ({
385
631
  }
386
632
  throw Error(`billing usage consume: contention on (${t}, ${n}, ${r}) exceeded 32 attempts`);
387
633
  }, "usageStore"),
388
- increment: (t, n, r, i) => z(async () => {
634
+ increment: (t, n, r, i) => V(async () => {
389
635
  for (let a = 0; a < 32; a++) {
390
636
  a > 0 && await new Promise((e) => setTimeout(e, a));
391
- let o = await U(e, t, n, r);
637
+ let o = await G(e, t, n, r);
392
638
  if (o) {
393
639
  let t = Number(o.used ?? 0);
394
640
  if (await e.updateMany("_voltro_billing_usage", {
@@ -396,7 +642,7 @@ var A = (e, t, n, r) => ({
396
642
  updatedAt: /* @__PURE__ */ new Date()
397
643
  }, { where: _(v("id", String(o.id)), v("used", t)) }) === 1) return t + i;
398
644
  } else {
399
- let a = R("buse"), o = await e.insertIgnore(L, {
645
+ let a = B("buse"), o = await e.insertIgnore(L, {
400
646
  id: a,
401
647
  tenantId: t,
402
648
  entitlementKey: n,
@@ -414,149 +660,250 @@ var A = (e, t, n, r) => ({
414
660
  }
415
661
  throw Error(`billing usage increment: contention on (${t}, ${n}, ${r}) exceeded 32 attempts`);
416
662
  }, "usageStore"),
417
- pending: () => z(async () => (await e.query(B({ table: L }))).map(be).filter((e) => e.used > e.reportedToProvider), "usageStore"),
418
- markReported: (t, n, r, i) => z(async () => {
419
- let a = await U(e, t, n, r);
663
+ pending: () => V(async () => (await e.query(H({ table: L }))).map(Ue).filter((e) => e.used > e.reportedToProvider), "usageStore"),
664
+ markReported: (t, n, r, i) => V(async () => {
665
+ let a = await G(e, t, n, r);
420
666
  a && await e.update(L, String(a.id), {
421
667
  reportedToProvider: i,
422
668
  updatedAt: /* @__PURE__ */ new Date()
423
669
  });
424
670
  }, "usageStore")
425
- }), Se = () => ({
426
- customers: ue(),
427
- subscriptions: pe(),
428
- invoices: ge(),
429
- usage: ye()
430
- }), W = (e) => ({
431
- customers: fe(e),
432
- subscriptions: he(e),
433
- invoices: ve(e),
434
- usage: xe(e)
435
- }), G = (e = /* @__PURE__ */ new Date()) => `${e.getUTCFullYear()}-${String(e.getUTCMonth() + 1).padStart(2, "0")}`, Ce = (e) => ({ stores: e ?? Se() }), K = (e, t) => e.pipe(f.retry({
436
- schedule: m.recurs(Math.max(0, t - 1)),
671
+ }), Ge = () => ({
672
+ customers: Me(),
673
+ subscriptions: Fe(),
674
+ invoices: ze(),
675
+ usage: He(),
676
+ dunningNotices: ke()
677
+ }), Ke = (e) => ({
678
+ customers: Pe(e),
679
+ subscriptions: Re(e),
680
+ invoices: Ve(e),
681
+ usage: We(e),
682
+ dunningNotices: je(e)
683
+ }), K = D({ scope: "@voltro/plugin-billing" }), q = (e = /* @__PURE__ */ new Date()) => `${e.getUTCFullYear()}-${String(e.getUTCMonth() + 1).padStart(2, "0")}`, qe = (e) => ({ stores: e ?? Ge() }), J = (e, t) => e.pipe(p.retry({
684
+ schedule: h.recurs(Math.max(0, t - 1)),
437
685
  while: (e) => e.transient
438
- })), we = (t) => {
439
- let { provider: i, plans: a } = t, o = t.attempts ?? 3, s = t.checkout, c = t.now ?? (() => /* @__PURE__ */ new Date()), l = Ce(t.stores), u = (e) => l.stores.subscriptions.getByTenant(e).pipe(f.map((e) => e !== null && (e.status === "active" || e.status === "trialing") ? e.plan : n)), ee = (e) => l.stores.subscriptions.getByTenant(e), d = (e, n) => f.gen(function* () {
686
+ })), Je = (t) => {
687
+ let { provider: n, plans: i } = t, a = t.attempts ?? 3, o = t.checkout, s = t.now ?? (() => /* @__PURE__ */ new Date()), c = qe(t.stores), l = t.dunning ?? be(void 0, {}), u = (e) => c.stores.subscriptions.getByTenant(e).pipe(p.map((t) => z(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* () {
440
688
  if (t.resolveEntitlementLimit !== void 0) {
441
689
  let r = yield* t.resolveEntitlementLimit(e, n);
442
690
  if (r !== null) return r;
443
691
  }
444
- let r = yield* u(e);
445
- return a.entitlementLimit(r, n);
446
- }), p = (e, t, n) => f.gen(function* () {
447
- return A(t, yield* d(e, t), yield* l.stores.usage.used(e, t, G(c())), n);
448
- }), m = (e, t, n) => f.gen(function* () {
449
- let i = yield* d(e, t);
692
+ let r = yield* d(e);
693
+ return i.entitlementLimit(r, n);
694
+ }), te = (e, t, n) => p.gen(function* () {
695
+ return j(t, yield* ee(e, t), yield* c.stores.usage.used(e, t, q(s())), n);
696
+ }), m = (e, t, n) => p.gen(function* () {
697
+ let i = yield* ee(e, t);
450
698
  if (!Number.isFinite(i) || n <= 0) return;
451
- let a = G(c()), o = yield* l.stores.usage.consume(e, t, a, n, i);
452
- if (!o.allowed) return yield* f.fail(new r({
699
+ let a = q(s()), o = yield* c.stores.usage.consume(e, t, a, n, i);
700
+ if (!o.allowed) return yield* p.fail(new r({
453
701
  entitlement: t,
454
702
  limit: o.limit,
455
703
  used: o.used,
456
704
  cost: n
457
705
  }));
458
- }), h = (e, t, n) => n <= 0 ? f.void : l.stores.usage.increment(e, t, G(c()), n).pipe(f.asVoid), g = () => f.gen(function* () {
459
- if (!i.supportsMeteredUsage) return;
460
- let e = yield* l.stores.usage.pending();
706
+ }), h = (e, t, n) => n <= 0 ? p.void : c.stores.usage.increment(e, t, q(s()), n).pipe(p.asVoid), ne = () => p.gen(function* () {
707
+ if (!n.supportsMeteredUsage) return;
708
+ let e = yield* c.stores.usage.pending();
461
709
  for (let t of e) {
462
710
  let e = t.used - t.reportedToProvider;
463
- e <= 0 || (yield* K(i.reportUsage({
711
+ e <= 0 || (yield* J(n.reportUsage({
464
712
  tenantId: t.tenantId,
465
713
  entitlementKey: t.entitlementKey,
466
714
  quantity: e
467
- }), o), yield* l.stores.usage.markReported(t.tenantId, t.entitlementKey, t.period, t.used));
715
+ }), a), yield* c.stores.usage.markReported(t.tenantId, t.entitlementKey, t.period, t.used));
468
716
  }
469
- }), te = (t) => f.gen(function* () {
470
- let n = a.priceIdFor(t.plan);
471
- if (n === null) return yield* f.fail(new e({
717
+ }), g = (t) => p.gen(function* () {
718
+ let r = i.priceIdFor(t.plan);
719
+ if (r === null) return yield* p.fail(new e({
472
720
  source: "billing",
473
721
  message: `plan '${t.plan}' has no provider priceId — not a paid plan`,
474
722
  transient: !1
475
723
  }));
476
- let r = yield* l.stores.customers.getByTenant(t.tenantId);
477
- return yield* K(i.createCheckoutSession({
724
+ let s = yield* c.stores.customers.getByTenant(t.tenantId);
725
+ return yield* J(n.createCheckoutSession({
478
726
  tenantId: t.tenantId,
479
- priceId: n,
727
+ priceId: r,
480
728
  successUrl: t.successUrl,
481
729
  cancelUrl: t.cancelUrl,
482
- ...r === null ? {} : { providerCustomerId: r.providerCustomerId },
730
+ ...s === null ? {} : { providerCustomerId: s.providerCustomerId },
483
731
  ...t.quantity === void 0 ? {} : { quantity: t.quantity },
484
- ...s?.adjustableQuantity === void 0 ? {} : { adjustableQuantity: s.adjustableQuantity },
485
- ...s?.trialDays === void 0 ? {} : { trialDays: s.trialDays }
486
- }), o);
487
- }), ne = (t, n) => f.gen(function* () {
488
- let r = yield* l.stores.customers.getByTenant(t);
489
- return r === null ? yield* f.fail(new e({
732
+ ...o?.adjustableQuantity === void 0 ? {} : { adjustableQuantity: o.adjustableQuantity },
733
+ ...o?.trialDays === void 0 ? {} : { trialDays: o.trialDays }
734
+ }), a);
735
+ }), re = (t, r) => p.gen(function* () {
736
+ let i = yield* c.stores.customers.getByTenant(t);
737
+ return i === null ? yield* p.fail(new e({
490
738
  source: "billing",
491
739
  message: `no provider customer linked for tenant '${t}'`,
492
740
  transient: !1
493
- })) : yield* K(i.createPortalSession({
741
+ })) : yield* J(n.createPortalSession({
494
742
  tenantId: t,
495
- providerCustomerId: r.providerCustomerId,
496
- returnUrl: n
497
- }), o);
498
- }), _ = (e) => {
743
+ providerCustomerId: i.providerCustomerId,
744
+ returnUrl: r
745
+ }), a);
746
+ }), ie = (e) => p.gen(function* () {
747
+ if (l.resolveRecipient !== void 0) {
748
+ let t = yield* l.resolveRecipient(e);
749
+ if (t !== null) return t;
750
+ }
751
+ if (n.customerEmail === void 0) return null;
752
+ let t = yield* c.stores.customers.getByTenant(e);
753
+ return t === null ? null : yield* n.customerEmail(t.providerCustomerId);
754
+ }), ae = (e) => {
755
+ let t = l.portalReturnUrl;
756
+ return t === void 0 ? p.succeed(null) : re(e, t).pipe(p.map((e) => e.url), p.catchAll(() => p.succeed(null)));
757
+ }, _ = (e, t, n, r, i) => p.gen(function* () {
758
+ let a = yield* ie(e.tenantId).pipe(p.catchAll(() => p.succeed(null))), o = yield* ae(e.tenantId), s = t.lockedSince !== null, c = Ee({
759
+ stepId: n,
760
+ locked: s,
761
+ graceEndsAt: i,
762
+ portalUrl: o
763
+ }), u = {
764
+ stepId: n,
765
+ tenantId: e.tenantId,
766
+ to: a,
767
+ plan: e.plan,
768
+ status: e.status,
769
+ pastDueSince: r,
770
+ graceEndsAt: i,
771
+ locked: s,
772
+ lockout: l.lockout,
773
+ portalUrl: o,
774
+ ...c
775
+ };
776
+ K.info("billing dunning notice", {
777
+ tenant: e.tenantId,
778
+ step: n,
779
+ locked: s,
780
+ graceEndsAt: i.toISOString(),
781
+ delivered: l.notify !== void 0 && a !== null
782
+ });
783
+ let d = l.notify;
784
+ d !== void 0 && (yield* d(u).pipe(p.catchAllCause((t) => p.sync(() => {
785
+ K.warn("billing dunning notify failed", {
786
+ tenant: e.tenantId,
787
+ step: n,
788
+ cause: String(t)
789
+ });
790
+ }))));
791
+ }), v = (e) => p.gen(function* () {
792
+ let t = yield* c.stores.subscriptions.getByTenant(e);
793
+ if (t === null) return z(null, l, s(), e);
794
+ let r = yield* n.fetchSubscription(t.providerSubscriptionId).pipe(p.catchAll((t) => p.sync(() => (K.warn("billing dunning reconcile: provider unreachable, not escalating", {
795
+ tenant: e,
796
+ source: t.source,
797
+ message: t.message
798
+ }), null)))), i = t;
799
+ if (r !== null) {
800
+ let n = s(), a = r.status === "pastDue" ? t.status === "pastDue" && t.pastDueSince !== null ? t.pastDueSince : n : null;
801
+ yield* c.stores.subscriptions.patchByTenant(e, {
802
+ status: r.status,
803
+ ...r.plan === null ? {} : { plan: r.plan },
804
+ quantity: r.quantity,
805
+ currentPeriodStart: r.currentPeriodStart,
806
+ currentPeriodEnd: r.currentPeriodEnd,
807
+ pastDueSince: a,
808
+ statusEventAt: n
809
+ }), i = {
810
+ ...t,
811
+ status: r.status,
812
+ plan: r.plan ?? t.plan,
813
+ quantity: r.quantity,
814
+ currentPeriodStart: r.currentPeriodStart,
815
+ currentPeriodEnd: r.currentPeriodEnd,
816
+ pastDueSince: a,
817
+ statusEventAt: n
818
+ };
819
+ }
820
+ let a = z(i, l, s(), e);
821
+ if (!l.enabled || i.status !== "pastDue" || i.pastDueSince === null) return a;
822
+ let o = i.pastDueSince, u = Se(o), d = a.graceEndsAt ?? o, f = we(l.steps, o, s()).map((e) => e.id);
823
+ a.lockedSince !== null && f.push(he);
824
+ for (let t of f) (yield* c.stores.dunningNotices.claim(e, u, t, s())) && (yield* _(i, a, t, o, d));
825
+ return a;
826
+ }), y = () => p.gen(function* () {
827
+ let e = yield* c.stores.subscriptions.listByStatus("pastDue"), t = 0;
828
+ for (let n of e) yield* v(n.tenantId).pipe(p.catchAll((e) => p.sync(() => {
829
+ K.warn("billing dunning sweep: tenant failed", {
830
+ tenant: n.tenantId,
831
+ message: e.message
832
+ });
833
+ }))), t += 1;
834
+ return t;
835
+ }), b = (e) => {
836
+ if (!l.enabled) return p.void;
837
+ if (e._tag === "invoicePaymentFailed") return v(e.tenantId).pipe(p.asVoid);
838
+ if (e._tag !== "invoicePaid" && e._tag !== "subscriptionUpserted") return p.void;
839
+ let t = e.tenantId;
840
+ return c.stores.subscriptions.getByTenant(t).pipe(p.flatMap((e) => e !== null && (e.status === "pastDue" || e.pastDueSince !== null) ? v(t).pipe(p.asVoid) : p.void));
841
+ }, x = (e) => {
499
842
  switch (e._tag) {
500
- case "subscriptionUpserted": return l.stores.subscriptions.upsert({
843
+ case "subscriptionUpserted": return c.stores.subscriptions.upsert({
501
844
  tenantId: e.tenantId,
502
- provider: i.name,
845
+ provider: n.name,
503
846
  providerSubscriptionId: e.providerSubscriptionId,
504
847
  plan: e.plan,
505
848
  status: e.status,
506
849
  quantity: e.quantity ?? 1,
507
850
  currentPeriodStart: e.currentPeriodStart ?? null,
508
851
  currentPeriodEnd: e.currentPeriodEnd,
509
- cancelAt: e.cancelAt
852
+ cancelAt: e.cancelAt,
853
+ pastDueSince: null,
854
+ statusEventAt: e.occurredAt
510
855
  });
511
- case "subscriptionCanceled": return l.stores.subscriptions.markCanceled(e.providerSubscriptionId);
512
- case "invoicePaid": return l.stores.invoices.upsert({
856
+ case "subscriptionCanceled": return c.stores.subscriptions.markCanceled(e.providerSubscriptionId);
857
+ case "invoicePaid": return c.stores.invoices.upsert({
513
858
  tenantId: e.tenantId,
514
- provider: i.name,
859
+ provider: n.name,
515
860
  providerInvoiceId: e.providerInvoiceId,
516
861
  amountMinor: e.amountMinor,
517
862
  currency: e.currency,
518
- status: "paid"
863
+ status: "paid",
864
+ statusEventAt: e.occurredAt
519
865
  });
520
- case "invoicePaymentFailed": return l.stores.invoices.upsert({
866
+ case "invoicePaymentFailed": return c.stores.invoices.upsert({
521
867
  tenantId: e.tenantId,
522
- provider: i.name,
868
+ provider: n.name,
523
869
  providerInvoiceId: e.providerInvoiceId,
524
870
  amountMinor: e.amountMinor,
525
871
  currency: e.currency,
526
- status: "open"
872
+ status: "open",
873
+ statusEventAt: e.occurredAt
527
874
  });
528
- case "customerLinked": return l.stores.customers.upsert({
875
+ case "customerLinked": return c.stores.customers.upsert({
529
876
  tenantId: e.tenantId,
530
- provider: i.name,
877
+ provider: n.name,
531
878
  providerCustomerId: e.providerCustomerId
532
879
  });
533
880
  }
534
- }, v = (t, n) => f.gen(function* () {
535
- let r = yield* l.stores.subscriptions.getByTenant(t);
536
- if (r === null) return yield* f.fail(new e({
881
+ }, S = (e) => x(e).pipe(p.zipRight(b(e))), C = (t, r) => p.gen(function* () {
882
+ let o = yield* c.stores.subscriptions.getByTenant(t);
883
+ if (o === null) return yield* p.fail(new e({
537
884
  source: "billing",
538
885
  message: `no subscription for tenant '${t}' to change`,
539
886
  transient: !1
540
887
  }));
541
- let s = n.plan ?? r.plan, c = n.quantity ?? r.quantity;
542
- if (n.plan !== void 0 && !a.has(s)) return yield* f.fail(new e({
888
+ let s = r.plan ?? o.plan, l = r.quantity ?? o.quantity;
889
+ if (r.plan !== void 0 && !i.has(s)) return yield* p.fail(new e({
543
890
  source: "billing",
544
891
  message: `unknown plan '${s}' — not in the plan registry`,
545
892
  transient: !1
546
893
  }));
547
- if (!Number.isInteger(c) || c <= 0) return yield* f.fail(new e({
894
+ if (!Number.isInteger(l) || l <= 0) return yield* p.fail(new e({
548
895
  source: "billing",
549
- message: `seat quantity must be a positive integer, got ${c}`,
896
+ message: `seat quantity must be a positive integer, got ${l}`,
550
897
  transient: !1
551
898
  }));
552
- let u = yield* K(i.updateSubscription({
553
- providerSubscriptionId: r.providerSubscriptionId,
554
- ...n.plan === void 0 ? {} : { priceId: a.priceIdFor(s) ?? s },
555
- ...n.quantity === void 0 ? {} : { quantity: c },
899
+ let u = yield* J(n.updateSubscription({
900
+ providerSubscriptionId: o.providerSubscriptionId,
901
+ ...r.plan === void 0 ? {} : { priceId: i.priceIdFor(s) ?? s },
902
+ ...r.quantity === void 0 ? {} : { quantity: l },
556
903
  proration: "prorate"
557
- }), o);
558
- return yield* l.stores.subscriptions.patchByTenant(t, {
559
- ...n.plan === void 0 ? {} : { plan: s },
904
+ }), a);
905
+ return yield* c.stores.subscriptions.patchByTenant(t, {
906
+ ...r.plan === void 0 ? {} : { plan: s },
560
907
  quantity: u.quantity,
561
908
  ...u.currentPeriodStart === null ? {} : { currentPeriodStart: u.currentPeriodStart },
562
909
  ...u.currentPeriodEnd === null ? {} : { currentPeriodEnd: u.currentPeriodEnd }
@@ -570,46 +917,49 @@ var A = (e, t, n, r) => ({
570
917
  });
571
918
  return {
572
919
  service: {
573
- subscription: ee,
574
- plan: u,
575
- checkEntitlement: p,
920
+ subscription: f,
921
+ plan: d,
922
+ entitlementStatus: u,
923
+ reconcileDunning: v,
924
+ dunningSweep: y,
925
+ checkEntitlement: te,
576
926
  consumeEntitlement: m,
577
927
  reportUsage: h,
578
- flushUsage: g,
579
- startCheckout: te,
580
- portalUrl: ne,
581
- applyEvent: _,
582
- changePlan: (e, t) => v(e, { plan: t }),
583
- changeSeats: (e, t) => v(e, { quantity: t }),
584
- previewChange: (t, n) => f.gen(function* () {
585
- let r = yield* l.stores.subscriptions.getByTenant(t);
586
- if (r === null) return yield* f.fail(new e({
928
+ flushUsage: ne,
929
+ startCheckout: g,
930
+ portalUrl: re,
931
+ applyEvent: S,
932
+ changePlan: (e, t) => C(e, { plan: t }),
933
+ changeSeats: (e, t) => C(e, { quantity: t }),
934
+ previewChange: (t, r) => p.gen(function* () {
935
+ let o = yield* c.stores.subscriptions.getByTenant(t);
936
+ if (o === null) return yield* p.fail(new e({
587
937
  source: "billing",
588
938
  message: `no subscription for tenant '${t}' to preview`,
589
939
  transient: !1
590
940
  }));
591
- let s = n.plan ?? r.plan, c = yield* K(i.previewSubscriptionChange({
592
- providerSubscriptionId: r.providerSubscriptionId,
593
- ...n.plan === void 0 ? {} : { priceId: a.priceIdFor(s) ?? s },
594
- ...n.quantity === void 0 ? {} : { quantity: n.quantity },
941
+ let s = r.plan ?? o.plan, l = yield* J(n.previewSubscriptionChange({
942
+ providerSubscriptionId: o.providerSubscriptionId,
943
+ ...r.plan === void 0 ? {} : { priceId: i.priceIdFor(s) ?? s },
944
+ ...r.quantity === void 0 ? {} : { quantity: r.quantity },
595
945
  proration: "prorate"
596
- }), o);
946
+ }), a);
597
947
  return {
598
948
  tenantId: t,
599
949
  plan: s,
600
- quantity: c.quantity,
601
- prorationMinor: c.prorationMinor,
602
- currency: c.currency
950
+ quantity: l.quantity,
951
+ prorationMinor: l.prorationMinor,
952
+ currency: l.currency
603
953
  };
604
954
  }),
605
- invoices: (e) => f.gen(function* () {
606
- let t = yield* l.stores.customers.getByTenant(e);
607
- return t === null ? [] : yield* K(i.listInvoices({ providerCustomerId: t.providerCustomerId }), o);
955
+ invoices: (e) => p.gen(function* () {
956
+ let t = yield* c.stores.customers.getByTenant(e);
957
+ return t === null ? [] : yield* J(n.listInvoices({ providerCustomerId: t.providerCustomerId }), a);
608
958
  })
609
959
  },
610
- holder: l
960
+ holder: c
611
961
  };
612
- }, Te = (e) => p.succeed(t, e), Ee = "_voltro_ai_usage", De = (e) => {
962
+ }, Ye = (e) => m.succeed(t, e), Xe = "_voltro_ai_usage", Ze = (e) => {
613
963
  for (let [t, n] of Object.entries(e)) {
614
964
  if (t.length === 0) throw Error("billing metering: a meter key must be non-empty");
615
965
  if (n.from === "rpc") {
@@ -618,29 +968,33 @@ var A = (e, t, n, r) => ({
618
968
  if (typeof n.table != "string" || n.table.length === 0) throw Error(`billing metering: cdc meter '${t}' needs a 'table'`);
619
969
  } else if (n.from !== "ai") throw Error(`billing metering: meter '${t}' has unknown source '${String(n.from)}'`);
620
970
  }
621
- }, Oe = (e, t) => typeof e == "string" ? t === e : e.test(t), ke = (e, t, n) => Object.entries(e).filter(([, e]) => e.from === "rpc" && (e.kind === void 0 || e.kind === n) && Oe(e.match, t)).map(([e]) => e), Ae = (e, t, n) => Object.entries(e).filter(([, e]) => e.from === "cdc" && e.table === t && (e.op ?? "insert") === n).map(([e]) => e), je = (e) => Object.entries(e).filter(([, e]) => e.from === "ai").map(([e, t]) => [e, t.metric ?? "tokens"]), Me = (e) => e.tenantId, Ne = (e, t, n, r) => f.forEach(n, (n) => e.reportUsage(t, n, r).pipe(f.catchAll(() => f.void)), { discard: !0 }), Pe = (e, t) => (n, r) => {
622
- let i = ke(t, r.tag, r.kind);
971
+ }, Qe = /* @__PURE__ */ new WeakMap(), $e = (e) => {
972
+ if (!e.global && !e.sticky) return e;
973
+ let t = Qe.get(e);
974
+ return t === void 0 && (t = new RegExp(e.source, e.flags.replace(/[gy]/g, "")), Qe.set(e, t)), t;
975
+ }, et = (e, t) => typeof e == "string" ? t === e : $e(e).test(t), tt = (e, t, n) => Object.entries(e).filter(([, e]) => e.from === "rpc" && (e.kind === void 0 || e.kind === n) && et(e.match, t)).map(([e]) => e), nt = (e, t, n) => Object.entries(e).filter(([, e]) => e.from === "cdc" && e.table === t && (e.op ?? "insert") === n).map(([e]) => e), rt = (e) => Object.entries(e).filter(([, e]) => e.from === "ai").map(([e, t]) => [e, t.metric ?? "tokens"]), it = (e) => e.tenantId, at = (e, t, n, r) => p.forEach(n, (n) => e.reportUsage(t, n, r).pipe(p.catchAll(() => p.void)), { discard: !0 }), ot = (e, t) => (n, r) => {
976
+ let i = tt(t, r.tag, r.kind);
623
977
  if (i.length === 0) return n;
624
- let a = Me(r.subject);
625
- return a === null ? n : n.pipe(f.tap(() => Ne(e, a, i, 1)));
626
- }, Fe = (e, t) => (n) => {
627
- let r = Ae(t, n.table, n.op);
628
- if (r.length === 0) return f.void;
978
+ let a = it(r.subject);
979
+ return a === null ? n : n.pipe(p.tap(() => at(e, a, i, 1)));
980
+ }, st = (e, t) => (n) => {
981
+ let r = nt(t, n.table, n.op);
982
+ if (r.length === 0) return p.void;
629
983
  let i = n.new?.tenantId ?? n.old?.tenantId ?? null;
630
- return i === null ? f.void : Ne(e, i, r, 1);
631
- }, Ie = (e) => new Date(Date.UTC(e.getUTCFullYear(), e.getUTCMonth(), 1, 0, 0, 0, 0)), Le = (e, t, n, r = () => /* @__PURE__ */ new Date()) => f.gen(function* () {
632
- let i = je(n);
984
+ return i === null ? p.void : at(e, i, r, 1);
985
+ }, ct = (e) => new Date(Date.UTC(e.getUTCFullYear(), e.getUTCMonth(), 1, 0, 0, 0, 0)), lt = (e, t, n, r = () => /* @__PURE__ */ new Date()) => p.gen(function* () {
986
+ let i = rt(n);
633
987
  if (i.length === 0) return;
634
988
  let a = r();
635
- G(a);
989
+ q(a);
636
990
  let o = {
637
- table: Ee,
638
- predicate: re("calledAt", Ie(a)),
991
+ table: Xe,
992
+ predicate: y("calledAt", ct(a)),
639
993
  order: [],
640
994
  take: void 0,
641
995
  skip: void 0,
642
996
  projection: void 0
643
- }, s = yield* f.promise(() => Promise.resolve(t.query(o)).then((e) => e).catch(() => [])), c = /* @__PURE__ */ new Map();
997
+ }, s = yield* p.promise(() => Promise.resolve(t.query(o)).then((e) => e).catch(() => [])), c = /* @__PURE__ */ new Map();
644
998
  for (let e of s) {
645
999
  let t = e.tenantId ?? null;
646
1000
  if (t === null) continue;
@@ -653,17 +1007,17 @@ var A = (e, t, n, r) => ({
653
1007
  for (let [t, n] of i) for (let [r, i] of c) {
654
1008
  let a = n === "costMicroUsd" ? i.cost : i.tokens;
655
1009
  if (a <= 0) continue;
656
- let o = a - (yield* e.checkEntitlement(r, t, 0).pipe(f.map((e) => e.used), f.catchAll(() => f.succeed(0))));
657
- o > 0 && (yield* e.reportUsage(r, t, o).pipe(f.catchAll(() => f.void)));
1010
+ let o = a - (yield* e.checkEntitlement(r, t, 0).pipe(p.map((e) => e.used), p.catchAll(() => p.succeed(0))));
1011
+ o > 0 && (yield* e.reportUsage(r, t, o).pipe(p.catchAll(() => p.void)));
658
1012
  }
659
- }), Re = (e, t, n, r) => Le(e, t, n, r).pipe(f.andThen(e.flushUsage().pipe(f.catchAll(() => f.void)))), ze = (e) => Object.values(e).some((e) => e.from === "cdc"), Be = (e) => e === void 0 ? 0 : e === "unlimited" ? Infinity : e, q = (e) => {
1013
+ }), ut = (e, t, n, r) => lt(e, t, n, r).pipe(p.andThen(e.flushUsage().pipe(p.catchAll(() => p.void)))), dt = (e) => Object.values(e).some((e) => e.from === "cdc"), ft = (e) => e === void 0 ? 0 : e === "unlimited" ? Infinity : e, pt = (e) => {
660
1014
  let t = /* @__PURE__ */ new Map();
661
1015
  for (let [n, r] of Object.entries(e)) r.priceId !== void 0 && t.set(r.priceId, n);
662
1016
  return {
663
1017
  has: (t) => Object.prototype.hasOwnProperty.call(e, t),
664
1018
  entitlementLimit: (t, n) => {
665
1019
  let r = e[t];
666
- return r === void 0 ? 0 : Be(r.entitlements[n]);
1020
+ return r === void 0 ? 0 : ft(r.entitlements[n]);
667
1021
  },
668
1022
  planForPriceId: (e) => t.get(e) ?? null,
669
1023
  priceIdFor: (t) => e[t]?.priceId ?? null,
@@ -674,14 +1028,14 @@ var A = (e, t, n, r) => ({
674
1028
  currencyFor: (t) => (e[t]?.currency ?? "usd").toLowerCase(),
675
1029
  planIds: () => Object.keys(e)
676
1030
  };
677
- }, Ve = () => q({ [n]: { entitlements: {} } }), J = "_voltro_billing_flush_claims", He = S(J, {
678
- id: y({ scheme: "numeric" }),
679
- windowKey: C(),
680
- claimedBy: C(),
681
- claimedAt: w().default("now")
682
- }).unique("billingFlushClaimWindow", ["windowKey"]), Ue = (e, t) => `billing-flush:${Math.floor(e / Math.max(1, t))}`, We = async (e, t, n, r) => {
1031
+ }, mt = () => pt({ [n]: { entitlements: {} } }), Y = "_voltro_billing_flush_claims", ht = w(Y, {
1032
+ id: b({ scheme: "numeric" }),
1033
+ windowKey: T(),
1034
+ claimedBy: T(),
1035
+ claimedAt: E().default("now")
1036
+ }).unique("billingFlushClaimWindow", ["windowKey"]), gt = (e, t) => `billing-flush:${Math.floor(e / Math.max(1, t))}`, _t = async (e, t, n, r) => {
683
1037
  try {
684
- return (await e.insertIgnore(J, {
1038
+ return (await e.insertIgnore(Y, {
685
1039
  windowKey: t,
686
1040
  claimedBy: n,
687
1041
  claimedAt: new Date(r)
@@ -689,38 +1043,38 @@ var A = (e, t, n, r) => ({
689
1043
  } catch {
690
1044
  return !0;
691
1045
  }
692
- }, Ge = 3, Ke = 2e4, qe = (e) => e.client ?? new T(e.apiKey, {
693
- maxNetworkRetries: Ge,
694
- timeout: Ke,
1046
+ }, vt = 3, yt = 2e4, bt = (e) => e.client ?? new O(e.apiKey, {
1047
+ maxNetworkRetries: vt,
1048
+ timeout: yt,
695
1049
  appInfo: {
696
1050
  name: "voltro",
697
1051
  url: "https://voltro.dev"
698
1052
  }
699
- }), Je = (e) => e instanceof T.errors.StripeConnectionError || e instanceof T.errors.StripeAPIError || e instanceof T.errors.StripeRateLimitError, Ye = (e) => e instanceof T.errors.StripeError ? e.code ?? e.type : void 0, Y = (t, n) => f.tryPromise({
1053
+ }), xt = (e) => e instanceof O.errors.StripeConnectionError || e instanceof O.errors.StripeAPIError || e instanceof O.errors.StripeRateLimitError, St = (e) => e instanceof O.errors.StripeError ? e.code ?? e.type : void 0, X = (t, n) => p.tryPromise({
700
1054
  try: n,
701
1055
  catch: (n) => new e({
702
1056
  source: t,
703
- message: Xe(n),
704
- transient: Je(n)
1057
+ message: Ct(n),
1058
+ transient: xt(n)
705
1059
  })
706
- }), Xe = (e) => {
707
- if (e instanceof T.errors.StripeError) {
708
- let t = Ye(e);
1060
+ }), Ct = (e) => {
1061
+ if (e instanceof O.errors.StripeError) {
1062
+ let t = St(e);
709
1063
  return t === void 0 ? e.message : `${e.message} (${t})`;
710
1064
  }
711
1065
  return String(e?.message ?? e);
712
- }, Ze = (e) => {
1066
+ }, wt = (e) => {
713
1067
  let t = e.items?.data?.[0];
714
1068
  return {
715
- start: X(t?.current_period_start),
716
- end: X(t?.current_period_end)
1069
+ start: Z(t?.current_period_start),
1070
+ end: Z(t?.current_period_end)
717
1071
  };
718
- }, X = (e) => typeof e == "number" && Number.isFinite(e) ? /* @__PURE__ */ new Date(e * 1e3) : null, Qe = (e) => {
1072
+ }, Z = (e) => typeof e == "number" && Number.isFinite(e) ? /* @__PURE__ */ new Date(e * 1e3) : null, Tt = (e) => {
719
1073
  let t = e.items?.data ?? [];
720
1074
  if (t.length === 0) return 1;
721
1075
  let n = t.reduce((e, t) => e + (t.quantity ?? 0), 0);
722
1076
  return n > 0 ? n : 1;
723
- }, $e = {
1077
+ }, Et = {
724
1078
  active: "active",
725
1079
  trialing: "trialing",
726
1080
  past_due: "pastDue",
@@ -729,79 +1083,84 @@ var A = (e, t, n, r) => ({
729
1083
  incomplete: "incomplete",
730
1084
  incomplete_expired: "canceled",
731
1085
  paused: "pastDue"
732
- }, Z = (e) => {
1086
+ }, Q = (e) => {
733
1087
  let t = e.metadata?.tenantId;
734
1088
  if (typeof t == "string" && t.length > 0) return t;
735
1089
  let n = e.client_reference_id;
736
1090
  return typeof n == "string" && n.length > 0 ? n : null;
737
- }, et = (e) => {
1091
+ }, Dt = (e) => {
738
1092
  let t = e.items?.data?.[0]?.price;
739
1093
  return typeof t?.id == "string" ? t.id : null;
740
- }, tt = (e, t) => {
1094
+ }, Ot = (e, t) => {
741
1095
  let n = e.metadata?.plan;
742
1096
  if (typeof n == "string" && n.length > 0) return n;
743
- let r = et(e);
1097
+ let r = Dt(e);
744
1098
  return r !== null && t ? t(r) : null;
745
- }, nt = (e, t) => {
1099
+ }, kt = (e, t) => {
1100
+ let n = Z(e.created);
746
1101
  switch (e.type) {
747
1102
  case "customer.subscription.created":
748
1103
  case "customer.subscription.updated":
749
1104
  case "customer.subscription.resumed":
750
1105
  case "customer.subscription.paused": {
751
- let n = e.data.object, r = Z(n), i = tt(n, t), a = $e[n.status];
752
- if (r === null || i === null || a === void 0) return null;
753
- let o = Ze(n);
1106
+ let r = e.data.object, i = Q(r), a = Ot(r, t), o = Et[r.status];
1107
+ if (i === null || a === null || o === void 0) return null;
1108
+ let s = wt(r);
754
1109
  return {
755
1110
  _tag: "subscriptionUpserted",
756
- tenantId: r,
757
- providerSubscriptionId: n.id,
758
- plan: i,
759
- status: a,
760
- quantity: Qe(n),
761
- currentPeriodStart: o.start,
762
- currentPeriodEnd: o.end,
763
- cancelAt: X(n.cancel_at)
1111
+ tenantId: i,
1112
+ providerSubscriptionId: r.id,
1113
+ plan: a,
1114
+ status: o,
1115
+ quantity: Tt(r),
1116
+ currentPeriodStart: s.start,
1117
+ currentPeriodEnd: s.end,
1118
+ cancelAt: Z(r.cancel_at),
1119
+ occurredAt: n
764
1120
  };
765
1121
  }
766
1122
  case "customer.subscription.deleted": {
767
- let t = e.data.object, n = Z(t);
768
- return n === null ? null : {
1123
+ let t = e.data.object, r = Q(t);
1124
+ return r === null ? null : {
769
1125
  _tag: "subscriptionCanceled",
770
- tenantId: n,
771
- providerSubscriptionId: t.id
1126
+ tenantId: r,
1127
+ providerSubscriptionId: t.id,
1128
+ occurredAt: n
772
1129
  };
773
1130
  }
774
1131
  case "invoice.paid":
775
1132
  case "invoice.payment_failed": {
776
- let t = e.data.object, n = Z(t);
777
- return n === null || typeof t.id != "string" ? null : {
1133
+ let t = e.data.object, r = Q(t);
1134
+ return r === null || typeof t.id != "string" ? null : {
778
1135
  _tag: e.type === "invoice.paid" ? "invoicePaid" : "invoicePaymentFailed",
779
- tenantId: n,
1136
+ tenantId: r,
780
1137
  providerInvoiceId: t.id,
781
1138
  amountMinor: t.amount_due,
782
- currency: t.currency
1139
+ currency: t.currency,
1140
+ occurredAt: n
783
1141
  };
784
1142
  }
785
1143
  case "customer.created": {
786
- let t = e.data.object, n = Z(t);
787
- return n === null ? null : {
1144
+ let t = e.data.object, r = Q(t);
1145
+ return r === null ? null : {
788
1146
  _tag: "customerLinked",
789
- tenantId: n,
790
- providerCustomerId: t.id
1147
+ tenantId: r,
1148
+ providerCustomerId: t.id,
1149
+ occurredAt: n
791
1150
  };
792
1151
  }
793
1152
  default: return null;
794
1153
  }
795
- }, rt = {
1154
+ }, At = {
796
1155
  prorate: "create_prorations",
797
1156
  none: "none",
798
1157
  always_invoice: "always_invoice"
799
- }, it = (t) => {
800
- let n = qe(t), r = t.planForPriceId, i = (e) => n.subscriptions.retrieve(e, { expand: ["items.data.price"] }), a = (e, t) => {
801
- let n = Ze(e), i = e.items?.data?.[0]?.price;
1158
+ }, jt = (t) => {
1159
+ let n = bt(t), r = t.planForPriceId, i = (e) => n.subscriptions.retrieve(e, { expand: ["items.data.price"] }), a = (e, t) => {
1160
+ let n = wt(e), i = e.items?.data?.[0]?.price;
802
1161
  return {
803
- plan: tt(e, r) ?? "free",
804
- quantity: Qe(e),
1162
+ plan: Ot(e, r) ?? "free",
1163
+ quantity: Tt(e),
805
1164
  prorationMinor: t,
806
1165
  currency: (i?.currency ?? "usd").toLowerCase(),
807
1166
  currentPeriodStart: n.start,
@@ -815,7 +1174,7 @@ var A = (e, t, n, r) => ({
815
1174
  ...t.priceId === void 0 ? {} : { price: t.priceId },
816
1175
  ...t.quantity === void 0 ? {} : { quantity: t.quantity }
817
1176
  }],
818
- proration_behavior: rt[t.proration ?? "prorate"],
1177
+ proration_behavior: At[t.proration ?? "prorate"],
819
1178
  ...t.priceId !== void 0 && r ? { metadata: {
820
1179
  ...e.metadata,
821
1180
  plan: r(t.priceId) ?? ""
@@ -825,7 +1184,7 @@ var A = (e, t, n, r) => ({
825
1184
  return {
826
1185
  name: "stripe",
827
1186
  supportsMeteredUsage: t.supportsMeteredUsage ?? !0,
828
- createCheckoutSession: (e) => Y("stripe.checkout.sessions.create", async () => {
1187
+ createCheckoutSession: (e) => X("stripe.checkout.sessions.create", async () => {
829
1188
  let t = await n.checkout.sessions.create({
830
1189
  mode: "subscription",
831
1190
  line_items: [{
@@ -857,11 +1216,11 @@ var A = (e, t, n, r) => ({
857
1216
  if (typeof t.url != "string") throw Error("checkout session has no url");
858
1217
  return { url: t.url };
859
1218
  }),
860
- createPortalSession: (e) => Y("stripe.billingPortal.sessions.create", async () => ({ url: (await n.billingPortal.sessions.create({
1219
+ createPortalSession: (e) => X("stripe.billingPortal.sessions.create", async () => ({ url: (await n.billingPortal.sessions.create({
861
1220
  customer: e.providerCustomerId,
862
1221
  return_url: e.returnUrl
863
1222
  })).url })),
864
- reportUsage: (e) => Y("stripe.billing.meterEvents.create", async () => {
1223
+ reportUsage: (e) => X("stripe.billing.meterEvents.create", async () => {
865
1224
  await n.billing.meterEvents.create({
866
1225
  event_name: e.entitlementKey,
867
1226
  identifier: `${e.tenantId}:${e.entitlementKey}:${e.periodEnd?.toISOString() ?? "current"}`,
@@ -871,11 +1230,11 @@ var A = (e, t, n, r) => ({
871
1230
  }
872
1231
  });
873
1232
  }),
874
- updateSubscription: (e) => Y("stripe.subscriptions.update", async () => {
875
- let t = await i(e.providerSubscriptionId), r = await n.subscriptions.update(e.providerSubscriptionId, o(t, e)), s = await ot(n, r);
1233
+ updateSubscription: (e) => X("stripe.subscriptions.update", async () => {
1234
+ let t = await i(e.providerSubscriptionId), r = await n.subscriptions.update(e.providerSubscriptionId, o(t, e)), s = await Nt(n, r);
876
1235
  return a(r, s);
877
1236
  }),
878
- previewSubscriptionChange: (e) => Y("stripe.invoices.createPreview", async () => {
1237
+ previewSubscriptionChange: (e) => X("stripe.invoices.createPreview", async () => {
879
1238
  let t = await i(e.providerSubscriptionId), s = o(t, e), c = (await n.invoices.createPreview({
880
1239
  subscription: e.providerSubscriptionId,
881
1240
  subscription_details: {
@@ -889,7 +1248,27 @@ var A = (e, t, n, r) => ({
889
1248
  ...e.priceId !== void 0 && r ? { plan: r(e.priceId) ?? "free" } : {}
890
1249
  };
891
1250
  }),
892
- listInvoices: (e) => Y("stripe.invoices.list", async () => (await n.invoices.list({
1251
+ fetchSubscription: (e) => X("stripe.subscriptions.retrieve", async () => {
1252
+ try {
1253
+ let t = await i(e), n = wt(t);
1254
+ return {
1255
+ status: Et[t.status] ?? "incomplete",
1256
+ plan: Ot(t, r),
1257
+ quantity: Tt(t),
1258
+ currentPeriodStart: n.start,
1259
+ currentPeriodEnd: n.end,
1260
+ cancelAt: Z(t.cancel_at)
1261
+ };
1262
+ } catch (e) {
1263
+ if (e instanceof O.errors.StripeInvalidRequestError && e.statusCode === 404) return null;
1264
+ throw e;
1265
+ }
1266
+ }),
1267
+ customerEmail: (e) => X("stripe.customers.retrieve", async () => {
1268
+ let t = await n.customers.retrieve(e);
1269
+ return t.deleted === !0 ? null : typeof t.email == "string" && t.email.length > 0 ? t.email : null;
1270
+ }),
1271
+ listInvoices: (e) => X("stripe.invoices.list", async () => (await n.invoices.list({
893
1272
  customer: e.providerCustomerId,
894
1273
  limit: e.limit ?? 20
895
1274
  })).data.map((e) => ({
@@ -897,11 +1276,11 @@ var A = (e, t, n, r) => ({
897
1276
  amountMinor: e.amount_due,
898
1277
  currency: e.currency,
899
1278
  status: e.status ?? "unknown",
900
- createdAt: X(e.created),
1279
+ createdAt: Z(e.created),
901
1280
  hostedUrl: e.hosted_invoice_url ?? null,
902
1281
  pdfUrl: e.invoice_pdf ?? null
903
1282
  }))),
904
- verifyWebhook: (t) => f.try({
1283
+ verifyWebhook: (t) => p.try({
905
1284
  try: () => n.webhooks.constructEvent(Buffer.from(t.rawBody), t.signatureHeader, t.secret),
906
1285
  catch: (t) => new e({
907
1286
  source: "stripe.webhooks.constructEvent",
@@ -909,27 +1288,36 @@ var A = (e, t, n, r) => ({
909
1288
  transient: !1
910
1289
  })
911
1290
  }),
912
- normalizeEvent: (e) => f.succeed(at(e) ? nt(e, r) : null)
1291
+ normalizeEvent: (e) => p.succeed(Mt(e) ? kt(e, r) : null)
913
1292
  };
914
- }, at = (e) => typeof e == "object" && !!e && typeof e.type == "string" && typeof e.data == "object", ot = async (e, t) => {
1293
+ }, Mt = (e) => typeof e == "object" && !!e && typeof e.type == "string" && typeof e.data == "object", Nt = async (e, t) => {
915
1294
  try {
916
1295
  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);
917
1296
  } catch {
918
1297
  return 0;
919
1298
  }
920
- }, st = (e) => {
921
- let t = e?.baseUrl ?? "https://mock.billing.local", n = [], r = [], i = (e) => typeof e == "object" && !!e && typeof e._tag == "string";
1299
+ }, Pt = (e) => {
1300
+ 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";
922
1301
  return {
923
1302
  name: "mock",
924
1303
  supportsMeteredUsage: e?.supportsMeteredUsage ?? !0,
925
1304
  pushed: n,
926
1305
  updates: r,
927
- createCheckoutSession: (e) => f.succeed({ url: `${t}/checkout?tenant=${encodeURIComponent(e.tenantId)}&price=${encodeURIComponent(e.priceId)}` }),
928
- createPortalSession: (e) => f.succeed({ url: `${t}/portal?customer=${encodeURIComponent(e.providerCustomerId)}` }),
929
- reportUsage: (e) => f.sync(() => {
1306
+ fetched: i,
1307
+ setSubscription: (e, t) => {
1308
+ t === null ? a.delete(e) : a.set(e, t);
1309
+ },
1310
+ setCustomerEmail: (e, t) => {
1311
+ t === null ? o.delete(e) : o.set(e, t);
1312
+ },
1313
+ fetchSubscription: (e) => p.sync(() => (i.push(e), a.get(e) ?? null)),
1314
+ customerEmail: (e) => p.sync(() => o.get(e) ?? null),
1315
+ createCheckoutSession: (e) => p.succeed({ url: `${t}/checkout?tenant=${encodeURIComponent(e.tenantId)}&price=${encodeURIComponent(e.priceId)}` }),
1316
+ createPortalSession: (e) => p.succeed({ url: `${t}/portal?customer=${encodeURIComponent(e.providerCustomerId)}` }),
1317
+ reportUsage: (e) => p.sync(() => {
930
1318
  n.push(e);
931
1319
  }),
932
- updateSubscription: (e) => f.sync(() => (r.push(e), {
1320
+ updateSubscription: (e) => p.sync(() => (r.push(e), {
933
1321
  plan: e.priceId ?? "mock-plan",
934
1322
  quantity: e.quantity ?? 1,
935
1323
  prorationMinor: 0,
@@ -937,7 +1325,7 @@ var A = (e, t, n, r) => ({
937
1325
  currentPeriodStart: null,
938
1326
  currentPeriodEnd: null
939
1327
  })),
940
- previewSubscriptionChange: (e) => f.succeed({
1328
+ previewSubscriptionChange: (e) => p.succeed({
941
1329
  plan: e.priceId ?? "mock-plan",
942
1330
  quantity: e.quantity ?? 1,
943
1331
  prorationMinor: 0,
@@ -945,55 +1333,55 @@ var A = (e, t, n, r) => ({
945
1333
  currentPeriodStart: null,
946
1334
  currentPeriodEnd: null
947
1335
  }),
948
- listInvoices: () => f.succeed([]),
949
- normalizeEvent: (e) => f.succeed(i(e) ? e : null)
1336
+ listInvoices: () => p.succeed([]),
1337
+ normalizeEvent: (e) => p.succeed(s(e) ? e : null)
950
1338
  };
951
- }, ct = (e) => {
1339
+ }, Ft = (e) => {
952
1340
  let { provider: t, env: n } = e;
953
1341
  if (t !== void 0 && typeof t == "object") return t;
954
1342
  if ((t ?? (n.STRIPE_SECRET_KEY ? "stripe" : "mock")) === "stripe") {
955
1343
  let t = e.apiKey ?? n.STRIPE_SECRET_KEY;
956
1344
  if (!t) throw Error("billingPlugin: provider 'stripe' needs an apiKey (option or STRIPE_SECRET_KEY env)");
957
- return it({
1345
+ return jt({
958
1346
  apiKey: t,
959
1347
  ...e.planForPriceId ? { planForPriceId: e.planForPriceId } : {}
960
1348
  });
961
1349
  }
962
- return st();
963
- }, lt = k({ scope: "@voltro/plugin-billing" }), ut = "billing", Q = "/billing/webhook", dt = (e) => E({
964
- id: ut,
965
- path: Q,
966
- provider: O(),
967
- payload: h.Unknown,
1350
+ return Pt();
1351
+ }, It = D({ scope: "@voltro/plugin-billing" }), Lt = "billing", $ = "/billing/webhook", Rt = (e) => oe({
1352
+ id: Lt,
1353
+ path: $,
1354
+ provider: A(),
1355
+ payload: ne.Unknown,
968
1356
  handler: async (t) => {
969
- let n = e.provider.verifyWebhook !== void 0 && e.webhookSecret !== null && e.webhookSecret !== void 0 ? await f.runPromise(e.provider.verifyWebhook({
1357
+ let n = e.provider.verifyWebhook !== void 0 && e.webhookSecret !== null && e.webhookSecret !== void 0 ? await p.runPromise(e.provider.verifyWebhook({
970
1358
  rawBody: t.rawBody,
971
1359
  signatureHeader: t.headers["stripe-signature"] ?? "",
972
1360
  secret: e.webhookSecret ?? ""
973
- })) : t.body, r = await f.runPromise(e.provider.normalizeEvent(n));
1361
+ })) : t.body, r = await p.runPromise(e.provider.normalizeEvent(n));
974
1362
  if (r === null) return;
975
- await f.runPromise(e.service.applyEvent(r));
1363
+ await p.runPromise(e.service.applyEvent(r));
976
1364
  let i = e.onEvent?.[r._tag];
977
- i !== void 0 && await f.runPromise(i(r).pipe(f.catchAllCause((e) => f.sync(() => {
978
- lt.warn("billing onEvent side effect failed", {
1365
+ i !== void 0 && await p.runPromise(i(r).pipe(p.catchAllCause((e) => p.sync(() => {
1366
+ It.warn("billing onEvent side effect failed", {
979
1367
  event: r._tag,
980
1368
  cause: String(e)
981
1369
  });
982
1370
  }))));
983
1371
  }
984
- }), ft = (e) => D(dt(e), {
1372
+ }), zt = (e) => k(Rt(e), {
985
1373
  resolveSecret: () => Promise.resolve(e.webhookSecret),
986
1374
  ...e.idempotencyCache === void 0 ? {} : { idempotencyCache: e.idempotencyCache },
987
1375
  log: (e) => {
988
- e.status >= 400 && lt.warn("billing webhook rejected", { ...e });
1376
+ e.status >= 400 && It.warn("billing webhook rejected", { ...e });
989
1377
  }
990
- }), pt = (e) => e === null ? null : {
1378
+ }), Bt = (e) => e === null ? null : {
991
1379
  plan: e.plan,
992
1380
  status: e.status,
993
1381
  quantity: e.quantity,
994
1382
  currentPeriodEnd: e.currentPeriodEnd ? e.currentPeriodEnd.toISOString() : null,
995
1383
  cancelAt: e.cancelAt ? e.cancelAt.toISOString() : null
996
- }, $ = g([{
1384
+ }, Vt = g([{
997
1385
  name: "STRIPE_SECRET_KEY",
998
1386
  required: !1,
999
1387
  secret: !0,
@@ -1003,40 +1391,50 @@ var A = (e, t, n, r) => ({
1003
1391
  required: !1,
1004
1392
  secret: !0,
1005
1393
  description: "Stripe webhook signing secret (whsec_…) for verifying webhook payloads."
1006
- }]), mt = (t = {}) => {
1007
- let n = process.env, p = q(t.plans ?? {}), m = ct({
1394
+ }]), Ht = "@voltro/plugin-billing", Ut = (t = {}) => {
1395
+ let n = process.env, m = pt(t.plans ?? {}), h = Ft({
1008
1396
  ...t.provider === void 0 ? {} : { provider: t.provider },
1009
1397
  ...t.apiKey === void 0 ? {} : { apiKey: t.apiKey },
1010
- planForPriceId: p.planForPriceId,
1398
+ planForPriceId: m.planForPriceId,
1011
1399
  env: n
1012
- }), h = t.webhookSecret ?? $.read("STRIPE_WEBHOOK_SECRET") ?? null;
1013
- x({
1400
+ }), ne = t.webhookSecret ?? Vt.read("STRIPE_WEBHOOK_SECRET") ?? null, g = be(t.dunning, n);
1401
+ S({
1402
+ source: "plugin",
1014
1403
  table: L,
1015
1404
  timeColumn: "updatedAt",
1016
- ttlMs: ie(process.env.VOLTRO_BILLING_USAGE_TTL_HOURS, 24 * 400),
1405
+ ttlMs: C(process.env.VOLTRO_BILLING_USAGE_TTL_HOURS, 24 * 400),
1017
1406
  label: "billing usage counters"
1018
- }), x({
1019
- table: J,
1407
+ }), S({
1408
+ source: "plugin",
1409
+ table: Y,
1020
1410
  timeColumn: "claimedAt",
1021
- ttlMs: ie(process.env.VOLTRO_BILLING_FLUSH_CLAIM_TTL_HOURS, 1),
1411
+ ttlMs: C(process.env.VOLTRO_BILLING_FLUSH_CLAIM_TTL_HOURS, 1),
1022
1412
  label: "billing flush claims"
1413
+ }), S({
1414
+ source: "plugin",
1415
+ table: R,
1416
+ timeColumn: "sentAt",
1417
+ ttlMs: C(process.env.VOLTRO_BILLING_DUNNING_TTL_HOURS, 24 * 400),
1418
+ label: "billing dunning notices"
1023
1419
  });
1024
- let { service: g, holder: _ } = we({
1025
- provider: m,
1026
- plans: p,
1420
+ let { service: _, holder: v } = Je({
1421
+ provider: h,
1422
+ plans: m,
1027
1423
  ...t.attempts === void 0 ? {} : { attempts: t.attempts },
1028
1424
  ...t.checkout === void 0 ? {} : { checkout: t.checkout },
1029
- ...t.resolveEntitlementLimit === void 0 ? {} : { resolveEntitlementLimit: t.resolveEntitlementLimit }
1030
- }), v = Te(g), re = ft({
1031
- provider: m,
1032
- service: g,
1033
- webhookSecret: h,
1425
+ ...t.resolveEntitlementLimit === void 0 ? {} : { resolveEntitlementLimit: t.resolveEntitlementLimit },
1426
+ dunning: g
1427
+ }), y = Ye(_), b = zt({
1428
+ provider: h,
1429
+ service: _,
1430
+ webhookSecret: ne,
1034
1431
  ...t.onEvent === void 0 ? {} : { onEvent: t.onEvent }
1035
- }), y = {
1432
+ }), x = {
1036
1433
  method: "POST",
1037
- path: Q,
1434
+ path: $,
1435
+ originGuard: "exempt",
1038
1436
  handle: async (e) => {
1039
- let t = await re({
1437
+ let t = await b({
1040
1438
  method: e.method,
1041
1439
  path: e.path,
1042
1440
  headers: e.headers,
@@ -1049,12 +1447,12 @@ var A = (e, t, n, r) => ({
1049
1447
  ...t.headers === void 0 ? {} : { headers: { ...t.headers } }
1050
1448
  };
1051
1449
  }
1052
- }, b = [
1450
+ }, w = [
1053
1451
  {
1054
1452
  ...ee,
1055
1453
  execute: (e, t) => {
1056
1454
  let n = e;
1057
- return g.startCheckout({
1455
+ return _.startCheckout({
1058
1456
  tenantId: t.request.subject.tenantId ?? "",
1059
1457
  plan: n.plan,
1060
1458
  successUrl: n.successUrl,
@@ -1064,24 +1462,37 @@ var A = (e, t, n, r) => ({
1064
1462
  }
1065
1463
  },
1066
1464
  {
1067
- ...c,
1465
+ ...u,
1068
1466
  execute: (e, t) => {
1069
1467
  let n = e;
1070
- return g.portalUrl(t.request.subject.tenantId ?? "", n.returnUrl);
1468
+ return _.portalUrl(t.request.subject.tenantId ?? "", n.returnUrl);
1071
1469
  }
1072
1470
  },
1073
1471
  {
1074
- ...d,
1075
- execute: (e, t) => g.subscription(t.request.subject.tenantId ?? "").pipe(f.map((e) => ({ subscription: pt(e) })))
1472
+ ...te,
1473
+ execute: (e, t) => _.subscription(t.request.subject.tenantId ?? "").pipe(p.map((e) => ({ subscription: Bt(e) })))
1076
1474
  },
1077
1475
  {
1078
- ...l,
1476
+ ...c,
1477
+ execute: (e, t) => _.entitlementStatus(t.request.subject.tenantId ?? "").pipe(p.map((e) => ({
1478
+ plan: e.plan,
1479
+ billedPlan: e.billedPlan,
1480
+ status: e.status,
1481
+ entitled: e.entitled,
1482
+ inGrace: e.inGrace,
1483
+ graceEndsAt: e.graceEndsAt ? e.graceEndsAt.toISOString() : null,
1484
+ lockedSince: e.lockedSince ? e.lockedSince.toISOString() : null,
1485
+ lockout: e.lockout
1486
+ })))
1487
+ },
1488
+ {
1489
+ ...d,
1079
1490
  execute: (e, t) => {
1080
1491
  let n = e;
1081
- return g.previewChange(t.request.subject.tenantId ?? "", {
1492
+ return _.previewChange(t.request.subject.tenantId ?? "", {
1082
1493
  ...n.plan === void 0 ? {} : { plan: n.plan },
1083
1494
  ...n.quantity === void 0 ? {} : { quantity: n.quantity }
1084
- }).pipe(f.map((e) => ({
1495
+ }).pipe(p.map((e) => ({
1085
1496
  plan: e.plan,
1086
1497
  quantity: e.quantity,
1087
1498
  prorationMinor: e.prorationMinor,
@@ -1090,24 +1501,24 @@ var A = (e, t, n, r) => ({
1090
1501
  }
1091
1502
  },
1092
1503
  {
1093
- ...s,
1094
- execute: (e, t) => g.invoices(t.request.subject.tenantId ?? "").pipe(f.map((e) => ({ invoices: e.map((e) => ({
1504
+ ...l,
1505
+ execute: (e, t) => _.invoices(t.request.subject.tenantId ?? "").pipe(p.map((e) => ({ invoices: e.map((e) => ({
1095
1506
  ...e,
1096
1507
  createdAt: e.createdAt?.toISOString() ?? null
1097
1508
  })) })))
1098
1509
  },
1099
1510
  {
1100
- ...u,
1511
+ ...f,
1101
1512
  execute: (e, t) => {
1102
1513
  let n = e;
1103
- return g.reportUsage(t.request.subject.tenantId ?? "", n.key, n.quantity).pipe(f.map(() => ({ ok: !0 })));
1514
+ return _.reportUsage(t.request.subject.tenantId ?? "", n.key, n.quantity).pipe(p.map(() => ({ ok: !0 })));
1104
1515
  }
1105
1516
  },
1106
1517
  {
1107
- ...a,
1518
+ ...o,
1108
1519
  execute: (e, t) => {
1109
1520
  let n = e;
1110
- return g.changePlan(t.request.subject.tenantId ?? "", n.plan).pipe(f.map((e) => ({
1521
+ return _.changePlan(t.request.subject.tenantId ?? "", n.plan).pipe(p.map((e) => ({
1111
1522
  plan: e.plan,
1112
1523
  quantity: e.quantity,
1113
1524
  prorationMinor: e.prorationMinor,
@@ -1116,10 +1527,10 @@ var A = (e, t, n, r) => ({
1116
1527
  }
1117
1528
  },
1118
1529
  {
1119
- ...o,
1530
+ ...s,
1120
1531
  execute: (e, t) => {
1121
1532
  let n = e;
1122
- return g.changeSeats(t.request.subject.tenantId ?? "", n.quantity).pipe(f.map((e) => ({
1533
+ return _.changeSeats(t.request.subject.tenantId ?? "", n.quantity).pipe(p.map((e) => ({
1123
1534
  plan: e.plan,
1124
1535
  quantity: e.quantity,
1125
1536
  prorationMinor: e.prorationMinor,
@@ -1127,101 +1538,120 @@ var A = (e, t, n, r) => ({
1127
1538
  })));
1128
1539
  }
1129
1540
  }
1130
- ], S = (e, t) => ({
1541
+ ], T = (e, t) => ({
1131
1542
  kind: "json",
1132
1543
  status: e,
1133
1544
  data: { error: t }
1134
- }), C = [{
1545
+ }), E = [{
1135
1546
  method: "GET",
1136
1547
  path: "/stats",
1137
1548
  description: "Billing config: provider, declared plans, enforced tags",
1138
- handler: () => f.succeed({
1549
+ handler: () => p.succeed({
1139
1550
  kind: "json",
1140
1551
  data: {
1141
- provider: m.name,
1142
- supportsMeteredUsage: m.supportsMeteredUsage,
1143
- plans: p.planIds(),
1552
+ provider: h.name,
1553
+ supportsMeteredUsage: h.supportsMeteredUsage,
1554
+ plans: m.planIds(),
1144
1555
  enforce: t.enforce ? Object.keys(t.enforce) : [],
1145
- webhookPath: Q
1556
+ webhookPath: $
1146
1557
  }
1147
1558
  })
1148
1559
  }, {
1149
1560
  method: "GET",
1150
1561
  path: "/usage",
1151
1562
  description: "Pending (un-flushed) metered usage counters",
1152
- handler: () => _.stores.usage.pending().pipe(f.map((e) => ({
1563
+ handler: () => v.stores.usage.pending().pipe(p.map((e) => ({
1153
1564
  kind: "json",
1154
1565
  data: { usage: e }
1155
- })), f.catchAll((e) => f.succeed(S(500, e.message))))
1156
- }], w = [{
1157
- schema: e,
1158
- import: {
1159
- module: "@voltro/plugin-billing/errors",
1160
- name: "BillingError"
1161
- }
1162
- }, {
1163
- schema: r,
1164
- import: {
1165
- module: "@voltro/plugin-billing/errors",
1166
- name: "EntitlementExceeded"
1566
+ })), p.catchAll((e) => p.succeed(T(500, e.message))))
1567
+ }], D = [
1568
+ {
1569
+ schema: e,
1570
+ import: {
1571
+ module: "@voltro/plugin-billing/errors",
1572
+ name: "BillingError"
1573
+ }
1574
+ },
1575
+ {
1576
+ schema: r,
1577
+ import: {
1578
+ module: "@voltro/plugin-billing/errors",
1579
+ name: "EntitlementExceeded"
1580
+ }
1581
+ },
1582
+ {
1583
+ schema: i,
1584
+ import: {
1585
+ module: "@voltro/plugin-billing/errors",
1586
+ name: "SubscriptionLocked"
1587
+ }
1167
1588
  }
1168
- }], T = t.enforce, E = T !== void 0 && Object.keys(T).length > 0 ? N(T, g) : void 0, D = t.metering;
1169
- D !== void 0 && De(D);
1170
- let O = D !== void 0 && Object.keys(D).length > 0 ? Pe(g, D) : void 0, k = D !== void 0 && ze(D), A = te([O, E].filter((e) => e !== void 0)), j, M, P = [
1589
+ ], O = t.enforce, oe = O !== void 0 && Object.keys(O).length > 0 ? ce(O, _) : void 0, k = t.metering;
1590
+ k !== void 0 && Ze(k);
1591
+ let A = k !== void 0 && Object.keys(k).length > 0 ? ot(_, k) : void 0, j = k !== void 0 && dt(k), M = re([A, oe].filter((e) => e !== void 0)), se, N, P = [
1171
1592
  "store:write",
1172
1593
  "inspect:read",
1173
1594
  "inspect:write",
1174
1595
  "network:outbound:*",
1175
- ...A === void 0 ? [] : ["rpc:intercept:mutation", "rpc:intercept:action"],
1176
- ...O === void 0 ? [] : ["rpc:intercept:query"],
1177
- ...k ? ["store:changes:read"] : []
1596
+ ...M === void 0 ? [] : ["rpc:intercept:mutation", "rpc:intercept:action"],
1597
+ ...A === void 0 ? [] : ["rpc:intercept:query"],
1598
+ ...j ? ["store:changes:read"] : []
1178
1599
  ];
1179
- return ne({
1180
- name: t.name ? `@voltro/plugin-billing#${t.name}` : "@voltro/plugin-billing",
1600
+ return ie({
1601
+ name: ae({
1602
+ base: Ht,
1603
+ alias: t.alias,
1604
+ instance: t.name
1605
+ }),
1606
+ baseName: Ht,
1181
1607
  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.",
1182
1608
  permissions: P,
1183
- declaredEnv: $.declared,
1184
- services: v,
1185
- routes: b,
1186
- rpcClientDescriptors: i,
1187
- httpRoutes: [y],
1188
- inspectEndpoints: C,
1189
- extendSchema: { tables: [...le(), He] },
1190
- errorSchemas: w,
1191
- ...A === void 0 ? {} : {
1192
- interceptMutation: A,
1193
- interceptAction: A
1609
+ declaredEnv: Vt.declared,
1610
+ services: y,
1611
+ routes: w,
1612
+ rpcClientDescriptors: a,
1613
+ httpRoutes: [x],
1614
+ inspectEndpoints: E,
1615
+ extendSchema: { tables: [
1616
+ ...pe(),
1617
+ ht,
1618
+ me
1619
+ ] },
1620
+ errorSchemas: D,
1621
+ ...M === void 0 ? {} : {
1622
+ interceptMutation: M,
1623
+ interceptAction: M
1194
1624
  },
1195
- ...O === void 0 ? {} : { interceptQuery: O },
1196
- ...k ? { onChangeEvent: Fe(g, D) } : {},
1625
+ ...A === void 0 ? {} : { interceptQuery: A },
1626
+ ...j ? { onChangeEvent: st(_, k) } : {},
1197
1627
  bindDataStore: (e) => {
1198
- _.stores = W(e), j = e;
1628
+ v.stores = Ke(e), se = e;
1199
1629
  },
1200
- onActivate: (e) => f.sync(() => {
1630
+ onActivate: (e) => p.sync(() => {
1201
1631
  e.logger.info("billing active", {
1202
- provider: m.name,
1203
- plans: p.planIds().length,
1204
- enforce: T ? Object.keys(T).length : 0,
1205
- metering: D ? Object.keys(D).length : 0,
1206
- webhook: Q
1632
+ provider: h.name,
1633
+ plans: m.planIds().length,
1634
+ enforce: O ? Object.keys(O).length : 0,
1635
+ metering: k ? Object.keys(k).length : 0,
1636
+ webhook: $
1207
1637
  });
1208
- let n = t.flushIntervalMs ?? 6e4, r = D, i = r !== void 0 && Object.keys(r).length > 0;
1638
+ let n = t.flushIntervalMs ?? 6e4, r = k, i = r !== void 0 && Object.keys(r).length > 0;
1209
1639
  if (n > 0) {
1210
1640
  let e = process.env.HOSTNAME ?? `pid-${process.pid}`;
1211
- M = setInterval(() => {
1212
- let t = j;
1641
+ N = setInterval(() => {
1642
+ let t = se;
1213
1643
  if (t === void 0) return;
1214
1644
  let a = Date.now();
1215
1645
  (async () => {
1216
- await We(t, Ue(a, n), e, a) && i && await f.runPromise(Re(g, t, r).pipe(f.catchAll(() => f.void)));
1646
+ await _t(t, gt(a, n), e, a) && i && await p.runPromise(ut(_, t, r).pipe(p.catchAll(() => p.void)));
1217
1647
  })();
1218
- }, n), M.unref?.();
1648
+ }, n), N.unref?.();
1219
1649
  }
1220
1650
  }),
1221
- onDeactivate: () => f.sync(() => {
1222
- M !== void 0 && (clearInterval(M), M = void 0);
1651
+ onDeactivate: () => p.sync(() => {
1652
+ N !== void 0 && (clearInterval(N), N = void 0);
1223
1653
  })
1224
1654
  });
1225
1655
  };
1226
1656
  //#endregion
1227
- export { P as BILLING_CUSTOMERS_TABLE, J as BILLING_FLUSH_CLAIMS_TABLE, I as BILLING_INVOICES_TABLE, F as BILLING_SUBSCRIPTIONS_TABLE, L as BILLING_USAGE_TABLE, ut as BILLING_WEBHOOK_ID, Q as BILLING_WEBHOOK_PATH, e as BillingError, t as BillingService, n as DEFAULT_PLAN, r as EntitlementExceeded, ae as billingCustomersTable, He as billingFlushClaimsTable, se as billingInvoicesTable, mt as billingPlugin, Te as billingServiceLayer, oe as billingSubscriptionsTable, le as billingTables, ce as billingUsageTable, dt as billingWebhookDescriptor, we as buildBillingService, N as buildEnforceInterceptor, q as buildPlanRegistry, We as claimFlushWindow, W as dataStoreStores, Ve as emptyPlanRegistry, A as evaluateEntitlement, Ue as flushWindowKey, Se as memoryStores, st as mockProvider, ft as mountBillingWebhook, nt as normalizeStripeEvent, G as periodKey, M as requireEntitlement, ct as resolveProvider, it as stripeProvider };
1657
+ export { P as BILLING_CUSTOMERS_TABLE, R as BILLING_DUNNING_NOTICES_TABLE, Y as BILLING_FLUSH_CLAIMS_TABLE, I as BILLING_INVOICES_TABLE, F as BILLING_SUBSCRIPTIONS_TABLE, L as BILLING_USAGE_TABLE, Lt as BILLING_WEBHOOK_ID, $ as BILLING_WEBHOOK_PATH, e as BillingError, t as BillingService, ge as DEFAULT_DUNNING_STEPS, _e as DEFAULT_GRACE_HOURS, ve as DEFAULT_LOCKOUT, n as DEFAULT_PLAN, he as DUNNING_LOCK_STEP_ID, r as EntitlementExceeded, i as SubscriptionLocked, le as billingCustomersTable, me as billingDunningNoticesTable, ht as billingFlushClaimsTable, de as billingInvoicesTable, Ut as billingPlugin, Ye as billingServiceLayer, ue as billingSubscriptionsTable, pe as billingTables, fe as billingUsageTable, Rt as billingWebhookDescriptor, Je as buildBillingService, ce as buildEnforceInterceptor, pt as buildPlanRegistry, _t as claimFlushWindow, je as dataStoreDunningNoticeStore, Ke as dataStoreStores, Ee as defaultDunningEmail, we as dueSteps, De as dunningMailNotifier, mt as emptyPlanRegistry, Se as episodeKeyOf, z as evaluateBillingEntitlement, j as evaluateEntitlement, gt as flushWindowKey, Ce as graceEndOf, U as isStaleStatusEvent, ke as memoryDunningNoticeStore, Ge as memoryStores, Pt as mockProvider, zt as mountBillingWebhook, kt as normalizeStripeEvent, q as periodKey, N as requireEntitled, se as requireEntitlement, be as resolveDunningConfig, Ft as resolveProvider, jt as stripeProvider };