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