@voltro/plugin-billing 0.5.0 → 0.7.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,222 +1,165 @@
1
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, portalUrlDescriptor as s, reportUsageDescriptor as c, startCheckoutDescriptor as l, subscriptionDescriptor as u } from "./rpc.js";
3
- import { Effect as d, Layer as f, Schedule as p, Schema as m } from "effect";
4
- import { pluginEnv as h } from "@voltro/env";
5
- import { composeRpcInterceptors as ee, definePlugin as te } from "@voltro/protocol";
6
- import { and as g, eq as _, gte as ne, id as v, integer as y, lte as b, registerRetention as x, retentionTtlMsFromEnv as S, table as C, text as w, timestamp as T } from "@voltro/database";
7
- import { defineIncomingWebhook as E, mountIncomingWebhook as re } from "@voltro/plugin-webhooks";
8
- import { stripeProvider as D } from "@voltro/plugin-webhooks/providers";
9
- import { createLogger as O } from "@voltro/logger";
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";
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";
10
11
  //#region src/entitlement.ts
11
- var k = (e, t, n, r) => ({
12
+ var A = (e, t, n, r) => ({
12
13
  allowed: !Number.isFinite(t) || r <= 0 || n + r <= t,
13
14
  entitlement: e,
14
15
  limit: t,
15
16
  used: n,
16
17
  cost: r
17
- }), A = (e) => e.tenantId, j = (e, n, i) => d.gen(function* () {
18
- let a = A(e.request.subject);
19
- if (a === null) return i <= 0 ? void 0 : yield* d.fail(new 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({
20
21
  entitlement: n,
21
22
  limit: 0,
22
23
  used: 0,
23
24
  cost: i
24
25
  }));
25
26
  yield* (yield* t).consumeEntitlement(a, n, i);
26
- }), ie = (e, t) => (n, i) => {
27
+ }), N = (e, t) => (n, i) => {
27
28
  let a = e[i.tag];
28
29
  if (a === void 0) return n;
29
30
  let o = a.cost ?? 1, s = i.subject.tenantId;
30
- return s === null ? o <= 0 ? n : d.fail(new r({
31
+ return s === null ? o <= 0 ? n : f.fail(new r({
31
32
  entitlement: a.entitlement,
32
33
  limit: 0,
33
34
  used: 0,
34
35
  cost: o
35
- })) : d.flatMap(t.consumeEntitlement(s, a.entitlement, o), () => n);
36
- }, M = (e) => e < 0 ? -Math.round(-e) : Math.round(e), ae = (t) => {
37
- let n = t.periodEnd.getTime() - t.periodStart.getTime();
38
- if (n <= 0) throw new e({
39
- source: "proration",
40
- message: `degenerate billing period: periodEnd (${t.periodEnd.toISOString()}) must be after periodStart (${t.periodStart.toISOString()})`,
41
- transient: !1
42
- });
43
- let r = t.periodEnd.getTime() - t.changeAt.getTime(), i = Math.min(1, Math.max(0, r / n));
44
- return {
45
- deltaMinor: M((t.newAmountMinor - t.oldAmountMinor) * i),
46
- unusedFraction: i
47
- };
48
- }, oe = 1440 * 60 * 1e3, N = [
49
- 1,
50
- 3,
51
- 5,
52
- 7
53
- ], P = (e, t, n) => t < 0 || t >= n.length ? null : new Date(e.getTime() + n[t] * oe), se = (e) => {
54
- if (e.length === 0) throw Error("billing dunning: the retry schedule must have at least one offset");
55
- let t = 0;
56
- for (let n of e) {
57
- if (!Number.isFinite(n) || n <= 0) throw Error(`billing dunning: schedule offsets must be positive day counts, got ${n}`);
58
- if (n <= t) throw Error(`billing dunning: schedule offsets must strictly increase, got ${n} after ${t}`);
59
- t = n;
60
- }
61
- }, ce = (e, t, n, r) => d.gen(function* () {
62
- let i = P(r, 0, e.schedule);
63
- return (yield* e.dunning.open(t, n, i, r)) ? (yield* e.subscriptions.setStatus(n, "pastDue"), {
64
- tenantId: t,
65
- providerSubscriptionId: n,
66
- attempt: 0,
67
- nextRetryAt: i,
68
- startedAt: r
69
- }) : null;
70
- }), le = (e, t) => d.gen(function* () {
71
- yield* e.dunning.close(t), yield* e.subscriptions.setStatus(t, "active");
72
- }), ue = (e, t) => d.gen(function* () {
73
- let n = t.attempt + 1, r = P(t.startedAt, n, e.schedule);
74
- return r === null ? (yield* e.dunning.advance(t.providerSubscriptionId, t.attempt, n, t.nextRetryAt)) ? (yield* e.dunning.close(t.providerSubscriptionId), yield* e.subscriptions.setStatus(t.providerSubscriptionId, "canceled"), "canceled") : null : (yield* e.dunning.advance(t.providerSubscriptionId, t.attempt, n, r)) ? "retried" : null;
75
- }), de = (e, t) => d.gen(function* () {
76
- let n = yield* e.dunning.due(t), r = 0, i = 0;
77
- for (let t of n) {
78
- let n = yield* ue(e, t);
79
- n === "retried" ? r++ : n === "canceled" && i++;
80
- }
81
- return {
82
- retried: r,
83
- canceled: i
84
- };
85
- }), F = "_voltro_billing_customers", I = "_voltro_billing_subscriptions", L = "_voltro_billing_invoices", R = "_voltro_billing_usage", z = "_voltro_billing_dunning", fe = C(F, {
86
- id: v({ prefix: "bcust" }),
87
- tenantId: w(),
88
- provider: w(),
89
- providerCustomerId: w(),
90
- createdAt: T(),
91
- updatedAt: T()
92
- }).index("byBillingCustomerTenant", ["tenantId"]).index("byBillingProviderCustomer", ["providerCustomerId"]), pe = C(I, {
93
- id: v({ prefix: "bsub" }),
94
- tenantId: w(),
95
- provider: w(),
96
- providerSubscriptionId: w(),
97
- plan: w(),
98
- status: w(),
99
- quantity: y().default(1),
100
- currentPeriodStart: T().nullable(),
101
- currentPeriodEnd: T().nullable(),
102
- cancelAt: T().nullable(),
103
- createdAt: T(),
104
- updatedAt: T()
105
- }).index("byBillingSubscriptionTenant", ["tenantId"]).index("byBillingProviderSubscription", ["providerSubscriptionId"]), me = C(L, {
106
- id: v({ prefix: "binv" }),
107
- tenantId: w(),
108
- provider: w(),
109
- providerInvoiceId: w(),
110
- amountMinor: y(),
111
- currency: w(),
112
- status: w(),
113
- createdAt: T()
114
- }).index("byBillingInvoiceTenant", ["tenantId"]).index("byBillingProviderInvoice", ["providerInvoiceId"]), he = C(R, {
115
- id: v({ prefix: "buse" }),
116
- tenantId: w().maxLength(191),
117
- entitlementKey: w().maxLength(191),
118
- period: w().maxLength(16),
119
- used: y().default(0),
120
- reportedToProvider: y().default(0),
121
- updatedAt: T()
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()
122
74
  }).unique("byBillingUsageWindow", [
123
75
  "tenantId",
124
76
  "entitlementKey",
125
77
  "period"
126
- ]), ge = C(z, {
127
- id: v({ prefix: "bdun" }),
128
- tenantId: w().maxLength(191),
129
- providerSubscriptionId: w().maxLength(191),
130
- attempt: y().default(0),
131
- nextRetryAt: T(),
132
- startedAt: T(),
133
- updatedAt: T()
134
- }).unique("byBillingDunningSubscription", ["providerSubscriptionId"]).index("byBillingDunningTenant", ["tenantId"]), _e = () => [
135
- fe,
136
- pe,
137
- me,
138
- he,
139
- ge
140
- ], B = (e) => `${e}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`, V = (t, n) => d.tryPromise({
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({
141
84
  try: t,
142
85
  catch: (t) => new e({
143
86
  source: n,
144
87
  message: String(t?.message ?? t),
145
88
  transient: !0
146
89
  })
147
- }), H = (e) => ({
90
+ }), B = (e) => ({
148
91
  table: e.table,
149
92
  predicate: e.predicate,
150
93
  order: e.order ?? [],
151
94
  take: e.take,
152
95
  skip: void 0,
153
96
  projection: void 0
154
- }), ve = () => {
97
+ }), ue = () => {
155
98
  let e = /* @__PURE__ */ new Map();
156
99
  return {
157
- upsert: (t) => d.sync(() => {
100
+ upsert: (t) => f.sync(() => {
158
101
  e.set(t.tenantId, t);
159
102
  }),
160
- getByTenant: (t) => d.sync(() => e.get(t) ?? null)
103
+ getByTenant: (t) => f.sync(() => e.get(t) ?? null)
161
104
  };
162
- }, ye = (e) => ({
105
+ }, de = (e) => ({
163
106
  tenantId: String(e.tenantId),
164
107
  provider: String(e.provider),
165
108
  providerCustomerId: String(e.providerCustomerId)
166
- }), be = (e) => ({
167
- upsert: (t) => V(async () => {
168
- let n = await e.query(H({
169
- table: F,
170
- predicate: _("tenantId", t.tenantId),
109
+ }), fe = (e) => ({
110
+ upsert: (t) => z(async () => {
111
+ let n = await e.query(B({
112
+ table: P,
113
+ predicate: v("tenantId", t.tenantId),
171
114
  take: 1
172
115
  })), r = /* @__PURE__ */ new Date();
173
- n[0] ? await e.update(F, String(n[0].id), {
116
+ n[0] ? await e.update(P, String(n[0].id), {
174
117
  provider: t.provider,
175
118
  providerCustomerId: t.providerCustomerId,
176
119
  updatedAt: r
177
- }) : await e.insert(F, {
178
- id: B("bcust"),
120
+ }) : await e.insert(P, {
121
+ id: R("bcust"),
179
122
  ...t,
180
123
  createdAt: r,
181
124
  updatedAt: r
182
125
  });
183
126
  }, "customerStore"),
184
- getByTenant: (t) => V(async () => {
185
- let n = await e.query(H({
186
- table: F,
187
- predicate: _("tenantId", t),
127
+ getByTenant: (t) => z(async () => {
128
+ let n = await e.query(B({
129
+ table: P,
130
+ predicate: v("tenantId", t),
188
131
  take: 1
189
132
  }));
190
- return n[0] ? ye(n[0]) : null;
133
+ return n[0] ? de(n[0]) : null;
191
134
  }, "customerStore")
192
- }), xe = () => {
135
+ }), pe = () => {
193
136
  let e = /* @__PURE__ */ new Map();
194
137
  return {
195
- upsert: (t) => d.sync(() => {
138
+ upsert: (t) => f.sync(() => {
196
139
  e.set(t.tenantId, t);
197
140
  }),
198
- getByTenant: (t) => d.sync(() => e.get(t) ?? null),
199
- markCanceled: (t) => d.sync(() => {
141
+ getByTenant: (t) => f.sync(() => e.get(t) ?? null),
142
+ markCanceled: (t) => f.sync(() => {
200
143
  for (let [n, r] of e) r.providerSubscriptionId === t && e.set(n, {
201
144
  ...r,
202
145
  status: "canceled"
203
146
  });
204
147
  }),
205
- patchByTenant: (t, n) => d.sync(() => {
148
+ patchByTenant: (t, n) => f.sync(() => {
206
149
  let r = e.get(t);
207
150
  r && e.set(t, {
208
151
  ...r,
209
152
  ...n
210
153
  });
211
154
  }),
212
- setStatus: (t, n) => d.sync(() => {
155
+ setStatus: (t, n) => f.sync(() => {
213
156
  for (let [r, i] of e) i.providerSubscriptionId === t && e.set(r, {
214
157
  ...i,
215
158
  status: n
216
159
  });
217
160
  })
218
161
  };
219
- }, Se = (e) => ({
162
+ }, me = (e) => ({
220
163
  tenantId: String(e.tenantId),
221
164
  provider: String(e.provider),
222
165
  providerSubscriptionId: String(e.providerSubscriptionId),
@@ -226,14 +169,14 @@ var k = (e, t, n, r) => ({
226
169
  currentPeriodStart: e.currentPeriodStart == null ? null : new Date(e.currentPeriodStart),
227
170
  currentPeriodEnd: e.currentPeriodEnd == null ? null : new Date(e.currentPeriodEnd),
228
171
  cancelAt: e.cancelAt == null ? null : new Date(e.cancelAt)
229
- }), U = async (e, t) => (await e.query(H({
172
+ }), V = async (e, t) => (await e.query(B({
230
173
  table: "_voltro_billing_subscriptions",
231
- predicate: _("providerSubscriptionId", t),
174
+ predicate: v("providerSubscriptionId", t),
232
175
  take: 1
233
- })))[0] ?? null, Ce = (e) => ({
234
- upsert: (t) => V(async () => {
235
- let n = await U(e, t.providerSubscriptionId), r = /* @__PURE__ */ new Date();
236
- n ? await e.update(I, String(n.id), {
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), {
237
180
  tenantId: t.tenantId,
238
181
  provider: t.provider,
239
182
  plan: t.plan,
@@ -243,8 +186,8 @@ var k = (e, t, n, r) => ({
243
186
  currentPeriodEnd: t.currentPeriodEnd,
244
187
  cancelAt: t.cancelAt,
245
188
  updatedAt: r
246
- }) : await e.insert(I, {
247
- id: B("bsub"),
189
+ }) : await e.insert(F, {
190
+ id: R("bsub"),
248
191
  tenantId: t.tenantId,
249
192
  provider: t.provider,
250
193
  providerSubscriptionId: t.providerSubscriptionId,
@@ -258,95 +201,95 @@ var k = (e, t, n, r) => ({
258
201
  updatedAt: r
259
202
  });
260
203
  }, "subscriptionStore"),
261
- getByTenant: (t) => V(async () => {
262
- let n = await e.query(H({
263
- table: I,
264
- predicate: _("tenantId", t),
204
+ getByTenant: (t) => z(async () => {
205
+ let n = await e.query(B({
206
+ table: F,
207
+ predicate: v("tenantId", t),
265
208
  order: [{
266
209
  column: "updatedAt",
267
210
  direction: "desc"
268
211
  }],
269
212
  take: 1
270
213
  }));
271
- return n[0] ? Se(n[0]) : null;
214
+ return n[0] ? me(n[0]) : null;
272
215
  }, "subscriptionStore"),
273
- markCanceled: (t) => V(async () => {
274
- let n = await U(e, t);
275
- n && await e.update(I, String(n.id), {
216
+ markCanceled: (t) => z(async () => {
217
+ let n = await V(e, t);
218
+ n && await e.update(F, String(n.id), {
276
219
  status: "canceled",
277
220
  updatedAt: /* @__PURE__ */ new Date()
278
221
  });
279
222
  }, "subscriptionStore"),
280
- patchByTenant: (t, n) => V(async () => {
281
- let r = (await e.query(H({
282
- table: I,
283
- predicate: _("tenantId", t),
223
+ patchByTenant: (t, n) => z(async () => {
224
+ let r = (await e.query(B({
225
+ table: F,
226
+ predicate: v("tenantId", t),
284
227
  order: [{
285
228
  column: "updatedAt",
286
229
  direction: "desc"
287
230
  }],
288
231
  take: 1
289
232
  })))[0];
290
- r && await e.update(I, String(r.id), {
233
+ r && await e.update(F, String(r.id), {
291
234
  ...n.plan === void 0 ? {} : { plan: n.plan },
292
235
  ...n.quantity === void 0 ? {} : { quantity: n.quantity },
293
236
  ...n.status === void 0 ? {} : { status: n.status },
294
237
  updatedAt: /* @__PURE__ */ new Date()
295
238
  });
296
239
  }, "subscriptionStore"),
297
- setStatus: (t, n) => V(async () => {
298
- let r = await U(e, t);
299
- r && await e.update(I, String(r.id), {
240
+ setStatus: (t, n) => z(async () => {
241
+ let r = await V(e, t);
242
+ r && await e.update(F, String(r.id), {
300
243
  status: n,
301
244
  updatedAt: /* @__PURE__ */ new Date()
302
245
  });
303
246
  }, "subscriptionStore")
304
- }), we = () => {
247
+ }), ge = () => {
305
248
  let e = /* @__PURE__ */ new Map();
306
249
  return {
307
- upsert: (t) => d.sync(() => {
250
+ upsert: (t) => f.sync(() => {
308
251
  e.set(t.providerInvoiceId, t);
309
252
  }),
310
- listByTenant: (t) => d.sync(() => [...e.values()].filter((e) => e.tenantId === t))
253
+ listByTenant: (t) => f.sync(() => [...e.values()].filter((e) => e.tenantId === t))
311
254
  };
312
- }, Te = (e) => ({
255
+ }, _e = (e) => ({
313
256
  tenantId: String(e.tenantId),
314
257
  provider: String(e.provider),
315
258
  providerInvoiceId: String(e.providerInvoiceId),
316
259
  amountMinor: Number(e.amountMinor),
317
260
  currency: String(e.currency),
318
261
  status: String(e.status)
319
- }), Ee = (e) => ({
320
- upsert: (t) => V(async () => {
321
- let n = await e.query(H({
322
- table: L,
323
- predicate: _("providerInvoiceId", t.providerInvoiceId),
262
+ }), ve = (e) => ({
263
+ upsert: (t) => z(async () => {
264
+ let n = await e.query(B({
265
+ table: I,
266
+ predicate: v("providerInvoiceId", t.providerInvoiceId),
324
267
  take: 1
325
268
  }));
326
- n[0] ? await e.update(L, String(n[0].id), {
269
+ n[0] ? await e.update(I, String(n[0].id), {
327
270
  amountMinor: t.amountMinor,
328
271
  currency: t.currency,
329
272
  status: t.status
330
- }) : await e.insert(L, {
331
- id: B("binv"),
273
+ }) : await e.insert(I, {
274
+ id: R("binv"),
332
275
  ...t,
333
276
  createdAt: /* @__PURE__ */ new Date()
334
277
  });
335
278
  }, "invoiceStore"),
336
- listByTenant: (t) => V(async () => (await e.query(H({
337
- table: L,
338
- predicate: _("tenantId", t),
279
+ listByTenant: (t) => z(async () => (await e.query(B({
280
+ table: I,
281
+ predicate: v("tenantId", t),
339
282
  order: [{
340
283
  column: "createdAt",
341
284
  direction: "desc"
342
285
  }]
343
- }))).map(Te), "invoiceStore")
344
- }), W = (e, t, n) => `${e}${t}${n}`, De = () => {
286
+ }))).map(_e), "invoiceStore")
287
+ }), H = (e, t, n) => `${e}${t}${n}`, ye = () => {
345
288
  let e = /* @__PURE__ */ new Map();
346
289
  return {
347
- used: (t, n, r) => d.sync(() => e.get(W(t, n, r))?.used ?? 0),
348
- consume: (t, n, r, i, a) => d.sync(() => {
349
- let o = W(t, n, r), s = e.get(o), c = s?.used ?? 0, l = c + i <= a;
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;
350
293
  return l && e.set(o, {
351
294
  tenantId: t,
352
295
  entitlementKey: n,
@@ -361,8 +304,8 @@ var k = (e, t, n, r) => ({
361
304
  cost: i
362
305
  };
363
306
  }),
364
- increment: (t, n, r, i) => d.sync(() => {
365
- let a = W(t, n, r), o = e.get(a), s = (o?.used ?? 0) + i;
307
+ increment: (t, n, r, i) => f.sync(() => {
308
+ let a = H(t, n, r), o = e.get(a), s = (o?.used ?? 0) + i;
366
309
  return e.set(a, {
367
310
  tenantId: t,
368
311
  entitlementKey: n,
@@ -371,34 +314,34 @@ var k = (e, t, n, r) => ({
371
314
  reportedToProvider: o?.reportedToProvider ?? 0
372
315
  }), s;
373
316
  }),
374
- pending: () => d.sync(() => [...e.values()].filter((e) => e.used > e.reportedToProvider)),
375
- markReported: (t, n, r, i) => d.sync(() => {
376
- let a = W(t, n, r), o = e.get(a);
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);
377
320
  o && e.set(a, {
378
321
  ...o,
379
322
  reportedToProvider: i
380
323
  });
381
324
  })
382
325
  };
383
- }, Oe = (e) => ({
326
+ }, be = (e) => ({
384
327
  tenantId: String(e.tenantId),
385
328
  entitlementKey: String(e.entitlementKey),
386
329
  period: String(e.period),
387
330
  used: Number(e.used ?? 0),
388
331
  reportedToProvider: Number(e.reportedToProvider ?? 0)
389
- }), G = async (e, t, n, r) => (await e.query(H({
332
+ }), U = async (e, t, n, r) => (await e.query(B({
390
333
  table: "_voltro_billing_usage",
391
- predicate: g(_("tenantId", t), _("entitlementKey", n), _("period", r)),
334
+ predicate: _(v("tenantId", t), v("entitlementKey", n), v("period", r)),
392
335
  take: 1
393
- })))[0] ?? null, ke = (e) => ({
394
- used: (t, n, r) => V(async () => {
395
- let i = await G(e, t, n, r);
336
+ })))[0] ?? null, xe = (e) => ({
337
+ used: (t, n, r) => z(async () => {
338
+ let i = await U(e, t, n, r);
396
339
  return i ? Number(i.used ?? 0) : 0;
397
340
  }, "usageStore"),
398
- consume: (t, n, r, i, a) => V(async () => {
341
+ consume: (t, n, r, i, a) => z(async () => {
399
342
  for (let o = 0; o < 32; o++) {
400
343
  o > 0 && await new Promise((e) => setTimeout(e, o));
401
- let s = await G(e, t, n, r), c = s ? Number(s.used ?? 0) : 0;
344
+ let s = await U(e, t, n, r), c = s ? Number(s.used ?? 0) : 0;
402
345
  if (c + i > a) return {
403
346
  allowed: !1,
404
347
  entitlement: n,
@@ -410,7 +353,7 @@ var k = (e, t, n, r) => ({
410
353
  if (await e.updateMany("_voltro_billing_usage", {
411
354
  used: c + i,
412
355
  updatedAt: /* @__PURE__ */ new Date()
413
- }, { where: g(_("id", String(s.id)), _("used", c)) }) === 1) return {
356
+ }, { where: _(v("id", String(s.id)), v("used", c)) }) === 1) return {
414
357
  allowed: !0,
415
358
  entitlement: n,
416
359
  limit: a,
@@ -418,7 +361,7 @@ var k = (e, t, n, r) => ({
418
361
  cost: i
419
362
  };
420
363
  } else {
421
- let o = B("buse"), s = await e.insertIgnore(R, {
364
+ let o = R("buse"), s = await e.insertIgnore(L, {
422
365
  id: o,
423
366
  tenantId: t,
424
367
  entitlementKey: n,
@@ -442,18 +385,18 @@ var k = (e, t, n, r) => ({
442
385
  }
443
386
  throw Error(`billing usage consume: contention on (${t}, ${n}, ${r}) exceeded 32 attempts`);
444
387
  }, "usageStore"),
445
- increment: (t, n, r, i) => V(async () => {
388
+ increment: (t, n, r, i) => z(async () => {
446
389
  for (let a = 0; a < 32; a++) {
447
390
  a > 0 && await new Promise((e) => setTimeout(e, a));
448
- let o = await G(e, t, n, r);
391
+ let o = await U(e, t, n, r);
449
392
  if (o) {
450
393
  let t = Number(o.used ?? 0);
451
394
  if (await e.updateMany("_voltro_billing_usage", {
452
395
  used: t + i,
453
396
  updatedAt: /* @__PURE__ */ new Date()
454
- }, { where: g(_("id", String(o.id)), _("used", t)) }) === 1) return t + i;
397
+ }, { where: _(v("id", String(o.id)), v("used", t)) }) === 1) return t + i;
455
398
  } else {
456
- let a = B("buse"), o = await e.insertIgnore(R, {
399
+ let a = R("buse"), o = await e.insertIgnore(L, {
457
400
  id: a,
458
401
  tenantId: t,
459
402
  entitlementKey: n,
@@ -471,167 +414,90 @@ var k = (e, t, n, r) => ({
471
414
  }
472
415
  throw Error(`billing usage increment: contention on (${t}, ${n}, ${r}) exceeded 32 attempts`);
473
416
  }, "usageStore"),
474
- pending: () => V(async () => (await e.query(H({ table: R }))).map(Oe).filter((e) => e.used > e.reportedToProvider), "usageStore"),
475
- markReported: (t, n, r, i) => V(async () => {
476
- let a = await G(e, t, n, r);
477
- a && await e.update(R, String(a.id), {
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);
420
+ a && await e.update(L, String(a.id), {
478
421
  reportedToProvider: i,
479
422
  updatedAt: /* @__PURE__ */ new Date()
480
423
  });
481
424
  }, "usageStore")
482
- }), Ae = (e) => ({
483
- tenantId: String(e.tenantId),
484
- providerSubscriptionId: String(e.providerSubscriptionId),
485
- attempt: Number(e.attempt ?? 0),
486
- nextRetryAt: new Date(e.nextRetryAt),
487
- startedAt: new Date(e.startedAt)
488
- }), je = () => {
489
- let e = /* @__PURE__ */ new Map();
490
- return {
491
- open: (t, n, r, i) => d.sync(() => e.has(n) ? !1 : (e.set(n, {
492
- tenantId: t,
493
- providerSubscriptionId: n,
494
- attempt: 0,
495
- nextRetryAt: r,
496
- startedAt: i
497
- }), !0)),
498
- getByTenant: (t) => d.sync(() => {
499
- for (let n of e.values()) if (n.tenantId === t) return n;
500
- return null;
501
- }),
502
- due: (t) => d.sync(() => [...e.values()].filter((e) => e.nextRetryAt.getTime() <= t.getTime())),
503
- advance: (t, n, r, i) => d.sync(() => {
504
- let a = e.get(t);
505
- return !a || a.attempt !== n ? !1 : (e.set(t, {
506
- ...a,
507
- attempt: r,
508
- nextRetryAt: i
509
- }), !0);
510
- }),
511
- close: (t) => d.sync(() => {
512
- e.delete(t);
513
- })
514
- };
515
- }, Me = async (e, t) => (await e.query(H({
516
- table: "_voltro_billing_dunning",
517
- predicate: _("providerSubscriptionId", t),
518
- take: 1
519
- })))[0] ?? null, Ne = (e) => ({
520
- open: (t, n, r, i) => V(async () => {
521
- let a = B("bdun"), o = await e.insertIgnore(z, {
522
- id: a,
523
- tenantId: t,
524
- providerSubscriptionId: n,
525
- attempt: 0,
526
- nextRetryAt: r,
527
- startedAt: i,
528
- updatedAt: i
529
- }, { conflictColumns: ["providerSubscriptionId"] });
530
- return String(o.id) === a;
531
- }, "dunningStore"),
532
- getByTenant: (t) => V(async () => {
533
- let n = await e.query(H({
534
- table: z,
535
- predicate: _("tenantId", t),
536
- take: 1
537
- }));
538
- return n[0] ? Ae(n[0]) : null;
539
- }, "dunningStore"),
540
- due: (t) => V(async () => (await e.query(H({
541
- table: z,
542
- predicate: b("nextRetryAt", t)
543
- }))).map(Ae), "dunningStore"),
544
- advance: (t, n, r, i) => V(async () => {
545
- let a = await Me(e, t);
546
- return a ? await e.updateMany(z, {
547
- attempt: r,
548
- nextRetryAt: i,
549
- updatedAt: /* @__PURE__ */ new Date()
550
- }, { where: g(_("id", String(a.id)), _("attempt", n)) }) === 1 : !1;
551
- }, "dunningStore"),
552
- close: (t) => V(async () => {
553
- let n = await Me(e, t);
554
- n && await e.delete(z, String(n.id));
555
- }, "dunningStore")
556
- }), Pe = () => ({
557
- customers: ve(),
558
- subscriptions: xe(),
559
- invoices: we(),
560
- usage: De(),
561
- dunning: je()
562
- }), Fe = (e) => ({
563
- customers: be(e),
564
- subscriptions: Ce(e),
565
- invoices: Ee(e),
566
- usage: ke(e),
567
- dunning: Ne(e)
568
- }), K = (e = /* @__PURE__ */ new Date()) => `${e.getUTCFullYear()}-${String(e.getUTCMonth() + 1).padStart(2, "0")}`, Ie = (e) => ({ stores: e ?? Pe() }), q = (e, t) => e.pipe(d.retry({
569
- schedule: p.recurs(Math.max(0, t - 1)),
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)),
570
437
  while: (e) => e.transient
571
- })), Le = (t) => {
572
- let { provider: i, plans: a } = t, o = t.attempts ?? 3, s = t.now ?? (() => /* @__PURE__ */ new Date()), c = Ie(t.stores), l = t.dunningSchedule ?? N, u = () => ({
573
- subscriptions: c.stores.subscriptions,
574
- dunning: c.stores.dunning,
575
- schedule: l
576
- }), f = (e) => c.stores.subscriptions.getByTenant(e).pipe(d.map((e) => e !== null && (e.status === "active" || e.status === "trialing") ? e.plan : n)), p = (e) => c.stores.subscriptions.getByTenant(e), m = (e, n) => d.gen(function* () {
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* () {
577
440
  if (t.resolveEntitlementLimit !== void 0) {
578
441
  let r = yield* t.resolveEntitlementLimit(e, n);
579
442
  if (r !== null) return r;
580
443
  }
581
- let r = yield* f(e);
444
+ let r = yield* u(e);
582
445
  return a.entitlementLimit(r, n);
583
- }), h = (e, t, n) => d.gen(function* () {
584
- return k(t, yield* m(e, t), yield* c.stores.usage.used(e, t, K(s())), n);
585
- }), ee = (e, t, n) => d.gen(function* () {
586
- let i = yield* m(e, t);
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);
587
450
  if (!Number.isFinite(i) || n <= 0) return;
588
- let a = K(s()), o = yield* c.stores.usage.consume(e, t, a, n, i);
589
- if (!o.allowed) return yield* d.fail(new r({
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({
590
453
  entitlement: t,
591
454
  limit: o.limit,
592
455
  used: o.used,
593
456
  cost: n
594
457
  }));
595
- }), te = (e, t, n) => n <= 0 ? d.void : c.stores.usage.increment(e, t, K(s()), n).pipe(d.asVoid), g = () => d.gen(function* () {
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* () {
596
459
  if (!i.supportsMeteredUsage) return;
597
- let e = yield* c.stores.usage.pending();
460
+ let e = yield* l.stores.usage.pending();
598
461
  for (let t of e) {
599
462
  let e = t.used - t.reportedToProvider;
600
- e <= 0 || (yield* q(i.reportUsage({
463
+ e <= 0 || (yield* K(i.reportUsage({
601
464
  tenantId: t.tenantId,
602
465
  entitlementKey: t.entitlementKey,
603
466
  quantity: e
604
- }), o), yield* c.stores.usage.markReported(t.tenantId, t.entitlementKey, t.period, t.used));
467
+ }), o), yield* l.stores.usage.markReported(t.tenantId, t.entitlementKey, t.period, t.used));
605
468
  }
606
- }), _ = (t) => d.gen(function* () {
469
+ }), te = (t) => f.gen(function* () {
607
470
  let n = a.priceIdFor(t.plan);
608
- if (n === null) return yield* d.fail(new e({
471
+ if (n === null) return yield* f.fail(new e({
609
472
  source: "billing",
610
473
  message: `plan '${t.plan}' has no provider priceId — not a paid plan`,
611
474
  transient: !1
612
475
  }));
613
- let r = yield* c.stores.customers.getByTenant(t.tenantId);
614
- return yield* q(i.createCheckoutSession({
476
+ let r = yield* l.stores.customers.getByTenant(t.tenantId);
477
+ return yield* K(i.createCheckoutSession({
615
478
  tenantId: t.tenantId,
616
479
  priceId: n,
617
480
  successUrl: t.successUrl,
618
481
  cancelUrl: t.cancelUrl,
619
- ...r === null ? {} : { providerCustomerId: r.providerCustomerId }
482
+ ...r === null ? {} : { providerCustomerId: r.providerCustomerId },
483
+ ...t.quantity === void 0 ? {} : { quantity: t.quantity },
484
+ ...s?.adjustableQuantity === void 0 ? {} : { adjustableQuantity: s.adjustableQuantity },
485
+ ...s?.trialDays === void 0 ? {} : { trialDays: s.trialDays }
620
486
  }), o);
621
- }), ne = (t, n) => d.gen(function* () {
622
- let r = yield* c.stores.customers.getByTenant(t);
623
- return r === null ? yield* d.fail(new e({
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({
624
490
  source: "billing",
625
491
  message: `no provider customer linked for tenant '${t}'`,
626
492
  transient: !1
627
- })) : yield* q(i.createPortalSession({
493
+ })) : yield* K(i.createPortalSession({
628
494
  tenantId: t,
629
495
  providerCustomerId: r.providerCustomerId,
630
496
  returnUrl: n
631
497
  }), o);
632
- }), v = (e) => {
498
+ }), _ = (e) => {
633
499
  switch (e._tag) {
634
- case "subscriptionUpserted": return c.stores.subscriptions.upsert({
500
+ case "subscriptionUpserted": return l.stores.subscriptions.upsert({
635
501
  tenantId: e.tenantId,
636
502
  provider: i.name,
637
503
  providerSubscriptionId: e.providerSubscriptionId,
@@ -642,91 +508,108 @@ var k = (e, t, n, r) => ({
642
508
  currentPeriodEnd: e.currentPeriodEnd,
643
509
  cancelAt: e.cancelAt
644
510
  });
645
- case "subscriptionCanceled": return c.stores.subscriptions.markCanceled(e.providerSubscriptionId);
646
- case "invoicePaid": return c.stores.invoices.upsert({
511
+ case "subscriptionCanceled": return l.stores.subscriptions.markCanceled(e.providerSubscriptionId);
512
+ case "invoicePaid": return l.stores.invoices.upsert({
647
513
  tenantId: e.tenantId,
648
514
  provider: i.name,
649
515
  providerInvoiceId: e.providerInvoiceId,
650
516
  amountMinor: e.amountMinor,
651
517
  currency: e.currency,
652
518
  status: "paid"
653
- }).pipe(d.andThen(w(e.tenantId)));
654
- case "invoicePaymentFailed": return c.stores.invoices.upsert({
519
+ });
520
+ case "invoicePaymentFailed": return l.stores.invoices.upsert({
655
521
  tenantId: e.tenantId,
656
522
  provider: i.name,
657
523
  providerInvoiceId: e.providerInvoiceId,
658
524
  amountMinor: e.amountMinor,
659
525
  currency: e.currency,
660
526
  status: "open"
661
- }).pipe(d.andThen(C(e.tenantId).pipe(d.asVoid)));
662
- case "customerLinked": return c.stores.customers.upsert({
527
+ });
528
+ case "customerLinked": return l.stores.customers.upsert({
663
529
  tenantId: e.tenantId,
664
530
  provider: i.name,
665
531
  providerCustomerId: e.providerCustomerId
666
532
  });
667
533
  }
668
- }, y = (e, t) => a.unitAmountFor(e) * t, b = (t, n, r) => d.gen(function* () {
669
- let i = yield* c.stores.subscriptions.getByTenant(t);
670
- if (i === null) return yield* d.fail(new e({
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({
671
537
  source: "billing",
672
538
  message: `no subscription for tenant '${t}' to change`,
673
539
  transient: !1
674
540
  }));
675
- let o = n.plan ?? i.plan, s = n.quantity ?? i.quantity;
676
- if (n.plan !== void 0 && !a.has(o)) return yield* d.fail(new e({
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({
677
543
  source: "billing",
678
- message: `unknown plan '${o}' — not in the plan registry`,
544
+ message: `unknown plan '${s}' — not in the plan registry`,
679
545
  transient: !1
680
546
  }));
681
- if (!Number.isInteger(s) || s <= 0) return yield* d.fail(new e({
547
+ if (!Number.isInteger(c) || c <= 0) return yield* f.fail(new e({
682
548
  source: "billing",
683
- message: `seat quantity must be a positive integer, got ${s}`,
549
+ message: `seat quantity must be a positive integer, got ${c}`,
684
550
  transient: !1
685
551
  }));
686
- let l = y(i.plan, i.quantity), u = y(o, s), f = a.currencyFor(o), p = 0;
687
- return i.currentPeriodEnd !== null && i.currentPeriodStart !== null && u !== l && (p = ae({
688
- oldAmountMinor: l,
689
- newAmountMinor: u,
690
- periodStart: i.currentPeriodStart,
691
- periodEnd: i.currentPeriodEnd,
692
- changeAt: r
693
- }).deltaMinor), yield* c.stores.subscriptions.patchByTenant(t, {
694
- ...n.plan === void 0 ? {} : { plan: o },
695
- ...n.quantity === void 0 ? {} : { quantity: s }
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 },
556
+ proration: "prorate"
557
+ }), o);
558
+ return yield* l.stores.subscriptions.patchByTenant(t, {
559
+ ...n.plan === void 0 ? {} : { plan: s },
560
+ quantity: u.quantity,
561
+ ...u.currentPeriodStart === null ? {} : { currentPeriodStart: u.currentPeriodStart },
562
+ ...u.currentPeriodEnd === null ? {} : { currentPeriodEnd: u.currentPeriodEnd }
696
563
  }), {
697
564
  tenantId: t,
698
- plan: o,
699
- quantity: s,
700
- prorationMinor: p,
701
- currency: f
565
+ plan: s,
566
+ quantity: u.quantity,
567
+ prorationMinor: u.prorationMinor,
568
+ currency: u.currency
702
569
  };
703
- }), x = (e, t, n = s()) => b(e, { plan: t }, n), S = (e, t, n = s()) => b(e, { quantity: t }, n), C = (e, t = s()) => d.gen(function* () {
704
- let n = yield* c.stores.subscriptions.getByTenant(e);
705
- return n === null ? null : yield* ce(u(), e, n.providerSubscriptionId, t);
706
- }), w = (e, t = s()) => d.gen(function* () {
707
- let t = yield* c.stores.dunning.getByTenant(e);
708
- t !== null && (yield* le(u(), t.providerSubscriptionId));
709
570
  });
710
571
  return {
711
572
  service: {
712
- subscription: p,
713
- plan: f,
714
- checkEntitlement: h,
715
- consumeEntitlement: ee,
716
- reportUsage: te,
573
+ subscription: ee,
574
+ plan: u,
575
+ checkEntitlement: p,
576
+ consumeEntitlement: m,
577
+ reportUsage: h,
717
578
  flushUsage: g,
718
- startCheckout: _,
579
+ startCheckout: te,
719
580
  portalUrl: ne,
720
- applyEvent: v,
721
- changePlan: x,
722
- changeSeats: S,
723
- recordPaymentFailure: C,
724
- recordPaymentSuccess: w,
725
- runDunningCycle: (e = s()) => de(u(), e)
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({
587
+ source: "billing",
588
+ message: `no subscription for tenant '${t}' to preview`,
589
+ transient: !1
590
+ }));
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 },
595
+ proration: "prorate"
596
+ }), o);
597
+ return {
598
+ tenantId: t,
599
+ plan: s,
600
+ quantity: c.quantity,
601
+ prorationMinor: c.prorationMinor,
602
+ currency: c.currency
603
+ };
604
+ }),
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);
608
+ })
726
609
  },
727
- holder: c
610
+ holder: l
728
611
  };
729
- }, Re = (e) => f.succeed(t, e), ze = "_voltro_ai_usage", Be = (e) => {
612
+ }, Te = (e) => p.succeed(t, e), Ee = "_voltro_ai_usage", De = (e) => {
730
613
  for (let [t, n] of Object.entries(e)) {
731
614
  if (t.length === 0) throw Error("billing metering: a meter key must be non-empty");
732
615
  if (n.from === "rpc") {
@@ -735,29 +618,29 @@ var k = (e, t, n, r) => ({
735
618
  if (typeof n.table != "string" || n.table.length === 0) throw Error(`billing metering: cdc meter '${t}' needs a 'table'`);
736
619
  } else if (n.from !== "ai") throw Error(`billing metering: meter '${t}' has unknown source '${String(n.from)}'`);
737
620
  }
738
- }, Ve = (e, t) => typeof e == "string" ? t === e : e.test(t), He = (e, t, n) => Object.entries(e).filter(([, e]) => e.from === "rpc" && (e.kind === void 0 || e.kind === n) && Ve(e.match, t)).map(([e]) => e), Ue = (e, t, n) => Object.entries(e).filter(([, e]) => e.from === "cdc" && e.table === t && (e.op ?? "insert") === n).map(([e]) => e), We = (e) => Object.entries(e).filter(([, e]) => e.from === "ai").map(([e, t]) => [e, t.metric ?? "tokens"]), Ge = (e) => e.tenantId, Ke = (e, t, n, r) => d.forEach(n, (n) => e.reportUsage(t, n, r).pipe(d.catchAll(() => d.void)), { discard: !0 }), qe = (e, t) => (n, r) => {
739
- let i = He(t, r.tag, r.kind);
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);
740
623
  if (i.length === 0) return n;
741
- let a = Ge(r.subject);
742
- return a === null ? n : n.pipe(d.tap(() => Ke(e, a, i, 1)));
743
- }, Je = (e, t) => (n) => {
744
- let r = Ue(t, n.table, n.op);
745
- if (r.length === 0) return d.void;
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;
746
629
  let i = n.new?.tenantId ?? n.old?.tenantId ?? null;
747
- return i === null ? d.void : Ke(e, i, r, 1);
748
- }, Ye = (e) => new Date(Date.UTC(e.getUTCFullYear(), e.getUTCMonth(), 1, 0, 0, 0, 0)), Xe = (e, t, n, r = () => /* @__PURE__ */ new Date()) => d.gen(function* () {
749
- let i = We(n);
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);
750
633
  if (i.length === 0) return;
751
634
  let a = r();
752
- K(a);
635
+ G(a);
753
636
  let o = {
754
- table: ze,
755
- predicate: ne("calledAt", Ye(a)),
637
+ table: Ee,
638
+ predicate: re("calledAt", Ie(a)),
756
639
  order: [],
757
640
  take: void 0,
758
641
  skip: void 0,
759
642
  projection: void 0
760
- }, s = yield* d.promise(() => Promise.resolve(t.query(o)).then((e) => e).catch(() => [])), c = /* @__PURE__ */ new Map();
643
+ }, s = yield* f.promise(() => Promise.resolve(t.query(o)).then((e) => e).catch(() => [])), c = /* @__PURE__ */ new Map();
761
644
  for (let e of s) {
762
645
  let t = e.tenantId ?? null;
763
646
  if (t === null) continue;
@@ -770,17 +653,17 @@ var k = (e, t, n, r) => ({
770
653
  for (let [t, n] of i) for (let [r, i] of c) {
771
654
  let a = n === "costMicroUsd" ? i.cost : i.tokens;
772
655
  if (a <= 0) continue;
773
- let o = a - (yield* e.checkEntitlement(r, t, 0).pipe(d.map((e) => e.used), d.catchAll(() => d.succeed(0))));
774
- o > 0 && (yield* e.reportUsage(r, t, o).pipe(d.catchAll(() => d.void)));
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)));
775
658
  }
776
- }), Ze = (e, t, n, r) => Xe(e, t, n, r).pipe(d.andThen(e.flushUsage().pipe(d.catchAll(() => d.void)))), Qe = (e) => Object.values(e).some((e) => e.from === "cdc"), $e = (e) => e === void 0 ? 0 : e === "unlimited" ? Infinity : e, J = (e) => {
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) => {
777
660
  let t = /* @__PURE__ */ new Map();
778
661
  for (let [n, r] of Object.entries(e)) r.priceId !== void 0 && t.set(r.priceId, n);
779
662
  return {
780
663
  has: (t) => Object.prototype.hasOwnProperty.call(e, t),
781
664
  entitlementLimit: (t, n) => {
782
665
  let r = e[t];
783
- return r === void 0 ? 0 : $e(r.entitlements[n]);
666
+ return r === void 0 ? 0 : Be(r.entitlements[n]);
784
667
  },
785
668
  planForPriceId: (e) => t.get(e) ?? null,
786
669
  priceIdFor: (t) => e[t]?.priceId ?? null,
@@ -791,14 +674,14 @@ var k = (e, t, n, r) => ({
791
674
  currencyFor: (t) => (e[t]?.currency ?? "usd").toLowerCase(),
792
675
  planIds: () => Object.keys(e)
793
676
  };
794
- }, et = () => J({ [n]: { entitlements: {} } }), Y = "_voltro_billing_flush_claims", tt = C(Y, {
795
- id: v({ scheme: "numeric" }),
796
- windowKey: w(),
797
- claimedBy: w(),
798
- claimedAt: T().default("now")
799
- }).unique("billingFlushClaimWindow", ["windowKey"]), nt = (e, t) => `billing-flush:${Math.floor(e / Math.max(1, t))}`, rt = async (e, t, n, r) => {
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) => {
800
683
  try {
801
- return (await e.insertIgnore(Y, {
684
+ return (await e.insertIgnore(J, {
802
685
  windowKey: t,
803
686
  claimedBy: n,
804
687
  claimedAt: new Date(r)
@@ -806,190 +689,306 @@ var k = (e, t, n, r) => ({
806
689
  } catch {
807
690
  return !0;
808
691
  }
809
- }, it = (e) => {
810
- let t = e?.baseUrl ?? "https://mock.billing.local", n = [], r = (e) => typeof e == "object" && !!e && typeof e._tag == "string";
692
+ }, Ge = 3, Ke = 2e4, qe = (e) => e.client ?? new T(e.apiKey, {
693
+ maxNetworkRetries: Ge,
694
+ timeout: Ke,
695
+ appInfo: {
696
+ name: "voltro",
697
+ url: "https://voltro.dev"
698
+ }
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({
700
+ try: n,
701
+ catch: (n) => new e({
702
+ source: t,
703
+ message: Xe(n),
704
+ transient: Je(n)
705
+ })
706
+ }), Xe = (e) => {
707
+ if (e instanceof T.errors.StripeError) {
708
+ let t = Ye(e);
709
+ return t === void 0 ? e.message : `${e.message} (${t})`;
710
+ }
711
+ return String(e?.message ?? e);
712
+ }, Ze = (e) => {
713
+ let t = e.items?.data?.[0];
811
714
  return {
812
- name: "mock",
813
- supportsMeteredUsage: e?.supportsMeteredUsage ?? !0,
814
- pushed: n,
815
- createCheckoutSession: (e) => d.succeed({ url: `${t}/checkout?tenant=${encodeURIComponent(e.tenantId)}&price=${encodeURIComponent(e.priceId)}` }),
816
- createPortalSession: (e) => d.succeed({ url: `${t}/portal?customer=${encodeURIComponent(e.providerCustomerId)}` }),
817
- reportUsage: (e) => d.sync(() => {
818
- n.push(e);
819
- }),
820
- normalizeEvent: (e) => d.succeed(r(e) ? e : null)
715
+ start: X(t?.current_period_start),
716
+ end: X(t?.current_period_end)
821
717
  };
822
- }, at = "https://api.stripe.com/v1", X = (t, n) => new e({
823
- source: "stripe",
824
- message: t,
825
- transient: n
826
- }), ot = (e) => {
827
- let t = new URLSearchParams();
828
- for (let [n, r] of Object.entries(e)) r !== void 0 && t.set(n, r);
829
- return t.toString();
830
- }, st = (e, t, n) => d.tryPromise({
831
- try: async () => {
832
- let r = await fetch(`${at}${t}`, {
833
- method: "POST",
834
- headers: {
835
- Authorization: `Bearer ${e}`,
836
- "Content-Type": "application/x-www-form-urlencoded"
837
- },
838
- body: ot(n)
839
- }), i = await r.json();
840
- if (!r.ok) {
841
- let e = r.status >= 500 || r.status === 429, t = i.error?.message ?? `HTTP ${r.status}`;
842
- throw Object.assign(Error(t), { transient: e });
843
- }
844
- return i;
845
- },
846
- catch: (e) => {
847
- let t = e.transient ?? !0;
848
- return X(String(e?.message ?? e), t);
849
- }
850
- }), ct = {
718
+ }, X = (e) => typeof e == "number" && Number.isFinite(e) ? /* @__PURE__ */ new Date(e * 1e3) : null, Qe = (e) => {
719
+ let t = e.items?.data ?? [];
720
+ if (t.length === 0) return 1;
721
+ let n = t.reduce((e, t) => e + (t.quantity ?? 0), 0);
722
+ return n > 0 ? n : 1;
723
+ }, $e = {
851
724
  active: "active",
852
725
  trialing: "trialing",
853
726
  past_due: "pastDue",
854
727
  unpaid: "pastDue",
855
728
  canceled: "canceled",
856
729
  incomplete: "incomplete",
857
- incomplete_expired: "canceled"
858
- }, Z = (e) => typeof e == "object" && e ? e : null, lt = (e) => typeof e == "number" && Number.isFinite(e) ? /* @__PURE__ */ new Date(e * 1e3) : null, Q = (e) => {
859
- let t = Z(e.metadata)?.tenantId;
730
+ incomplete_expired: "canceled",
731
+ paused: "pastDue"
732
+ }, Z = (e) => {
733
+ let t = e.metadata?.tenantId;
860
734
  if (typeof t == "string" && t.length > 0) return t;
861
735
  let n = e.client_reference_id;
862
736
  return typeof n == "string" && n.length > 0 ? n : null;
863
- }, ut = (e) => {
864
- let t = Z(e.metadata)?.plan;
865
- return typeof t == "string" && t.length > 0 ? t : null;
866
- }, dt = (e) => {
867
- let t = Z(e.items)?.data, n = (Array.isArray(t) ? Z(t[0]) : null)?.quantity;
868
- return typeof n == "number" && Number.isInteger(n) && n > 0 ? n : 1;
869
- }, ft = (e) => {
870
- let t = Z(e);
871
- if (t === null) return null;
872
- let n = t.type;
873
- if (typeof n != "string") return null;
874
- let r = Z(Z(t.data)?.object);
875
- if (r === null) return null;
876
- switch (n) {
737
+ }, et = (e) => {
738
+ let t = e.items?.data?.[0]?.price;
739
+ return typeof t?.id == "string" ? t.id : null;
740
+ }, tt = (e, t) => {
741
+ let n = e.metadata?.plan;
742
+ if (typeof n == "string" && n.length > 0) return n;
743
+ let r = et(e);
744
+ return r !== null && t ? t(r) : null;
745
+ }, nt = (e, t) => {
746
+ switch (e.type) {
877
747
  case "customer.subscription.created":
878
- case "customer.subscription.updated": {
879
- let e = Q(r), t = ut(r), n = r.id, i = ct[String(r.status)];
880
- return e === null || t === null || typeof n != "string" || i === void 0 ? null : {
748
+ case "customer.subscription.updated":
749
+ case "customer.subscription.resumed":
750
+ 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);
754
+ return {
881
755
  _tag: "subscriptionUpserted",
882
- tenantId: e,
883
- providerSubscriptionId: n,
884
- plan: t,
885
- status: i,
886
- quantity: dt(r),
887
- currentPeriodStart: lt(r.current_period_start),
888
- currentPeriodEnd: lt(r.current_period_end),
889
- cancelAt: lt(r.cancel_at)
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)
890
764
  };
891
765
  }
892
766
  case "customer.subscription.deleted": {
893
- let e = r.id, t = Q(r);
894
- return typeof e != "string" || t === null ? null : {
767
+ let t = e.data.object, n = Z(t);
768
+ return n === null ? null : {
895
769
  _tag: "subscriptionCanceled",
896
- tenantId: t,
897
- providerSubscriptionId: e
770
+ tenantId: n,
771
+ providerSubscriptionId: t.id
898
772
  };
899
773
  }
900
774
  case "invoice.paid":
901
775
  case "invoice.payment_failed": {
902
- let e = Q(r), t = r.id, i = r.amount_due, a = r.currency;
903
- return e === null || typeof t != "string" || typeof i != "number" || typeof a != "string" ? null : {
904
- _tag: n === "invoice.paid" ? "invoicePaid" : "invoicePaymentFailed",
905
- tenantId: e,
906
- providerInvoiceId: t,
907
- amountMinor: i,
908
- currency: a
776
+ let t = e.data.object, n = Z(t);
777
+ return n === null || typeof t.id != "string" ? null : {
778
+ _tag: e.type === "invoice.paid" ? "invoicePaid" : "invoicePaymentFailed",
779
+ tenantId: n,
780
+ providerInvoiceId: t.id,
781
+ amountMinor: t.amount_due,
782
+ currency: t.currency
909
783
  };
910
784
  }
911
785
  case "customer.created": {
912
- let e = Q(r), t = r.id;
913
- return e === null || typeof t != "string" ? null : {
786
+ let t = e.data.object, n = Z(t);
787
+ return n === null ? null : {
914
788
  _tag: "customerLinked",
915
- tenantId: e,
916
- providerCustomerId: t
789
+ tenantId: n,
790
+ providerCustomerId: t.id
917
791
  };
918
792
  }
919
793
  default: return null;
920
794
  }
921
- }, pt = (e) => {
922
- let { apiKey: t } = e;
795
+ }, rt = {
796
+ prorate: "create_prorations",
797
+ none: "none",
798
+ 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;
802
+ return {
803
+ plan: tt(e, r) ?? "free",
804
+ quantity: Qe(e),
805
+ prorationMinor: t,
806
+ currency: (i?.currency ?? "usd").toLowerCase(),
807
+ currentPeriodStart: n.start,
808
+ currentPeriodEnd: n.end
809
+ };
810
+ }, o = (e, t) => {
811
+ let n = e.items.data[0]?.id;
812
+ return {
813
+ items: [{
814
+ ...n === void 0 ? {} : { id: n },
815
+ ...t.priceId === void 0 ? {} : { price: t.priceId },
816
+ ...t.quantity === void 0 ? {} : { quantity: t.quantity }
817
+ }],
818
+ proration_behavior: rt[t.proration ?? "prorate"],
819
+ ...t.priceId !== void 0 && r ? { metadata: {
820
+ ...e.metadata,
821
+ plan: r(t.priceId) ?? ""
822
+ } } : {}
823
+ };
824
+ };
923
825
  return {
924
826
  name: "stripe",
925
- supportsMeteredUsage: e.supportsMeteredUsage ?? !0,
926
- createCheckoutSession: (e) => st(t, "/checkout/sessions", {
927
- mode: "subscription",
928
- "line_items[0][price]": e.priceId,
929
- "line_items[0][quantity]": "1",
930
- success_url: e.successUrl,
931
- cancel_url: e.cancelUrl,
932
- client_reference_id: e.tenantId,
933
- "metadata[tenantId]": e.tenantId,
934
- "subscription_data[metadata][tenantId]": e.tenantId,
935
- customer: e.providerCustomerId
936
- }).pipe(d.flatMap((e) => {
937
- let t = e.url;
938
- return typeof t == "string" ? d.succeed({ url: t }) : d.fail(X("checkout session response missing url", !1));
939
- })),
940
- createPortalSession: (e) => st(t, "/billing_portal/sessions", {
827
+ supportsMeteredUsage: t.supportsMeteredUsage ?? !0,
828
+ createCheckoutSession: (e) => Y("stripe.checkout.sessions.create", async () => {
829
+ let t = await n.checkout.sessions.create({
830
+ mode: "subscription",
831
+ line_items: [{
832
+ price: e.priceId,
833
+ quantity: e.quantity ?? 1,
834
+ ...e.adjustableQuantity ? { adjustable_quantity: {
835
+ enabled: !0,
836
+ minimum: e.adjustableQuantity.min,
837
+ maximum: e.adjustableQuantity.max
838
+ } } : {}
839
+ }],
840
+ success_url: e.successUrl,
841
+ cancel_url: e.cancelUrl,
842
+ client_reference_id: e.tenantId,
843
+ metadata: { tenantId: e.tenantId },
844
+ subscription_data: {
845
+ metadata: { tenantId: e.tenantId },
846
+ ...e.trialDays === void 0 ? {} : { trial_period_days: e.trialDays }
847
+ },
848
+ ...e.providerCustomerId === void 0 ? {} : { customer: e.providerCustomerId },
849
+ automatic_tax: { enabled: !0 },
850
+ allow_promotion_codes: !0
851
+ });
852
+ if (typeof t.url != "string") throw Error("checkout session has no url");
853
+ return { url: t.url };
854
+ }),
855
+ createPortalSession: (e) => Y("stripe.billingPortal.sessions.create", async () => ({ url: (await n.billingPortal.sessions.create({
941
856
  customer: e.providerCustomerId,
942
857
  return_url: e.returnUrl
943
- }).pipe(d.flatMap((e) => {
944
- let t = e.url;
945
- return typeof t == "string" ? d.succeed({ url: t }) : d.fail(X("portal session response missing url", !1));
858
+ })).url })),
859
+ reportUsage: (e) => Y("stripe.billing.meterEvents.create", async () => {
860
+ await n.billing.meterEvents.create({
861
+ event_name: e.entitlementKey,
862
+ identifier: `${e.tenantId}:${e.entitlementKey}:${e.periodEnd?.toISOString() ?? "current"}`,
863
+ payload: {
864
+ value: String(e.quantity),
865
+ stripe_customer_id: e.tenantId
866
+ }
867
+ });
868
+ }),
869
+ updateSubscription: (e) => Y("stripe.subscriptions.update", async () => {
870
+ let t = await i(e.providerSubscriptionId), r = await n.subscriptions.update(e.providerSubscriptionId, o(t, e)), s = await ot(n, r);
871
+ return a(r, s);
872
+ }),
873
+ previewSubscriptionChange: (e) => Y("stripe.invoices.createPreview", async () => {
874
+ let t = await i(e.providerSubscriptionId), s = o(t, e), c = (await n.invoices.createPreview({
875
+ subscription: e.providerSubscriptionId,
876
+ subscription_details: {
877
+ items: s.items,
878
+ ...s.proration_behavior === void 0 ? {} : { proration_behavior: s.proration_behavior }
879
+ }
880
+ })).lines.data.filter((e) => e.parent?.subscription_item_details?.proration === !0).reduce((e, t) => e + t.amount, 0);
881
+ return {
882
+ ...a(t, c),
883
+ ...e.quantity === void 0 ? {} : { quantity: e.quantity },
884
+ ...e.priceId !== void 0 && r ? { plan: r(e.priceId) ?? "free" } : {}
885
+ };
886
+ }),
887
+ listInvoices: (e) => Y("stripe.invoices.list", async () => (await n.invoices.list({
888
+ customer: e.providerCustomerId,
889
+ limit: e.limit ?? 20
890
+ })).data.map((e) => ({
891
+ providerInvoiceId: e.id ?? "",
892
+ amountMinor: e.amount_due,
893
+ currency: e.currency,
894
+ status: e.status ?? "unknown",
895
+ createdAt: X(e.created),
896
+ hostedUrl: e.hosted_invoice_url ?? null,
897
+ pdfUrl: e.invoice_pdf ?? null
898
+ }))),
899
+ verifyWebhook: (t) => f.try({
900
+ try: () => n.webhooks.constructEvent(Buffer.from(t.rawBody), t.signatureHeader, t.secret),
901
+ catch: (t) => new e({
902
+ source: "stripe.webhooks.constructEvent",
903
+ message: String(t?.message ?? t),
904
+ transient: !1
905
+ })
906
+ }),
907
+ normalizeEvent: (e) => f.succeed(at(e) ? nt(e, r) : null)
908
+ };
909
+ }, at = (e) => typeof e == "object" && !!e && typeof e.type == "string" && typeof e.data == "object", ot = async (e, t) => {
910
+ try {
911
+ 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);
912
+ } catch {
913
+ return 0;
914
+ }
915
+ }, st = (e) => {
916
+ let t = e?.baseUrl ?? "https://mock.billing.local", n = [], r = [], i = (e) => typeof e == "object" && !!e && typeof e._tag == "string";
917
+ return {
918
+ name: "mock",
919
+ supportsMeteredUsage: e?.supportsMeteredUsage ?? !0,
920
+ pushed: n,
921
+ updates: r,
922
+ createCheckoutSession: (e) => f.succeed({ url: `${t}/checkout?tenant=${encodeURIComponent(e.tenantId)}&price=${encodeURIComponent(e.priceId)}` }),
923
+ createPortalSession: (e) => f.succeed({ url: `${t}/portal?customer=${encodeURIComponent(e.providerCustomerId)}` }),
924
+ reportUsage: (e) => f.sync(() => {
925
+ n.push(e);
926
+ }),
927
+ updateSubscription: (e) => f.sync(() => (r.push(e), {
928
+ plan: e.priceId ?? "mock-plan",
929
+ quantity: e.quantity ?? 1,
930
+ prorationMinor: 0,
931
+ currency: "usd",
932
+ currentPeriodStart: null,
933
+ currentPeriodEnd: null
946
934
  })),
947
- reportUsage: (e) => st(t, "/billing/meter_events", {
948
- event_name: e.entitlementKey,
949
- "payload[value]": String(e.quantity),
950
- "payload[stripe_customer_id]": e.tenantId
951
- }).pipe(d.asVoid),
952
- normalizeEvent: (e) => d.succeed(ft(e))
935
+ previewSubscriptionChange: (e) => f.succeed({
936
+ plan: e.priceId ?? "mock-plan",
937
+ quantity: e.quantity ?? 1,
938
+ prorationMinor: 0,
939
+ currency: "usd",
940
+ currentPeriodStart: null,
941
+ currentPeriodEnd: null
942
+ }),
943
+ listInvoices: () => f.succeed([]),
944
+ normalizeEvent: (e) => f.succeed(i(e) ? e : null)
953
945
  };
954
- }, mt = (e) => {
946
+ }, ct = (e) => {
955
947
  let { provider: t, env: n } = e;
956
948
  if (t !== void 0 && typeof t == "object") return t;
957
949
  if ((t ?? (n.STRIPE_SECRET_KEY ? "stripe" : "mock")) === "stripe") {
958
950
  let t = e.apiKey ?? n.STRIPE_SECRET_KEY;
959
951
  if (!t) throw Error("billingPlugin: provider 'stripe' needs an apiKey (option or STRIPE_SECRET_KEY env)");
960
- return pt({ apiKey: t });
952
+ return it({
953
+ apiKey: t,
954
+ ...e.planForPriceId ? { planForPriceId: e.planForPriceId } : {}
955
+ });
961
956
  }
962
- return it();
963
- }, ht = O({ scope: "@voltro/plugin-billing" }), gt = "billing", $ = "/billing/webhook", _t = (e) => E({
964
- id: gt,
965
- path: $,
966
- provider: D(),
967
- payload: m.Unknown,
957
+ return st();
958
+ }, lt = k({ scope: "@voltro/plugin-billing" }), ut = "billing", Q = "/billing/webhook", dt = (e) => E({
959
+ id: ut,
960
+ path: Q,
961
+ provider: O(),
962
+ payload: h.Unknown,
968
963
  handler: async (t) => {
969
- let n = await d.runPromise(e.provider.normalizeEvent(t.body));
970
- if (n === null) return;
971
- await d.runPromise(e.service.applyEvent(n));
972
- let r = e.onEvent?.[n._tag];
973
- r !== void 0 && await d.runPromise(r(n).pipe(d.catchAllCause((e) => d.sync(() => {
974
- ht.warn("billing onEvent side effect failed", {
975
- event: n._tag,
964
+ let n = e.provider.verifyWebhook !== void 0 && e.webhookSecret !== null && e.webhookSecret !== void 0 ? await f.runPromise(e.provider.verifyWebhook({
965
+ rawBody: t.rawBody,
966
+ signatureHeader: t.headers["stripe-signature"] ?? "",
967
+ secret: e.webhookSecret ?? ""
968
+ })) : t.body, r = await f.runPromise(e.provider.normalizeEvent(n));
969
+ if (r === null) return;
970
+ await f.runPromise(e.service.applyEvent(r));
971
+ let i = e.onEvent?.[r._tag];
972
+ i !== void 0 && await f.runPromise(i(r).pipe(f.catchAllCause((e) => f.sync(() => {
973
+ lt.warn("billing onEvent side effect failed", {
974
+ event: r._tag,
976
975
  cause: String(e)
977
976
  });
978
977
  }))));
979
978
  }
980
- }), vt = (e) => re(_t(e), {
979
+ }), ft = (e) => D(dt(e), {
981
980
  resolveSecret: () => Promise.resolve(e.webhookSecret),
982
981
  ...e.idempotencyCache === void 0 ? {} : { idempotencyCache: e.idempotencyCache },
983
982
  log: (e) => {
984
- e.status >= 400 && ht.warn("billing webhook rejected", { ...e });
983
+ e.status >= 400 && lt.warn("billing webhook rejected", { ...e });
985
984
  }
986
- }), yt = (e) => e === null ? null : {
985
+ }), pt = (e) => e === null ? null : {
987
986
  plan: e.plan,
988
987
  status: e.status,
989
988
  quantity: e.quantity,
990
989
  currentPeriodEnd: e.currentPeriodEnd ? e.currentPeriodEnd.toISOString() : null,
991
990
  cancelAt: e.cancelAt ? e.cancelAt.toISOString() : null
992
- }, bt = h([{
991
+ }, $ = g([{
993
992
  name: "STRIPE_SECRET_KEY",
994
993
  required: !1,
995
994
  secret: !0,
@@ -999,41 +998,40 @@ var k = (e, t, n, r) => ({
999
998
  required: !1,
1000
999
  secret: !0,
1001
1000
  description: "Stripe webhook signing secret (whsec_…) for verifying webhook payloads."
1002
- }]), xt = (t = {}) => {
1003
- let n = process.env, f = mt({
1001
+ }]), mt = (t = {}) => {
1002
+ let n = process.env, p = q(t.plans ?? {}), m = ct({
1004
1003
  ...t.provider === void 0 ? {} : { provider: t.provider },
1005
1004
  ...t.apiKey === void 0 ? {} : { apiKey: t.apiKey },
1005
+ planForPriceId: p.planForPriceId,
1006
1006
  env: n
1007
- }), p = J(t.plans ?? {}), m = t.webhookSecret ?? bt.read("STRIPE_WEBHOOK_SECRET") ?? null;
1007
+ }), h = t.webhookSecret ?? $.read("STRIPE_WEBHOOK_SECRET") ?? null;
1008
1008
  x({
1009
- table: R,
1009
+ table: L,
1010
1010
  timeColumn: "updatedAt",
1011
- ttlMs: S(process.env.VOLTRO_BILLING_USAGE_TTL_HOURS, 24 * 400),
1011
+ ttlMs: ie(process.env.VOLTRO_BILLING_USAGE_TTL_HOURS, 24 * 400),
1012
1012
  label: "billing usage counters"
1013
1013
  }), x({
1014
- table: Y,
1014
+ table: J,
1015
1015
  timeColumn: "claimedAt",
1016
- ttlMs: S(process.env.VOLTRO_BILLING_FLUSH_CLAIM_TTL_HOURS, 1),
1016
+ ttlMs: ie(process.env.VOLTRO_BILLING_FLUSH_CLAIM_TTL_HOURS, 1),
1017
1017
  label: "billing flush claims"
1018
1018
  });
1019
- let h = t.dunningSchedule ?? N;
1020
- se(h);
1021
- let { service: g, holder: _ } = Le({
1022
- provider: f,
1019
+ let { service: g, holder: _ } = we({
1020
+ provider: m,
1023
1021
  plans: p,
1024
- dunningSchedule: h,
1025
1022
  ...t.attempts === void 0 ? {} : { attempts: t.attempts },
1023
+ ...t.checkout === void 0 ? {} : { checkout: t.checkout },
1026
1024
  ...t.resolveEntitlementLimit === void 0 ? {} : { resolveEntitlementLimit: t.resolveEntitlementLimit }
1027
- }), ne = Re(g), v = vt({
1028
- provider: f,
1025
+ }), v = Te(g), re = ft({
1026
+ provider: m,
1029
1027
  service: g,
1030
- webhookSecret: m,
1028
+ webhookSecret: h,
1031
1029
  ...t.onEvent === void 0 ? {} : { onEvent: t.onEvent }
1032
1030
  }), y = {
1033
1031
  method: "POST",
1034
- path: $,
1032
+ path: Q,
1035
1033
  handle: async (e) => {
1036
- let t = await v({
1034
+ let t = await re({
1037
1035
  method: e.method,
1038
1036
  path: e.path,
1039
1037
  headers: e.headers,
@@ -1048,40 +1046,63 @@ var k = (e, t, n, r) => ({
1048
1046
  }
1049
1047
  }, b = [
1050
1048
  {
1051
- ...l,
1049
+ ...ee,
1052
1050
  execute: (e, t) => {
1053
1051
  let n = e;
1054
1052
  return g.startCheckout({
1055
1053
  tenantId: t.request.subject.tenantId ?? "",
1056
1054
  plan: n.plan,
1057
1055
  successUrl: n.successUrl,
1058
- cancelUrl: n.cancelUrl
1056
+ cancelUrl: n.cancelUrl,
1057
+ ...n.quantity === void 0 ? {} : { quantity: n.quantity }
1059
1058
  });
1060
1059
  }
1061
1060
  },
1062
1061
  {
1063
- ...s,
1062
+ ...c,
1064
1063
  execute: (e, t) => {
1065
1064
  let n = e;
1066
1065
  return g.portalUrl(t.request.subject.tenantId ?? "", n.returnUrl);
1067
1066
  }
1068
1067
  },
1069
1068
  {
1070
- ...u,
1071
- execute: (e, t) => g.subscription(t.request.subject.tenantId ?? "").pipe(d.map((e) => ({ subscription: yt(e) })))
1069
+ ...d,
1070
+ execute: (e, t) => g.subscription(t.request.subject.tenantId ?? "").pipe(f.map((e) => ({ subscription: pt(e) })))
1072
1071
  },
1073
1072
  {
1074
- ...c,
1073
+ ...l,
1074
+ execute: (e, t) => {
1075
+ let n = e;
1076
+ return g.previewChange(t.request.subject.tenantId ?? "", {
1077
+ ...n.plan === void 0 ? {} : { plan: n.plan },
1078
+ ...n.quantity === void 0 ? {} : { quantity: n.quantity }
1079
+ }).pipe(f.map((e) => ({
1080
+ plan: e.plan,
1081
+ quantity: e.quantity,
1082
+ prorationMinor: e.prorationMinor,
1083
+ currency: e.currency
1084
+ })));
1085
+ }
1086
+ },
1087
+ {
1088
+ ...s,
1089
+ execute: (e, t) => g.invoices(t.request.subject.tenantId ?? "").pipe(f.map((e) => ({ invoices: e.map((e) => ({
1090
+ ...e,
1091
+ createdAt: e.createdAt?.toISOString() ?? null
1092
+ })) })))
1093
+ },
1094
+ {
1095
+ ...u,
1075
1096
  execute: (e, t) => {
1076
1097
  let n = e;
1077
- return g.reportUsage(t.request.subject.tenantId ?? "", n.key, n.quantity).pipe(d.map(() => ({ ok: !0 })));
1098
+ return g.reportUsage(t.request.subject.tenantId ?? "", n.key, n.quantity).pipe(f.map(() => ({ ok: !0 })));
1078
1099
  }
1079
1100
  },
1080
1101
  {
1081
1102
  ...a,
1082
1103
  execute: (e, t) => {
1083
1104
  let n = e;
1084
- return g.changePlan(t.request.subject.tenantId ?? "", n.plan).pipe(d.map((e) => ({
1105
+ return g.changePlan(t.request.subject.tenantId ?? "", n.plan).pipe(f.map((e) => ({
1085
1106
  plan: e.plan,
1086
1107
  quantity: e.quantity,
1087
1108
  prorationMinor: e.prorationMinor,
@@ -1093,7 +1114,7 @@ var k = (e, t, n, r) => ({
1093
1114
  ...o,
1094
1115
  execute: (e, t) => {
1095
1116
  let n = e;
1096
- return g.changeSeats(t.request.subject.tenantId ?? "", n.quantity).pipe(d.map((e) => ({
1117
+ return g.changeSeats(t.request.subject.tenantId ?? "", n.quantity).pipe(f.map((e) => ({
1097
1118
  plan: e.plan,
1098
1119
  quantity: e.quantity,
1099
1120
  prorationMinor: e.prorationMinor,
@@ -1101,33 +1122,33 @@ var k = (e, t, n, r) => ({
1101
1122
  })));
1102
1123
  }
1103
1124
  }
1104
- ], C = (e, t) => ({
1125
+ ], S = (e, t) => ({
1105
1126
  kind: "json",
1106
1127
  status: e,
1107
1128
  data: { error: t }
1108
- }), w = [{
1129
+ }), C = [{
1109
1130
  method: "GET",
1110
1131
  path: "/stats",
1111
1132
  description: "Billing config: provider, declared plans, enforced tags",
1112
- handler: () => d.succeed({
1133
+ handler: () => f.succeed({
1113
1134
  kind: "json",
1114
1135
  data: {
1115
- provider: f.name,
1116
- supportsMeteredUsage: f.supportsMeteredUsage,
1136
+ provider: m.name,
1137
+ supportsMeteredUsage: m.supportsMeteredUsage,
1117
1138
  plans: p.planIds(),
1118
1139
  enforce: t.enforce ? Object.keys(t.enforce) : [],
1119
- webhookPath: $
1140
+ webhookPath: Q
1120
1141
  }
1121
1142
  })
1122
1143
  }, {
1123
1144
  method: "GET",
1124
1145
  path: "/usage",
1125
1146
  description: "Pending (un-flushed) metered usage counters",
1126
- handler: () => _.stores.usage.pending().pipe(d.map((e) => ({
1147
+ handler: () => _.stores.usage.pending().pipe(f.map((e) => ({
1127
1148
  kind: "json",
1128
1149
  data: { usage: e }
1129
- })), d.catchAll((e) => d.succeed(C(500, e.message))))
1130
- }], T = [{
1150
+ })), f.catchAll((e) => f.succeed(S(500, e.message))))
1151
+ }], w = [{
1131
1152
  schema: e,
1132
1153
  import: {
1133
1154
  module: "@voltro/plugin-billing/errors",
@@ -1139,9 +1160,9 @@ var k = (e, t, n, r) => ({
1139
1160
  module: "@voltro/plugin-billing/errors",
1140
1161
  name: "EntitlementExceeded"
1141
1162
  }
1142
- }], E = t.enforce, re = E !== void 0 && Object.keys(E).length > 0 ? ie(E, g) : void 0, D = t.metering;
1143
- D !== void 0 && Be(D);
1144
- let O = D !== void 0 && Object.keys(D).length > 0 ? qe(g, D) : void 0, k = D !== void 0 && Qe(D), A = ee([O, re].filter((e) => e !== void 0)), j, M, ae = [
1163
+ }], T = t.enforce, E = T !== void 0 && Object.keys(T).length > 0 ? N(T, g) : void 0, D = t.metering;
1164
+ D !== void 0 && De(D);
1165
+ 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 = [
1145
1166
  "store:write",
1146
1167
  "inspect:read",
1147
1168
  "network:outbound:*",
@@ -1149,35 +1170,34 @@ var k = (e, t, n, r) => ({
1149
1170
  ...O === void 0 ? [] : ["rpc:intercept:query"],
1150
1171
  ...k ? ["store:changes:read"] : []
1151
1172
  ];
1152
- return te({
1173
+ return ne({
1153
1174
  name: t.name ? `@voltro/plugin-billing#${t.name}` : "@voltro/plugin-billing",
1154
1175
  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.",
1155
- permissions: ae,
1156
- declaredEnv: bt.declared,
1157
- services: ne,
1176
+ permissions: P,
1177
+ declaredEnv: $.declared,
1178
+ services: v,
1158
1179
  routes: b,
1159
1180
  rpcClientDescriptors: i,
1160
1181
  httpRoutes: [y],
1161
- inspectEndpoints: w,
1162
- extendSchema: { tables: [..._e(), tt] },
1163
- errorSchemas: T,
1182
+ inspectEndpoints: C,
1183
+ extendSchema: { tables: [...le(), He] },
1184
+ errorSchemas: w,
1164
1185
  ...A === void 0 ? {} : {
1165
1186
  interceptMutation: A,
1166
1187
  interceptAction: A
1167
1188
  },
1168
1189
  ...O === void 0 ? {} : { interceptQuery: O },
1169
- ...k ? { onChangeEvent: Je(g, D) } : {},
1190
+ ...k ? { onChangeEvent: Fe(g, D) } : {},
1170
1191
  bindDataStore: (e) => {
1171
- _.stores = Fe(e), j = e;
1192
+ _.stores = W(e), j = e;
1172
1193
  },
1173
- onActivate: (e) => d.sync(() => {
1194
+ onActivate: (e) => f.sync(() => {
1174
1195
  e.logger.info("billing active", {
1175
- provider: f.name,
1196
+ provider: m.name,
1176
1197
  plans: p.planIds().length,
1177
- enforce: E ? Object.keys(E).length : 0,
1198
+ enforce: T ? Object.keys(T).length : 0,
1178
1199
  metering: D ? Object.keys(D).length : 0,
1179
- dunningRetries: h.length,
1180
- webhook: $
1200
+ webhook: Q
1181
1201
  });
1182
1202
  let n = t.flushIntervalMs ?? 6e4, r = D, i = r !== void 0 && Object.keys(r).length > 0;
1183
1203
  if (n > 0) {
@@ -1187,15 +1207,15 @@ var k = (e, t, n, r) => ({
1187
1207
  if (t === void 0) return;
1188
1208
  let a = Date.now();
1189
1209
  (async () => {
1190
- await rt(t, nt(a, n), e, a) && (i && await d.runPromise(Ze(g, t, r).pipe(d.catchAll(() => d.void))), await d.runPromise(g.runDunningCycle(new Date(a)).pipe(d.catchAll(() => d.void))));
1210
+ await We(t, Ue(a, n), e, a) && i && await f.runPromise(Re(g, t, r).pipe(f.catchAll(() => f.void)));
1191
1211
  })();
1192
1212
  }, n), M.unref?.();
1193
1213
  }
1194
1214
  }),
1195
- onDeactivate: () => d.sync(() => {
1215
+ onDeactivate: () => f.sync(() => {
1196
1216
  M !== void 0 && (clearInterval(M), M = void 0);
1197
1217
  })
1198
1218
  });
1199
1219
  };
1200
1220
  //#endregion
1201
- export { F as BILLING_CUSTOMERS_TABLE, z as BILLING_DUNNING_TABLE, Y as BILLING_FLUSH_CLAIMS_TABLE, L as BILLING_INVOICES_TABLE, I as BILLING_SUBSCRIPTIONS_TABLE, R as BILLING_USAGE_TABLE, gt as BILLING_WEBHOOK_ID, $ as BILLING_WEBHOOK_PATH, e as BillingError, t as BillingService, N as DEFAULT_DUNNING_SCHEDULE, n as DEFAULT_PLAN, r as EntitlementExceeded, ue as advanceDunning, fe as billingCustomersTable, ge as billingDunningTable, tt as billingFlushClaimsTable, me as billingInvoicesTable, xt as billingPlugin, Re as billingServiceLayer, pe as billingSubscriptionsTable, _e as billingTables, he as billingUsageTable, _t as billingWebhookDescriptor, Le as buildBillingService, ie as buildEnforceInterceptor, J as buildPlanRegistry, rt as claimFlushWindow, ae as computeProration, Fe as dataStoreStores, et as emptyPlanRegistry, k as evaluateEntitlement, nt as flushWindowKey, Pe as memoryStores, it as mockProvider, vt as mountBillingWebhook, ft as normalizeStripeEvent, ce as openDunning, K as periodKey, le as recoverDunning, j as requireEntitlement, mt as resolveProvider, P as retryTimeFor, M as roundHalfAwayFromZero, de as runDunningCycle, pt as stripeProvider, se as validateDunningSchedule };
1221
+ 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 };