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