@voltro/plugin-billing 0.85.0 → 0.87.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 +5987 -0
- package/README.md +435 -0
- package/THIRD-PARTY-NOTICES.md +864 -253
- package/dist/catalog.js +471 -0
- package/dist/checkoutStore.js +165 -0
- package/dist/closeStore.js +125 -0
- package/dist/custodyInventory.js +155 -0
- package/dist/customerStore.js +259 -0
- package/dist/dunning.js +209 -0
- package/dist/economy.d.ts +30 -0
- package/dist/economy.js +266 -0
- package/dist/entitlement.js +41 -0
- package/dist/eventDelivery.js +72 -0
- package/dist/flushClaim.js +20 -0
- package/dist/index.d.ts +669 -65
- package/dist/index.js +243 -2803
- package/dist/invoiceHistory.js +91 -0
- package/dist/invoiceHistoryJournal.js +476 -0
- package/dist/invoiceHistoryProgress.js +235 -0
- package/dist/invoiceHistoryWork.js +363 -0
- package/dist/invoiceSnapshot.js +66 -0
- package/dist/invoiceStore.js +133 -0
- package/dist/metering.js +74 -0
- package/dist/plans.js +24 -0
- package/dist/providerIdentity.js +24 -0
- package/dist/providers.js +338 -0
- package/dist/provision.js +153 -0
- package/dist/service.js +1116 -0
- package/dist/stores.js +260 -0
- package/dist/stripeClient.js +53 -0
- package/dist/stripeIdentity.js +56 -0
- package/dist/stripeInvoiceHistory.js +67 -0
- package/dist/stripeInvoiceSnapshot.js +86 -0
- package/dist/stripeProvider.js +479 -0
- package/dist/stripeRest.js +52 -0
- package/dist/stripeVerifiedEvent.js +29 -0
- package/dist/table.js +77 -0
- package/dist/types.d.ts +298 -27
- package/dist/usageEvents.js +65 -0
- package/dist/webhook.js +38 -0
- package/package.json +17 -7
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { BillingError as e } from "./types.js";
|
|
2
|
+
import { decodeInvoiceSnapshot as t, encodeInvoiceSnapshot as n, invoiceSnapshotError as r } from "./invoiceSnapshot.js";
|
|
3
|
+
import { Effect as i } from "effect";
|
|
4
|
+
import { and as a, eq as o, id as s, integer as c, json as l, table as u, text as d, timestamp as f } from "@voltro/database";
|
|
5
|
+
//#region src/invoiceStore.ts
|
|
6
|
+
var p = "_voltro_billing_account_invoices", m = u("_voltro_billing_invoice_observations", {
|
|
7
|
+
id: s({ prefix: "binvobs" }),
|
|
8
|
+
tenantId: d(),
|
|
9
|
+
provider: d(),
|
|
10
|
+
providerInvoiceId: d(),
|
|
11
|
+
providerCustomerId: d(),
|
|
12
|
+
identityKey: d().maxLength(64),
|
|
13
|
+
revision: c(),
|
|
14
|
+
providerSnapshot: l().serverOnly(),
|
|
15
|
+
createdAt: f()
|
|
16
|
+
}).index("byBillingInvoiceObservationTenant", ["tenantId"]).unique("byBillingInvoiceObservationIdentity", ["identityKey"]), h = u(p, {
|
|
17
|
+
id: s({ prefix: "binvobs" }),
|
|
18
|
+
tenantId: d(),
|
|
19
|
+
provider: d(),
|
|
20
|
+
providerAccountId: d().maxLength(191),
|
|
21
|
+
livemode: c(),
|
|
22
|
+
providerInvoiceId: d(),
|
|
23
|
+
providerCustomerId: d(),
|
|
24
|
+
identityKey: d().maxLength(64),
|
|
25
|
+
revision: c(),
|
|
26
|
+
providerSnapshot: l().serverOnly(),
|
|
27
|
+
createdAt: f()
|
|
28
|
+
}).index("byBillingAccountInvoiceTenant", ["tenantId"]).unique("byBillingAccountInvoiceIdentity", ["identityKey"]), g = 2147483647, _ = (e) => typeof e == "string" && e.length > 0 && e.trim() === e, v = (e, t) => ({
|
|
29
|
+
table: p,
|
|
30
|
+
predicate: e,
|
|
31
|
+
order: [],
|
|
32
|
+
take: t,
|
|
33
|
+
skip: void 0,
|
|
34
|
+
projection: void 0
|
|
35
|
+
}), y = (t) => i.tryPromise({
|
|
36
|
+
try: t,
|
|
37
|
+
catch: (t) => t instanceof e ? t : r("The native invoice observation could not be persisted or read.", !0)
|
|
38
|
+
}), b = (e) => {
|
|
39
|
+
if (![
|
|
40
|
+
e.tenantId,
|
|
41
|
+
e.provider,
|
|
42
|
+
e.providerAccountId,
|
|
43
|
+
e.providerCustomerId,
|
|
44
|
+
e.providerInvoiceId
|
|
45
|
+
].every(_) || typeof e.livemode != "boolean") throw r("A valid customer-bound invoice identity is required.");
|
|
46
|
+
return Object.freeze({
|
|
47
|
+
tenantId: e.tenantId,
|
|
48
|
+
provider: e.provider,
|
|
49
|
+
providerAccountId: e.providerAccountId,
|
|
50
|
+
livemode: e.livemode,
|
|
51
|
+
providerCustomerId: e.providerCustomerId,
|
|
52
|
+
providerInvoiceId: e.providerInvoiceId
|
|
53
|
+
});
|
|
54
|
+
}, x = async (e) => {
|
|
55
|
+
let t = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([
|
|
56
|
+
"voltro.account-invoice-observation.v1",
|
|
57
|
+
e.provider,
|
|
58
|
+
e.providerAccountId,
|
|
59
|
+
e.livemode,
|
|
60
|
+
e.providerInvoiceId
|
|
61
|
+
])));
|
|
62
|
+
return [...new Uint8Array(t)].map((e) => e.toString(16).padStart(2, "0")).join("");
|
|
63
|
+
}, S = (e, t) => e.tenantId === t.tenantId && e.provider === t.provider && e.providerAccountId === t.providerAccountId && e.livemode === t.livemode && e.providerCustomerId === t.providerCustomerId && e.providerInvoiceId === t.providerInvoiceId, C = (e, t) => {
|
|
64
|
+
if (!S(e, t)) throw r("The invoice is bound to a different tenant or billing customer.");
|
|
65
|
+
}, w = (e) => {
|
|
66
|
+
let n = e.tenantId, i = e.provider, a = e.providerInvoiceId, o = e.providerAccountId, s = e.livemode, c = e.providerCustomerId, l = e.identityKey, u = e.revision, d = e.id;
|
|
67
|
+
if (!_(n) || !_(i) || !_(a) || !_(c) || !_(d) || !_(o) || s !== 0 && s !== 1 || typeof l != "string" || !/^[0-9a-f]{64}$/.test(l) || typeof u != "number" || !Number.isInteger(u) || u < 1 || u > g) throw r("The native invoice observation identity is invalid.");
|
|
68
|
+
let f = t(e.providerSnapshot);
|
|
69
|
+
if (f.providerCustomerId !== c || f.providerInvoiceId !== a || f.livemode !== (s === 1)) throw r("The stored observation has a different customer or invoice.");
|
|
70
|
+
return {
|
|
71
|
+
id: d,
|
|
72
|
+
tenantId: n,
|
|
73
|
+
provider: i,
|
|
74
|
+
providerAccountId: o,
|
|
75
|
+
livemode: s === 1,
|
|
76
|
+
providerInvoiceId: a,
|
|
77
|
+
providerCustomerId: c,
|
|
78
|
+
identityKey: l,
|
|
79
|
+
revision: u,
|
|
80
|
+
snapshot: f
|
|
81
|
+
};
|
|
82
|
+
}, T = (e, t, i) => {
|
|
83
|
+
let a = b(e), o = t === null ? null : Object.freeze({
|
|
84
|
+
...t,
|
|
85
|
+
...b(t)
|
|
86
|
+
});
|
|
87
|
+
if (o !== null && (C(o, a), !_(o.id) || !/^[0-9a-f]{64}$/.test(o.identityKey) || !Number.isInteger(o.revision) || o.revision < 1 || o.revision >= g)) throw r("The expected native invoice revision is invalid or exhausted.");
|
|
88
|
+
if (i.providerInvoiceId !== a.providerInvoiceId || i.providerCustomerId !== a.providerCustomerId || i.livemode !== a.livemode) throw r("The complete invoice must match the confirmed customer binding.");
|
|
89
|
+
return {
|
|
90
|
+
owner: a,
|
|
91
|
+
previous: o,
|
|
92
|
+
providerSnapshot: n(i)
|
|
93
|
+
};
|
|
94
|
+
}, E = (e) => {
|
|
95
|
+
let t = async (t) => {
|
|
96
|
+
let n = await e.query(v(o("identityKey", t), 2));
|
|
97
|
+
if (n.length > 1) throw r("The native invoice identity is not unique.");
|
|
98
|
+
let i = n[0] ? w(n[0]) : null;
|
|
99
|
+
if (i !== null && (i.identityKey !== t || await x(i) !== t)) throw r("The native invoice identity digest could not be confirmed.");
|
|
100
|
+
return i;
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
get: (e) => y(async () => {
|
|
104
|
+
let n = b(e), r = await t(await x(n));
|
|
105
|
+
return r !== null && C(r, n), r;
|
|
106
|
+
}),
|
|
107
|
+
complete: (n, i, s) => y(async () => {
|
|
108
|
+
let { owner: c, previous: l, providerSnapshot: u } = T(n, i, s), d = await x(c);
|
|
109
|
+
if (l === null) {
|
|
110
|
+
let n = "binvobs_" + globalThis.crypto.randomUUID();
|
|
111
|
+
await e.insertIgnore(p, {
|
|
112
|
+
id: n,
|
|
113
|
+
...c,
|
|
114
|
+
livemode: +!!c.livemode,
|
|
115
|
+
identityKey: d,
|
|
116
|
+
revision: 1,
|
|
117
|
+
providerSnapshot: u,
|
|
118
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
119
|
+
}, { conflictColumns: ["identityKey"] });
|
|
120
|
+
let i = await t(d);
|
|
121
|
+
if (i === null) throw r("The persisted invoice observation could not be confirmed.", !0);
|
|
122
|
+
return C(i, c), i.id === n;
|
|
123
|
+
}
|
|
124
|
+
if (d !== l.identityKey) throw r("The expected invoice identity digest is invalid.");
|
|
125
|
+
return await e.updateMany(p, {
|
|
126
|
+
providerSnapshot: u,
|
|
127
|
+
revision: l.revision + 1
|
|
128
|
+
}, { where: a(o("id", l.id), o("identityKey", d), o("revision", l.revision), o("tenantId", c.tenantId), o("provider", c.provider), o("providerAccountId", c.providerAccountId), o("livemode", +!!c.livemode), o("providerCustomerId", c.providerCustomerId), o("providerInvoiceId", c.providerInvoiceId)) }) === 1;
|
|
129
|
+
})
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
//#endregion
|
|
133
|
+
export { p as BILLING_INVOICE_OBSERVATIONS_TABLE, h as billingInvoiceObservationsTable, m as billingRetainedInvoiceObservationsTable, E as dataStoreInvoiceStore };
|
package/dist/metering.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { periodKey as e } from "./service.js";
|
|
2
|
+
import { Effect as t } from "effect";
|
|
3
|
+
import { OccurrenceCounter as n, changeDigest as r, composeChangeKey as i, gte as a, occurrenceWindowMs as o } from "@voltro/database";
|
|
4
|
+
//#region src/metering.ts
|
|
5
|
+
var s = "_voltro_ai_usage", c = (e) => {
|
|
6
|
+
for (let [t, n] of Object.entries(e)) {
|
|
7
|
+
if (t.length === 0) throw Error("billing metering: a meter key must be non-empty");
|
|
8
|
+
if (n.from === "rpc") {
|
|
9
|
+
if (n.match === void 0 || typeof n.match == "string" && n.match.length === 0) throw Error(`billing metering: rpc meter '${t}' needs a non-empty 'match'`);
|
|
10
|
+
} else if (n.from === "cdc") {
|
|
11
|
+
if (typeof n.table != "string" || n.table.length === 0) throw Error(`billing metering: cdc meter '${t}' needs a 'table'`);
|
|
12
|
+
} else if (n.from !== "ai") throw Error(`billing metering: meter '${t}' has unknown source '${String(n.from)}'`);
|
|
13
|
+
}
|
|
14
|
+
}, l = /* @__PURE__ */ new WeakMap(), u = (e) => {
|
|
15
|
+
if (!e.global && !e.sticky) return e;
|
|
16
|
+
let t = l.get(e);
|
|
17
|
+
return t === void 0 && (t = new RegExp(e.source, e.flags.replace(/[gy]/g, "")), l.set(e, t)), t;
|
|
18
|
+
}, d = (e, t) => typeof e == "string" ? t === e : u(e).test(t), f = (e, t, n) => Object.entries(e).filter(([, e]) => e.from === "rpc" && (e.kind === void 0 || e.kind === n) && d(e.match, t)).map(([e]) => e), p = (e, t, n) => Object.entries(e).filter(([, e]) => e.from === "cdc" && e.table === t && (e.op ?? "insert") === n).map(([e]) => e), m = (e) => Object.entries(e).filter(([, e]) => e.from === "ai").map(([e, t]) => [e, t.metric ?? "tokens"]), h = (e) => e.tenantId, g = (e, n, r, i) => t.forEach(r, (r) => e.reportUsage(n, r, i).pipe(t.catch(() => t.void)), { discard: !0 }), _ = (e, n) => (r, i) => {
|
|
19
|
+
let a = f(n, i.tag, i.kind);
|
|
20
|
+
if (a.length === 0) return r;
|
|
21
|
+
let o = h(i.subject);
|
|
22
|
+
return o === null ? r : r.pipe(t.tap(() => g(e, o, a, 1)));
|
|
23
|
+
}, v = "billing-meter", y = 5e4, b = (e, a, s = {}) => {
|
|
24
|
+
let c = new n({
|
|
25
|
+
windowMs: o(),
|
|
26
|
+
maxEntries: y
|
|
27
|
+
}), l = s.now ?? (() => Date.now()), u = !1;
|
|
28
|
+
return (n) => {
|
|
29
|
+
let o = p(a, n.table, n.op);
|
|
30
|
+
if (o.length === 0) return t.void;
|
|
31
|
+
let d = n.new?.tenantId ?? n.old?.tenantId ?? null;
|
|
32
|
+
if (d === null) return t.void;
|
|
33
|
+
let f = s.claimChange?.();
|
|
34
|
+
if (f === void 0) return u || (u = !0, s.onUngated?.()), g(e, d, o, 1);
|
|
35
|
+
let m = String(n.new?.id ?? n.old?.id ?? ""), h = r({
|
|
36
|
+
pipe: v,
|
|
37
|
+
op: n.op,
|
|
38
|
+
key: m,
|
|
39
|
+
next: n.new ?? null,
|
|
40
|
+
prev: n.old ?? null
|
|
41
|
+
}), _ = i(h, c.next(h, l()));
|
|
42
|
+
return t.promise(() => f(v, _)).pipe(t.flatMap((n) => n ? g(e, d, o, 1) : t.void));
|
|
43
|
+
};
|
|
44
|
+
}, x = (e) => new Date(Date.UTC(e.getUTCFullYear(), e.getUTCMonth(), 1, 0, 0, 0, 0)), S = (n, r, i, o = () => /* @__PURE__ */ new Date()) => t.gen(function* () {
|
|
45
|
+
let c = m(i);
|
|
46
|
+
if (c.length === 0) return;
|
|
47
|
+
let l = o();
|
|
48
|
+
e(l);
|
|
49
|
+
let u = {
|
|
50
|
+
table: s,
|
|
51
|
+
predicate: a("calledAt", x(l)),
|
|
52
|
+
order: [],
|
|
53
|
+
take: void 0,
|
|
54
|
+
skip: void 0,
|
|
55
|
+
projection: void 0
|
|
56
|
+
}, d = yield* t.promise(() => Promise.resolve(r.query(u)).then((e) => e).catch(() => [])), f = /* @__PURE__ */ new Map();
|
|
57
|
+
for (let e of d) {
|
|
58
|
+
let t = e.tenantId ?? null;
|
|
59
|
+
if (t === null) continue;
|
|
60
|
+
let n = f.get(t) ?? {
|
|
61
|
+
tokens: 0,
|
|
62
|
+
cost: 0
|
|
63
|
+
};
|
|
64
|
+
n.tokens += (Number(e.inputTokens) || 0) + (Number(e.outputTokens) || 0), n.cost += Number(e.costMicroUsd) || 0, f.set(t, n);
|
|
65
|
+
}
|
|
66
|
+
for (let [e, r] of c) for (let [i, a] of f) {
|
|
67
|
+
let o = r === "costMicroUsd" ? a.cost : a.tokens;
|
|
68
|
+
if (o <= 0) continue;
|
|
69
|
+
let s = o - (yield* n.checkEntitlement(i, e, 0).pipe(t.map((e) => e.used), t.catch(() => t.succeed(0))));
|
|
70
|
+
s > 0 && (yield* n.reportUsage(i, e, s).pipe(t.catch(() => t.void)));
|
|
71
|
+
}
|
|
72
|
+
}), C = (e, n, r, i) => S(e, n, r, i).pipe(t.andThen(e.flushUsage().pipe(t.catch(() => t.void)))), w = (e) => Object.values(e).some((e) => e.from === "cdc");
|
|
73
|
+
//#endregion
|
|
74
|
+
export { v as METERING_CLAIM_SCOPE, b as buildMeteringChangeTap, _ as buildMeteringInterceptor, p as cdcMetersFor, w as hasCdcMeters, S as reconcileAiMeters, f as rpcMetersFor, C as runAutopilotFlush, c as validateMeteringConfig };
|
package/dist/plans.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { DEFAULT_PLAN as e } from "./types.js";
|
|
2
|
+
//#region src/plans.ts
|
|
3
|
+
var t = (e) => e === void 0 ? 0 : e === "unlimited" ? Infinity : e, n = (e) => {
|
|
4
|
+
let n = /* @__PURE__ */ new Map();
|
|
5
|
+
for (let [t, r] of Object.entries(e)) r.priceId !== void 0 && n.set(r.priceId, t);
|
|
6
|
+
return {
|
|
7
|
+
has: (t) => Object.prototype.hasOwnProperty.call(e, t),
|
|
8
|
+
entitlementLimit: (n, r) => {
|
|
9
|
+
let i = e[n];
|
|
10
|
+
return i === void 0 ? 0 : t(i.entitlements[r]);
|
|
11
|
+
},
|
|
12
|
+
planForPriceId: (e) => n.get(e) ?? null,
|
|
13
|
+
priceIdFor: (t) => e[t]?.priceId ?? null,
|
|
14
|
+
meteredPriceIdsFor: (t) => e[t]?.meteredPriceIds ?? [],
|
|
15
|
+
unitAmountFor: (t) => {
|
|
16
|
+
let n = e[t]?.unitAmountMinor;
|
|
17
|
+
return n !== void 0 && Number.isFinite(n) ? n : 0;
|
|
18
|
+
},
|
|
19
|
+
currencyFor: (t) => (e[t]?.currency ?? "usd").toLowerCase(),
|
|
20
|
+
planIds: () => Object.keys(e)
|
|
21
|
+
};
|
|
22
|
+
}, r = () => n({ [e]: { entitlements: {} } });
|
|
23
|
+
//#endregion
|
|
24
|
+
export { n as buildPlanRegistry, r as emptyPlanRegistry };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BillingError as e } from "./types.js";
|
|
2
|
+
//#region src/providerIdentity.ts
|
|
3
|
+
var t = () => new e({
|
|
4
|
+
source: "providerIdentity",
|
|
5
|
+
message: "The provider account or customer observation could not be confirmed.",
|
|
6
|
+
transient: !0
|
|
7
|
+
}), n = (e) => typeof e == "string" && e.length > 0 && e.trim() === e, r = (e) => {
|
|
8
|
+
if (!n(e.providerAccountId) || typeof e.livemode != "boolean") throw t();
|
|
9
|
+
return Object.freeze({
|
|
10
|
+
providerAccountId: e.providerAccountId,
|
|
11
|
+
livemode: e.livemode
|
|
12
|
+
});
|
|
13
|
+
}, i = (e, t) => e.providerAccountId === t.providerAccountId && e.livemode === t.livemode, a = (e) => {
|
|
14
|
+
let i = r(e.account);
|
|
15
|
+
if (!n(e.providerCustomerId) || typeof e.deleted != "boolean" || !(e.observedAt instanceof Date) || !Number.isFinite(e.observedAt.getTime())) throw t();
|
|
16
|
+
return {
|
|
17
|
+
account: i,
|
|
18
|
+
providerCustomerId: e.providerCustomerId,
|
|
19
|
+
deleted: e.deleted,
|
|
20
|
+
observedAt: new Date(e.observedAt)
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
export { r as captureProviderAccount, a as captureProviderCustomer, n as exactProviderIdentifier, t as providerIdentityError, i as sameProviderAccount };
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { BillingError as e } from "./types.js";
|
|
2
|
+
import { captureProviderAccount as t, captureProviderCustomer as n, providerIdentityError as r, sameProviderAccount as i } from "./providerIdentity.js";
|
|
3
|
+
import { decodeInvoiceSnapshot as a, encodeInvoiceSnapshot as o, invoiceSnapshotError as s } from "./invoiceSnapshot.js";
|
|
4
|
+
import { captureInvoiceHistoryInput as c, captureInvoiceHistoryPage as l, historyError as u, invoiceHistoryPreview as d } from "./invoiceHistory.js";
|
|
5
|
+
import { STRIPE_WEBHOOK_EVENTS as f, normalizeStripeEvent as p, stripeProvider as m } from "./stripeProvider.js";
|
|
6
|
+
import { Effect as h } from "effect";
|
|
7
|
+
//#region src/providers.ts
|
|
8
|
+
var g = (f) => {
|
|
9
|
+
let p = t({
|
|
10
|
+
providerAccountId: f?.accountId ?? "mock_" + globalThis.crypto.randomUUID(),
|
|
11
|
+
livemode: !1
|
|
12
|
+
}), m = /* @__PURE__ */ new Map(), g = 0, _ = f?.baseUrl ?? "https://mock.billing.local", v = [], y = [], b = [], x = /* @__PURE__ */ new Map(), S = /* @__PURE__ */ new Map(), C = /* @__PURE__ */ new Map(), w = /* @__PURE__ */ new Map(), T = [], E = (t) => h.try({
|
|
13
|
+
try: () => {
|
|
14
|
+
let e = c(t);
|
|
15
|
+
if (!i(e.account, p)) throw u("The mock invoice history account differs.");
|
|
16
|
+
let n = T.filter((t) => t.providerCustomerId === e.providerCustomerId).slice().sort((e, t) => t.createdAt.getTime() - e.createdAt.getTime() || (e.providerInvoiceId < t.providerInvoiceId ? 1 : e.providerInvoiceId > t.providerInvoiceId ? -1 : 0)), r = e.cursor === void 0 ? -1 : n.findIndex((t) => t.providerInvoiceId === e.cursor?.afterInvoiceId);
|
|
17
|
+
if (e.cursor !== void 0 && r < 0) throw u("The mock invoice cursor does not belong to this customer.");
|
|
18
|
+
let a = n.slice(r + 1, r + 1 + e.limit), o = a[a.length - 1];
|
|
19
|
+
return l(e, {
|
|
20
|
+
account: p,
|
|
21
|
+
providerCustomerId: e.providerCustomerId,
|
|
22
|
+
invoices: a,
|
|
23
|
+
nextCursor: r + 1 + a.length < n.length && o !== void 0 ? {
|
|
24
|
+
...p,
|
|
25
|
+
providerCustomerId: e.providerCustomerId,
|
|
26
|
+
afterInvoiceId: o.providerInvoiceId
|
|
27
|
+
} : null,
|
|
28
|
+
observedAt: j()
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
catch: (t) => t instanceof e ? t : u()
|
|
32
|
+
}), D = [], O = [], k = [], A = [], j = f?.now ?? (() => /* @__PURE__ */ new Date()), M = /* @__PURE__ */ new Map(), N = (e) => e.status === "open" && e.expiresAt.getTime() <= j().getTime() ? {
|
|
33
|
+
...e,
|
|
34
|
+
status: "expired",
|
|
35
|
+
url: null
|
|
36
|
+
} : e, P = [], F = [], I = /* @__PURE__ */ new Map(), L = [], R = 0, z = (e, t) => {
|
|
37
|
+
let n = {
|
|
38
|
+
...x.get(e) ?? {
|
|
39
|
+
status: "active",
|
|
40
|
+
plan: null,
|
|
41
|
+
quantity: 1,
|
|
42
|
+
currentPeriodStart: null,
|
|
43
|
+
currentPeriodEnd: null,
|
|
44
|
+
cancelAt: null
|
|
45
|
+
},
|
|
46
|
+
...t
|
|
47
|
+
};
|
|
48
|
+
x.set(e, n);
|
|
49
|
+
let r = S.get(e);
|
|
50
|
+
return r !== void 0 && S.set(e, {
|
|
51
|
+
...r,
|
|
52
|
+
...t
|
|
53
|
+
}), n;
|
|
54
|
+
}, B = (e) => typeof e == "object" && !!e && typeof e._tag == "string";
|
|
55
|
+
return {
|
|
56
|
+
name: "mock",
|
|
57
|
+
fetchAccountIdentity: () => h.succeed(t(p)),
|
|
58
|
+
fetchCustomerSnapshot: (e) => h.try({
|
|
59
|
+
try: () => {
|
|
60
|
+
let a = t(e.account);
|
|
61
|
+
if (!i(a, p)) throw r();
|
|
62
|
+
let o = m.get(e.providerCustomerId);
|
|
63
|
+
if (o === void 0) throw r();
|
|
64
|
+
return n({
|
|
65
|
+
...o,
|
|
66
|
+
observedAt: j()
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
catch: () => r()
|
|
70
|
+
}),
|
|
71
|
+
setCustomerSnapshot: (e) => {
|
|
72
|
+
let t = n(e);
|
|
73
|
+
if (!i(t.account, p)) throw r();
|
|
74
|
+
m.set(t.providerCustomerId, t);
|
|
75
|
+
},
|
|
76
|
+
setInvoiceHistory: (e) => {
|
|
77
|
+
if (!Array.isArray(e) || Object.keys(e).length !== e.length) throw u("The mock invoice history must be complete and dense.");
|
|
78
|
+
let t = /* @__PURE__ */ new Set(), n = e.map((e) => {
|
|
79
|
+
if (t.has(e.providerInvoiceId)) throw u("The mock invoice history contains duplicate identities.");
|
|
80
|
+
t.add(e.providerInvoiceId);
|
|
81
|
+
let n = c({
|
|
82
|
+
account: p,
|
|
83
|
+
providerCustomerId: e.providerCustomerId,
|
|
84
|
+
limit: 1
|
|
85
|
+
}), r = l(n, {
|
|
86
|
+
account: p,
|
|
87
|
+
providerCustomerId: e.providerCustomerId,
|
|
88
|
+
invoices: [e],
|
|
89
|
+
nextCursor: null,
|
|
90
|
+
observedAt: j()
|
|
91
|
+
}).invoices[0];
|
|
92
|
+
if (r === void 0) throw u();
|
|
93
|
+
return r;
|
|
94
|
+
});
|
|
95
|
+
T = Object.freeze(n);
|
|
96
|
+
},
|
|
97
|
+
setInvoiceSnapshot: (e) => {
|
|
98
|
+
let t = o(e);
|
|
99
|
+
w.set(e.providerInvoiceId, {
|
|
100
|
+
customerId: e.providerCustomerId,
|
|
101
|
+
encoded: t
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
fetchInvoiceSnapshot: (t) => h.try({
|
|
105
|
+
try: () => {
|
|
106
|
+
let e = w.get(t.providerInvoiceId);
|
|
107
|
+
if (e === void 0) throw s("The mock provider has no detailed invoice evidence.");
|
|
108
|
+
if (e.customerId !== t.providerCustomerId) throw s("The invoice belongs to a different mock billing customer.");
|
|
109
|
+
return a(e.encoded);
|
|
110
|
+
},
|
|
111
|
+
catch: (t) => t instanceof e ? t : s("The mock invoice observation is invalid.")
|
|
112
|
+
}),
|
|
113
|
+
supportsMeteredUsage: f?.supportsMeteredUsage ?? !0,
|
|
114
|
+
pushed: v,
|
|
115
|
+
updates: y,
|
|
116
|
+
fetched: b,
|
|
117
|
+
cancels: D,
|
|
118
|
+
resumes: O,
|
|
119
|
+
priceAdds: k,
|
|
120
|
+
checkouts: A,
|
|
121
|
+
customersCreated: P,
|
|
122
|
+
expiredFor: F,
|
|
123
|
+
setUndelivered: (e) => {
|
|
124
|
+
L = structuredClone(e);
|
|
125
|
+
},
|
|
126
|
+
undeliveredReads: () => R,
|
|
127
|
+
listUndeliveredEvents: (t) => h.try({
|
|
128
|
+
try: () => {
|
|
129
|
+
if (R += 1, !Number.isInteger(t.limit) || t.limit < 1 || t.limit > 100) throw Error("Invalid event page size.");
|
|
130
|
+
let e = t.cursor === null ? 0 : L.findIndex((e) => e.id === t.cursor) + 1;
|
|
131
|
+
if (t.cursor !== null && e === 0) throw Error("Unknown event cursor.");
|
|
132
|
+
let n = structuredClone(L.slice(e, e + t.limit));
|
|
133
|
+
return {
|
|
134
|
+
events: n,
|
|
135
|
+
nextCursor: e + n.length < L.length ? n.at(-1)?.id ?? null : null
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
catch: () => new e({
|
|
139
|
+
source: "mock.events.list",
|
|
140
|
+
message: "The event page could not be read.",
|
|
141
|
+
transient: !1
|
|
142
|
+
})
|
|
143
|
+
}),
|
|
144
|
+
ensureCustomer: (e) => h.sync(() => {
|
|
145
|
+
let t = I.get(e.tenantId);
|
|
146
|
+
if (t !== void 0 && m.get(t)?.deleted === !1) return t;
|
|
147
|
+
let r = `mock_cus_${e.tenantId}`, i = t === void 0 ? r : `${r}_${++g}`, a = n({
|
|
148
|
+
account: p,
|
|
149
|
+
providerCustomerId: i,
|
|
150
|
+
deleted: !1,
|
|
151
|
+
observedAt: j()
|
|
152
|
+
});
|
|
153
|
+
return I.set(e.tenantId, i), m.set(i, a), P.push(e.tenantId), i;
|
|
154
|
+
}),
|
|
155
|
+
expireOpenCheckouts: (e) => h.sync(() => {
|
|
156
|
+
F.push(e.providerCustomerId);
|
|
157
|
+
let t = 0;
|
|
158
|
+
for (let [n, r] of M) r.receipt.providerCustomerId === e.providerCustomerId && n !== e.exceptOperationId && N(r.receipt).status === "open" && (r.receipt = {
|
|
159
|
+
...r.receipt,
|
|
160
|
+
status: "expired",
|
|
161
|
+
url: null
|
|
162
|
+
}, t++);
|
|
163
|
+
return t;
|
|
164
|
+
}),
|
|
165
|
+
listSubscription: (e) => {
|
|
166
|
+
S.set(e.providerSubscriptionId, e), x.set(e.providerSubscriptionId, e);
|
|
167
|
+
},
|
|
168
|
+
setSubscription: (e, t) => {
|
|
169
|
+
t === null ? x.delete(e) : x.set(e, t);
|
|
170
|
+
},
|
|
171
|
+
setCustomerEmail: (e, t) => {
|
|
172
|
+
t === null ? C.delete(e) : C.set(e, t);
|
|
173
|
+
},
|
|
174
|
+
fetchSubscriptionSnapshot: (e) => h.sync(() => {
|
|
175
|
+
b.push(e);
|
|
176
|
+
let t = S.get(e), n = x.get(e);
|
|
177
|
+
return t === void 0 || n === void 0 ? null : {
|
|
178
|
+
...t,
|
|
179
|
+
...n
|
|
180
|
+
};
|
|
181
|
+
}),
|
|
182
|
+
fetchSubscription: (e) => h.sync(() => (b.push(e), x.get(e) ?? null)),
|
|
183
|
+
listSubscriptions: (e) => h.sync(() => [...S.values()].filter((t) => t.status !== "canceled" && (e.providerCustomerId === void 0 || t.providerCustomerId === e.providerCustomerId))),
|
|
184
|
+
prepareSubscriptionClose: () => h.sync(() => ({ cancelAt: /* @__PURE__ */ new Date(Math.ceil(j().getTime() / 1e3) * 1e3 + 6e5) })),
|
|
185
|
+
cancelSubscription: (t) => h.try({
|
|
186
|
+
try: () => {
|
|
187
|
+
D.push({
|
|
188
|
+
providerSubscriptionId: t.providerSubscriptionId,
|
|
189
|
+
mode: t.mode
|
|
190
|
+
});
|
|
191
|
+
let e = x.get(t.providerSubscriptionId);
|
|
192
|
+
if (e?.status === "canceled") return e;
|
|
193
|
+
if (t.mode === "periodEnd") return z(t.providerSubscriptionId, { cancelAt: e?.currentPeriodEnd ?? /* @__PURE__ */ new Date(0) });
|
|
194
|
+
let n = t.closeOperation.cancelAt;
|
|
195
|
+
if (!t.closeOperation.id || !Number.isFinite(n.getTime())) throw Error("A retained close operation is required.");
|
|
196
|
+
return e?.cancelAt !== null && e?.cancelAt !== void 0 && e.cancelAt.getTime() <= n.getTime() ? e : z(t.providerSubscriptionId, {
|
|
197
|
+
cancelAt: n,
|
|
198
|
+
...j().getTime() >= n.getTime() ? { status: "canceled" } : {}
|
|
199
|
+
});
|
|
200
|
+
},
|
|
201
|
+
catch: () => new e({
|
|
202
|
+
source: "mock",
|
|
203
|
+
message: "A valid retained billing close operation is required.",
|
|
204
|
+
transient: !1
|
|
205
|
+
})
|
|
206
|
+
}),
|
|
207
|
+
resumeSubscription: (e) => h.sync(() => (O.push(e), z(e, { cancelAt: null }))),
|
|
208
|
+
addSubscriptionPrices: (e) => h.sync(() => {
|
|
209
|
+
k.push({
|
|
210
|
+
providerSubscriptionId: e.providerSubscriptionId,
|
|
211
|
+
priceIds: e.priceIds
|
|
212
|
+
});
|
|
213
|
+
let t = S.get(e.providerSubscriptionId);
|
|
214
|
+
t !== void 0 && z(e.providerSubscriptionId, { priceIds: [...t.priceIds, ...e.priceIds] });
|
|
215
|
+
}),
|
|
216
|
+
customerEmail: (e) => h.sync(() => C.get(e) ?? null),
|
|
217
|
+
createCheckoutSession: (t) => h.try({
|
|
218
|
+
try: () => {
|
|
219
|
+
let e = t.idempotencyKey ?? "mock_checkout_" + globalThis.crypto.randomUUID(), n = JSON.stringify([
|
|
220
|
+
t.tenantId,
|
|
221
|
+
t.priceId,
|
|
222
|
+
t.meteredPriceIds ?? [],
|
|
223
|
+
t.successUrl,
|
|
224
|
+
t.cancelUrl,
|
|
225
|
+
t.providerCustomerId ?? null,
|
|
226
|
+
t.quantity ?? 1,
|
|
227
|
+
t.adjustableQuantity ?? null,
|
|
228
|
+
t.trialDays ?? null,
|
|
229
|
+
t.expiresAt?.toISOString() ?? null
|
|
230
|
+
]), r = M.get(e);
|
|
231
|
+
if (r) {
|
|
232
|
+
if (r.fingerprint !== n) throw Error("A checkout operation cannot change its provider parameters.");
|
|
233
|
+
return N(r.receipt);
|
|
234
|
+
}
|
|
235
|
+
let i = t.expiresAt ?? new Date(j().getTime() + 36e5);
|
|
236
|
+
if (!Number.isFinite(i.getTime()) || i.getTime() <= j().getTime()) throw Error("Checkout deadline has passed.");
|
|
237
|
+
let a = {
|
|
238
|
+
id: "mock_cs_" + e,
|
|
239
|
+
operationId: t.idempotencyKey ?? null,
|
|
240
|
+
tenantId: t.tenantId,
|
|
241
|
+
providerCustomerId: t.providerCustomerId ?? null,
|
|
242
|
+
providerSubscriptionId: null,
|
|
243
|
+
status: "open",
|
|
244
|
+
expiresAt: i,
|
|
245
|
+
url: _ + "/checkout?tenant=" + encodeURIComponent(t.tenantId) + "&price=" + encodeURIComponent(t.priceId) + "&operation=" + encodeURIComponent(e)
|
|
246
|
+
};
|
|
247
|
+
return M.set(e, {
|
|
248
|
+
fingerprint: n,
|
|
249
|
+
receipt: a
|
|
250
|
+
}), A.push(t), a;
|
|
251
|
+
},
|
|
252
|
+
catch: () => new e({
|
|
253
|
+
source: "mock",
|
|
254
|
+
message: "Checkout identity, parameters or deadline could not be confirmed.",
|
|
255
|
+
transient: !1
|
|
256
|
+
})
|
|
257
|
+
}),
|
|
258
|
+
findCheckoutSession: (t) => h.try({
|
|
259
|
+
try: () => {
|
|
260
|
+
let e = M.get(t.operationId);
|
|
261
|
+
if (!e) return {
|
|
262
|
+
receipt: null,
|
|
263
|
+
observedAt: j()
|
|
264
|
+
};
|
|
265
|
+
let n = N(e.receipt);
|
|
266
|
+
if (n.tenantId !== t.tenantId || t.providerCustomerId !== void 0 && n.providerCustomerId !== t.providerCustomerId) throw Error("Checkout receipt owner mismatch.");
|
|
267
|
+
return {
|
|
268
|
+
receipt: n,
|
|
269
|
+
observedAt: j()
|
|
270
|
+
};
|
|
271
|
+
},
|
|
272
|
+
catch: () => new e({
|
|
273
|
+
source: "mock",
|
|
274
|
+
message: "Checkout receipt authority could not be confirmed.",
|
|
275
|
+
transient: !1
|
|
276
|
+
})
|
|
277
|
+
}),
|
|
278
|
+
createPortalSession: (e) => h.succeed({ url: `${_}/portal?customer=${encodeURIComponent(e.providerCustomerId)}` }),
|
|
279
|
+
reportUsage: (e) => h.sync(() => {
|
|
280
|
+
v.push(e);
|
|
281
|
+
}),
|
|
282
|
+
updateSubscription: (e) => h.sync(() => (y.push(e), {
|
|
283
|
+
plan: e.priceId ?? "mock-plan",
|
|
284
|
+
quantity: e.quantity ?? 1,
|
|
285
|
+
prorationMinor: 0,
|
|
286
|
+
currency: "usd",
|
|
287
|
+
currentPeriodStart: null,
|
|
288
|
+
currentPeriodEnd: null
|
|
289
|
+
})),
|
|
290
|
+
previewSubscriptionChange: (e) => h.succeed({
|
|
291
|
+
plan: e.priceId ?? "mock-plan",
|
|
292
|
+
quantity: e.quantity ?? 1,
|
|
293
|
+
prorationMinor: 0,
|
|
294
|
+
currency: "usd",
|
|
295
|
+
currentPeriodStart: null,
|
|
296
|
+
currentPeriodEnd: null
|
|
297
|
+
}),
|
|
298
|
+
listInvoiceHistory: E,
|
|
299
|
+
listInvoices: (e) => E({
|
|
300
|
+
account: p,
|
|
301
|
+
providerCustomerId: e.providerCustomerId,
|
|
302
|
+
limit: e.limit ?? 20
|
|
303
|
+
}).pipe(h.map((e) => e.invoices.map(d))),
|
|
304
|
+
normalizeEvent: (e) => h.succeed(B(e) ? e : null)
|
|
305
|
+
};
|
|
306
|
+
}, _ = /* @__PURE__ */ new Set([
|
|
307
|
+
"",
|
|
308
|
+
"development",
|
|
309
|
+
"dev",
|
|
310
|
+
"test"
|
|
311
|
+
]), v = (e) => !_.has((e ?? "").trim().toLowerCase()), y = (e) => {
|
|
312
|
+
if (v(e.env.NODE_ENV)) {
|
|
313
|
+
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.");
|
|
314
|
+
if (e.resolvedName === "stripe") {
|
|
315
|
+
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.");
|
|
316
|
+
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.");
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}, b = (e) => {
|
|
320
|
+
let { provider: t, env: n } = e;
|
|
321
|
+
if (t !== void 0 && typeof t == "object") return t;
|
|
322
|
+
if ((t ?? (n.STRIPE_SECRET_KEY ? "stripe" : "mock")) === "stripe") {
|
|
323
|
+
let t = e.apiKey ?? n.STRIPE_SECRET_KEY;
|
|
324
|
+
if (!t) throw Error("billingPlugin: provider 'stripe' needs an apiKey (option or STRIPE_SECRET_KEY env)");
|
|
325
|
+
return m({
|
|
326
|
+
apiKey: t,
|
|
327
|
+
...e.planForPriceId ? { planForPriceId: e.planForPriceId } : {}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return g();
|
|
331
|
+
}, x = (e) => e.name === "mock" ? [] : [{
|
|
332
|
+
destination: e.name === "stripe" ? "api.stripe.com" : `runtime:the '${e.name}' billing provider`,
|
|
333
|
+
purpose: "customers, subscriptions, checkout and metered usage",
|
|
334
|
+
data: ["personal", "metadata"],
|
|
335
|
+
disable: "without STRIPE_SECRET_KEY the mock provider answers, and nothing leaves (production refuses to boot on it)"
|
|
336
|
+
}];
|
|
337
|
+
//#endregion
|
|
338
|
+
export { f as STRIPE_WEBHOOK_EVENTS, y as assertBillingProductionReady, x as billingEgressOf, v as isProductionBoot, g as mockProvider, p as normalizeStripeEvent, b as resolveProvider, m as stripeProvider };
|