@voltro/plugin-billing 0.84.0 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1215 -0
- package/README.md +7 -0
- package/THIRD-PARTY-NOTICES.md +118 -600
- package/dist/catalog.js +471 -0
- package/dist/dunning.js +209 -0
- package/dist/entitlement.js +41 -0
- package/dist/flushClaim.js +20 -0
- package/dist/index.d.ts +19 -24
- package/dist/index.js +158 -2776
- package/dist/metering.js +74 -0
- package/dist/plans.js +24 -0
- package/dist/providers.js +133 -0
- package/dist/provision.js +153 -0
- package/dist/rpc.d.ts +72 -73
- package/dist/service.js +615 -0
- package/dist/stores.js +425 -0
- package/dist/stripeClient.js +53 -0
- package/dist/stripeProvider.js +352 -0
- package/dist/stripeRest.js +52 -0
- package/dist/table.js +54 -0
- package/dist/types.d.ts +19 -24
- package/dist/types.js +1 -1
- package/dist/webhook.js +45 -0
- package/package.json +7 -7
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,133 @@
|
|
|
1
|
+
import { STRIPE_WEBHOOK_EVENTS as e, normalizeStripeEvent as t, stripeProvider as n } from "./stripeProvider.js";
|
|
2
|
+
import { Effect as r } from "effect";
|
|
3
|
+
//#region src/providers.ts
|
|
4
|
+
var i = (e) => {
|
|
5
|
+
let t = e?.baseUrl ?? "https://mock.billing.local", n = [], i = [], a = [], o = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map(), c = /* @__PURE__ */ new Map(), l = [], u = [], d = [], f = [], p = [], m = [], h = /* @__PURE__ */ new Map(), g = [], _ = 0, v = (e, t) => {
|
|
6
|
+
let n = {
|
|
7
|
+
...o.get(e) ?? {
|
|
8
|
+
status: "active",
|
|
9
|
+
plan: null,
|
|
10
|
+
quantity: 1,
|
|
11
|
+
currentPeriodStart: null,
|
|
12
|
+
currentPeriodEnd: null,
|
|
13
|
+
cancelAt: null
|
|
14
|
+
},
|
|
15
|
+
...t
|
|
16
|
+
};
|
|
17
|
+
o.set(e, n);
|
|
18
|
+
let r = s.get(e);
|
|
19
|
+
return r !== void 0 && s.set(e, {
|
|
20
|
+
...r,
|
|
21
|
+
...t
|
|
22
|
+
}), n;
|
|
23
|
+
}, y = (e) => typeof e == "object" && !!e && typeof e._tag == "string";
|
|
24
|
+
return {
|
|
25
|
+
name: "mock",
|
|
26
|
+
supportsMeteredUsage: e?.supportsMeteredUsage ?? !0,
|
|
27
|
+
pushed: n,
|
|
28
|
+
updates: i,
|
|
29
|
+
fetched: a,
|
|
30
|
+
cancels: l,
|
|
31
|
+
resumes: u,
|
|
32
|
+
priceAdds: d,
|
|
33
|
+
checkouts: f,
|
|
34
|
+
customersCreated: p,
|
|
35
|
+
expiredFor: m,
|
|
36
|
+
setUndelivered: (e) => {
|
|
37
|
+
g = e;
|
|
38
|
+
},
|
|
39
|
+
undeliveredReads: () => _,
|
|
40
|
+
listUndeliveredEvents: () => r.sync(() => (_ += 1, g)),
|
|
41
|
+
ensureCustomer: (e) => r.sync(() => {
|
|
42
|
+
let t = h.get(e.tenantId);
|
|
43
|
+
if (t !== void 0) return t;
|
|
44
|
+
let n = `mock_cus_${e.tenantId}`;
|
|
45
|
+
return h.set(e.tenantId, n), p.push(e.tenantId), n;
|
|
46
|
+
}),
|
|
47
|
+
expireOpenCheckouts: (e) => r.sync(() => (m.push(e.providerCustomerId), 0)),
|
|
48
|
+
listSubscription: (e) => {
|
|
49
|
+
s.set(e.providerSubscriptionId, e), o.set(e.providerSubscriptionId, e);
|
|
50
|
+
},
|
|
51
|
+
setSubscription: (e, t) => {
|
|
52
|
+
t === null ? o.delete(e) : o.set(e, t);
|
|
53
|
+
},
|
|
54
|
+
setCustomerEmail: (e, t) => {
|
|
55
|
+
t === null ? c.delete(e) : c.set(e, t);
|
|
56
|
+
},
|
|
57
|
+
fetchSubscription: (e) => r.sync(() => (a.push(e), o.get(e) ?? null)),
|
|
58
|
+
listSubscriptions: (e) => r.sync(() => [...s.values()].filter((t) => t.status !== "canceled" && (e.providerCustomerId === void 0 || t.providerCustomerId === e.providerCustomerId))),
|
|
59
|
+
cancelSubscription: (e) => r.sync(() => {
|
|
60
|
+
l.push({
|
|
61
|
+
providerSubscriptionId: e.providerSubscriptionId,
|
|
62
|
+
mode: e.mode
|
|
63
|
+
});
|
|
64
|
+
let t = o.get(e.providerSubscriptionId);
|
|
65
|
+
return e.mode === "close" ? v(e.providerSubscriptionId, { cancelAt: new Date(Date.now() + 6e5) }) : v(e.providerSubscriptionId, { cancelAt: t?.currentPeriodEnd ?? /* @__PURE__ */ new Date(0) });
|
|
66
|
+
}),
|
|
67
|
+
resumeSubscription: (e) => r.sync(() => (u.push(e), v(e, { cancelAt: null }))),
|
|
68
|
+
addSubscriptionPrices: (e) => r.sync(() => {
|
|
69
|
+
d.push({
|
|
70
|
+
providerSubscriptionId: e.providerSubscriptionId,
|
|
71
|
+
priceIds: e.priceIds
|
|
72
|
+
});
|
|
73
|
+
let t = s.get(e.providerSubscriptionId);
|
|
74
|
+
t !== void 0 && v(e.providerSubscriptionId, { priceIds: [...t.priceIds, ...e.priceIds] });
|
|
75
|
+
}),
|
|
76
|
+
customerEmail: (e) => r.sync(() => c.get(e) ?? null),
|
|
77
|
+
createCheckoutSession: (e) => r.sync(() => (f.push(e), { url: `${t}/checkout?tenant=${encodeURIComponent(e.tenantId)}&price=${encodeURIComponent(e.priceId)}` })),
|
|
78
|
+
createPortalSession: (e) => r.succeed({ url: `${t}/portal?customer=${encodeURIComponent(e.providerCustomerId)}` }),
|
|
79
|
+
reportUsage: (e) => r.sync(() => {
|
|
80
|
+
n.push(e);
|
|
81
|
+
}),
|
|
82
|
+
updateSubscription: (e) => r.sync(() => (i.push(e), {
|
|
83
|
+
plan: e.priceId ?? "mock-plan",
|
|
84
|
+
quantity: e.quantity ?? 1,
|
|
85
|
+
prorationMinor: 0,
|
|
86
|
+
currency: "usd",
|
|
87
|
+
currentPeriodStart: null,
|
|
88
|
+
currentPeriodEnd: null
|
|
89
|
+
})),
|
|
90
|
+
previewSubscriptionChange: (e) => r.succeed({
|
|
91
|
+
plan: e.priceId ?? "mock-plan",
|
|
92
|
+
quantity: e.quantity ?? 1,
|
|
93
|
+
prorationMinor: 0,
|
|
94
|
+
currency: "usd",
|
|
95
|
+
currentPeriodStart: null,
|
|
96
|
+
currentPeriodEnd: null
|
|
97
|
+
}),
|
|
98
|
+
listInvoices: () => r.succeed([]),
|
|
99
|
+
normalizeEvent: (e) => r.succeed(y(e) ? e : null)
|
|
100
|
+
};
|
|
101
|
+
}, a = /* @__PURE__ */ new Set([
|
|
102
|
+
"",
|
|
103
|
+
"development",
|
|
104
|
+
"dev",
|
|
105
|
+
"test"
|
|
106
|
+
]), o = (e) => !a.has((e ?? "").trim().toLowerCase()), s = (e) => {
|
|
107
|
+
if (o(e.env.NODE_ENV)) {
|
|
108
|
+
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.");
|
|
109
|
+
if (e.resolvedName === "stripe") {
|
|
110
|
+
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.");
|
|
111
|
+
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.");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}, c = (e) => {
|
|
115
|
+
let { provider: t, env: r } = e;
|
|
116
|
+
if (t !== void 0 && typeof t == "object") return t;
|
|
117
|
+
if ((t ?? (r.STRIPE_SECRET_KEY ? "stripe" : "mock")) === "stripe") {
|
|
118
|
+
let t = e.apiKey ?? r.STRIPE_SECRET_KEY;
|
|
119
|
+
if (!t) throw Error("billingPlugin: provider 'stripe' needs an apiKey (option or STRIPE_SECRET_KEY env)");
|
|
120
|
+
return n({
|
|
121
|
+
apiKey: t,
|
|
122
|
+
...e.planForPriceId ? { planForPriceId: e.planForPriceId } : {}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return i();
|
|
126
|
+
}, l = (e) => e.name === "mock" ? [] : [{
|
|
127
|
+
destination: e.name === "stripe" ? "api.stripe.com" : `runtime:the '${e.name}' billing provider`,
|
|
128
|
+
purpose: "customers, subscriptions, checkout and metered usage",
|
|
129
|
+
data: ["personal", "metadata"],
|
|
130
|
+
disable: "without STRIPE_SECRET_KEY the mock provider answers, and nothing leaves (production refuses to boot on it)"
|
|
131
|
+
}];
|
|
132
|
+
//#endregion
|
|
133
|
+
export { e as STRIPE_WEBHOOK_EVENTS, s as assertBillingProductionReady, l as billingEgressOf, o as isProductionBoot, i as mockProvider, t as normalizeStripeEvent, c as resolveProvider, n as stripeProvider };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { diffMeter as e, diffPortal as t, diffPrice as n, diffProduct as r, diffTaxSettings as i, diffWebhook as a, sellingReadiness as o } from "./catalog.js";
|
|
2
|
+
import { redactStripeKeys as s, stripeRest as c } from "./stripeRest.js";
|
|
3
|
+
import { execFileSync as l } from "node:child_process";
|
|
4
|
+
import { chmodSync as u, existsSync as d, readFileSync as f, realpathSync as p, writeFileSync as m } from "node:fs";
|
|
5
|
+
import { dirname as h, resolve as g } from "node:path";
|
|
6
|
+
//#region src/provision.ts
|
|
7
|
+
var _ = (e) => e.filter((e) => e.applied !== !0 && (e.kind === "create" || e.kind === "update" || e.kind === "adopt" || e.kind === "drift")), v = async (s, c, l) => {
|
|
8
|
+
let u = c.environments[l.env], d = await s.get("/v1/tax/settings"), f = l.env === "live";
|
|
9
|
+
if (d.livemode !== f) throw Error(`the key reaches a ${d.livemode ? "LIVE" : "test"} account, not ${l.env}`);
|
|
10
|
+
let p = [], m = {}, h = async (e, t) => {
|
|
11
|
+
let n = "apply" in e ? e.apply : void 0;
|
|
12
|
+
if (l.apply && n !== void 0) {
|
|
13
|
+
let r = await s.post(n.path, n.params);
|
|
14
|
+
t?.(r), p.push({
|
|
15
|
+
...e,
|
|
16
|
+
applied: !0
|
|
17
|
+
});
|
|
18
|
+
} else p.push(e);
|
|
19
|
+
}, g = await s.list("/v1/billing/meters"), _ = /* @__PURE__ */ new Map();
|
|
20
|
+
for (let t of c.meters) {
|
|
21
|
+
let n = e(t, g);
|
|
22
|
+
_.set(t.eventName, n.id), await h(n.finding, (e) => {
|
|
23
|
+
_.set(t.eventName, e.id);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
let v = await s.list("/v1/products"), y = [];
|
|
27
|
+
for (let e of c.products) {
|
|
28
|
+
let t = r(e, v), n = {
|
|
29
|
+
inPortal: e.inPortal,
|
|
30
|
+
productId: t.id,
|
|
31
|
+
priceId: null
|
|
32
|
+
};
|
|
33
|
+
await h(t.finding, (e) => {
|
|
34
|
+
n.productId = e.id;
|
|
35
|
+
}), y.push(n);
|
|
36
|
+
}
|
|
37
|
+
for (let [e, t] of c.products.entries()) {
|
|
38
|
+
let r = y[e], i = t.price.meter;
|
|
39
|
+
if (t.price.usageType === "metered" && (i === void 0 || !_.has(i))) {
|
|
40
|
+
p.push({
|
|
41
|
+
kind: "manual",
|
|
42
|
+
object: `price ${t.price.lookupKey}`,
|
|
43
|
+
detail: `metered, but names no catalog meter (${i ?? "none"})`
|
|
44
|
+
});
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
let a = i === void 0 ? null : _.get(i) ?? null, o = r.productId ?? (l.apply ? null : `pending:${t.key}`), c = a ?? (l.apply || t.price.usageType !== "metered" ? a : `pending:${i}`), u = (await s.list("/v1/prices", { lookup_keys: [t.price.lookupKey] }))[0] ?? null, d = r.productId === null ? [] : await s.list("/v1/prices", { product: r.productId }), f = n(t.price, o, c, u, d, l.allowPriceChange);
|
|
48
|
+
r.priceId = f.id, await h(f.finding, (e) => {
|
|
49
|
+
r.priceId = e.id;
|
|
50
|
+
}), r.priceId !== null && (m[t.price.envName] = r.priceId);
|
|
51
|
+
}
|
|
52
|
+
await h(i(c.tax, d));
|
|
53
|
+
let b = y.filter((e) => e.inPortal), x = b.every((e) => e.productId !== null && e.priceId !== null) ? b.map((e) => ({
|
|
54
|
+
productId: e.productId,
|
|
55
|
+
priceId: e.priceId
|
|
56
|
+
})) : null, S = await s.list("/v1/billing_portal/configurations", { expand: ["data.features.subscription_update.products"] });
|
|
57
|
+
for (let e of t(c.portal, u, x, S)) await h(e);
|
|
58
|
+
let C = a(u, await s.list("/v1/webhook_endpoints"));
|
|
59
|
+
C.kind === "create" && l.apply && l.envOut === null ? p.push({
|
|
60
|
+
kind: "manual",
|
|
61
|
+
object: C.object,
|
|
62
|
+
detail: "not created: pass --env-out so its signing secret is captured (Stripe shows it once)"
|
|
63
|
+
}) : await h(C, (e) => {
|
|
64
|
+
typeof e.secret == "string" && (m.STRIPE_WEBHOOK_SECRET = e.secret);
|
|
65
|
+
});
|
|
66
|
+
let w = await s.get("/v1/account").catch(() => null), T = await s.list("/v1/tax_ids", { owner: { type: "account" } }).catch(() => null), E = await s.list("/v1/tax/registrations", { status: "active" }).catch(() => []);
|
|
67
|
+
return p.push(...o({
|
|
68
|
+
catalog: c,
|
|
69
|
+
env: u,
|
|
70
|
+
chargesEnabled: w === null ? null : w.charges_enabled === !0,
|
|
71
|
+
accountTaxIds: T === null ? null : T.length,
|
|
72
|
+
activeRegistrations: E.map((e) => e.country)
|
|
73
|
+
})), {
|
|
74
|
+
livemode: d.livemode,
|
|
75
|
+
findings: p,
|
|
76
|
+
envOut: m
|
|
77
|
+
};
|
|
78
|
+
}, y = (e, t) => {
|
|
79
|
+
let n = new Set(Object.keys(t)), r = e.split("\n").filter((e) => !n.has(e.split("=")[0] ?? ""));
|
|
80
|
+
for (; r.length > 0 && r[r.length - 1] === "";) r.pop();
|
|
81
|
+
return [
|
|
82
|
+
...r,
|
|
83
|
+
...Object.entries(t).map(([e, t]) => `${e}=${t}`),
|
|
84
|
+
""
|
|
85
|
+
].join("\n");
|
|
86
|
+
}, b = (e, t) => {
|
|
87
|
+
try {
|
|
88
|
+
return l("git", [...e], {
|
|
89
|
+
cwd: t,
|
|
90
|
+
stdio: "ignore"
|
|
91
|
+
}), !0;
|
|
92
|
+
} catch {
|
|
93
|
+
return !1;
|
|
94
|
+
}
|
|
95
|
+
}, x = (e, t = b, n = (e) => d(e) ? p(e) : e) => {
|
|
96
|
+
for (let r of /* @__PURE__ */ new Set([g(e), g(n(g(e)))])) {
|
|
97
|
+
let n = h(r);
|
|
98
|
+
if (t(["rev-parse", "--is-inside-work-tree"], n) && !t([
|
|
99
|
+
"check-ignore",
|
|
100
|
+
"-q",
|
|
101
|
+
r
|
|
102
|
+
], n)) return `${e} is inside a git work tree and not ignored — write it to a gitignored file or outside the repo`;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}, S = (e) => {
|
|
106
|
+
let t = (t) => {
|
|
107
|
+
let n = e.indexOf(t);
|
|
108
|
+
return n < 0 ? null : e[n + 1];
|
|
109
|
+
}, n = t("--env");
|
|
110
|
+
if (n !== "sandbox" && n !== "live") return "--env sandbox|live is required";
|
|
111
|
+
let r = t("--env-out");
|
|
112
|
+
if (r === void 0 || r?.startsWith("--")) return "--env-out needs a file path";
|
|
113
|
+
let i = {
|
|
114
|
+
env: n,
|
|
115
|
+
apply: e.includes("--apply"),
|
|
116
|
+
allowPriceChange: e.includes("--allow-price-change"),
|
|
117
|
+
confirmLive: e.includes("--confirm-live"),
|
|
118
|
+
strict: e.includes("--strict"),
|
|
119
|
+
envOut: r
|
|
120
|
+
};
|
|
121
|
+
return i.env === "live" && i.apply && !i.confirmLive ? "writing to LIVE needs --confirm-live (run without --apply first to see the plan)" : i;
|
|
122
|
+
}, C = (e, t, n) => {
|
|
123
|
+
let r = t.STRIPE_PROVISION_KEY?.trim() || (e === "sandbox" ? n?.trim() : void 0);
|
|
124
|
+
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 };
|
|
125
|
+
}, w = (e, t) => d(e) ? f(e, "utf8").split("\n").find((e) => e.startsWith(`${t}=`))?.slice(t.length + 1).trim() : void 0, T = async (e, t, n = {}) => {
|
|
126
|
+
let r = n.vars ?? process.env, i = (e) => (console.error(`stripe provision: ${e}`), 2), a = S(t);
|
|
127
|
+
if (typeof a == "string") return i(a);
|
|
128
|
+
if (a.envOut !== null) {
|
|
129
|
+
let e = x(a.envOut);
|
|
130
|
+
if (e !== null) return i(e);
|
|
131
|
+
}
|
|
132
|
+
let o = C(a.env, r, n.dotenv === void 0 ? void 0 : w(n.dotenv, "STRIPE_SECRET_KEY"));
|
|
133
|
+
if ("refusal" in o) return i(o.refusal);
|
|
134
|
+
let l;
|
|
135
|
+
try {
|
|
136
|
+
l = await v((n.rest ?? c)(o.key), e, a);
|
|
137
|
+
} catch (e) {
|
|
138
|
+
return console.error(`stripe provision: ${s(e instanceof Error ? e.message : String(e))}`), 1;
|
|
139
|
+
}
|
|
140
|
+
console.log(`Stripe ${a.env} (${l.livemode ? "LIVE" : "test mode"}) — ${a.apply ? "apply" : "check"}`);
|
|
141
|
+
for (let e of l.findings) {
|
|
142
|
+
let t = e.applied === !0 ? `${e.kind} ✓` : e.kind;
|
|
143
|
+
console.log(` ${t.padEnd(10)} ${e.object}${"detail" in e ? ` — ${e.detail}` : ""}`);
|
|
144
|
+
}
|
|
145
|
+
if (a.envOut !== null) {
|
|
146
|
+
let e = a.envOut;
|
|
147
|
+
m(e, y(d(e) ? f(e, "utf8") : "", l.envOut), { mode: 384 }), u(e, 384), console.log(` wrote ${Object.keys(l.envOut).sort().join(", ")} to ${e}`);
|
|
148
|
+
}
|
|
149
|
+
let p = _(l.findings), h = l.findings.filter((e) => e.kind === "manual");
|
|
150
|
+
return console.log(` ${p.length} open, ${h.length} manual`), p.length > 0 || a.strict && h.length > 0 ? 1 : 0;
|
|
151
|
+
};
|
|
152
|
+
//#endregion
|
|
153
|
+
export { x as envOutRefusal, y as mergeEnvFile, _ as openFindings, S as parseProvisionArgs, v as provisionStripeCatalog, C as resolveProvisionKey, T as runStripeProvision };
|
package/dist/rpc.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { ActionProcedureDescriptor } from '@voltro/protocol';
|
|
|
2
2
|
import { PluginRpcClientDescriptor } from '@voltro/protocol';
|
|
3
3
|
import { QueryProcedureDescriptor } from '@voltro/protocol';
|
|
4
4
|
import { Schema } from 'effect';
|
|
5
|
+
import { YieldableError } from 'effect/Cause';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* A billing failure. `transient: true` marks failures the service retries
|
|
@@ -13,15 +14,13 @@ import { Schema } from 'effect';
|
|
|
13
14
|
declare class BillingError extends BillingError_base {
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
declare const BillingError_base: Schema.
|
|
17
|
-
readonly _tag: Schema.tag<"BillingError">;
|
|
18
|
-
} & {
|
|
17
|
+
declare const BillingError_base: Schema.Class<BillingError, Schema.TaggedStruct<"BillingError", {
|
|
19
18
|
/** Where the failure originated — provider name or an internal stage. */
|
|
20
|
-
source:
|
|
21
|
-
message:
|
|
19
|
+
readonly source: Schema.String;
|
|
20
|
+
readonly message: Schema.String;
|
|
22
21
|
/** Retryable? The service retries `transient` failures on a Schedule. */
|
|
23
|
-
transient:
|
|
24
|
-
}>;
|
|
22
|
+
readonly transient: Schema.Boolean;
|
|
23
|
+
}>, YieldableError>;
|
|
25
24
|
|
|
26
25
|
/** Declared for the codegen (`VoltroPlugin.rpcClientDescriptors`) so each tag is
|
|
27
26
|
* emitted into the generated client group. Kept in lockstep with the exports. */
|
|
@@ -30,33 +29,33 @@ export declare const billingRpcClientImports: ReadonlyArray<PluginRpcClientDescr
|
|
|
30
29
|
/** The customer's own cancellation: active until the paid period ends, then
|
|
31
30
|
* ended. `cancelAt` on the answer is when. */
|
|
32
31
|
export declare const cancelSubscriptionDescriptor: ActionProcedureDescriptor<"billing.cancel", Schema.Struct<{}>, Schema.Struct<{
|
|
33
|
-
subscription: Schema.Struct<{
|
|
34
|
-
plan:
|
|
35
|
-
status:
|
|
36
|
-
quantity:
|
|
37
|
-
currentPeriodEnd: Schema.NullOr<
|
|
38
|
-
cancelAt: Schema.NullOr<
|
|
32
|
+
readonly subscription: Schema.Struct<{
|
|
33
|
+
readonly plan: Schema.String;
|
|
34
|
+
readonly status: Schema.String;
|
|
35
|
+
readonly quantity: Schema.Number;
|
|
36
|
+
readonly currentPeriodEnd: Schema.NullOr<Schema.String>;
|
|
37
|
+
readonly cancelAt: Schema.NullOr<Schema.String>;
|
|
39
38
|
}>;
|
|
40
39
|
}>, typeof BillingError>;
|
|
41
40
|
|
|
42
41
|
export declare const changePlanDescriptor: ActionProcedureDescriptor<"billing.changePlan", Schema.Struct<{
|
|
43
|
-
plan:
|
|
42
|
+
readonly plan: Schema.String;
|
|
44
43
|
}>, Schema.Struct<{
|
|
45
|
-
plan:
|
|
46
|
-
quantity:
|
|
44
|
+
readonly plan: Schema.String;
|
|
45
|
+
readonly quantity: Schema.Number;
|
|
47
46
|
/** Integer minor-unit settlement: > 0 charge, < 0 credit, 0 at boundary. */
|
|
48
|
-
prorationMinor:
|
|
49
|
-
currency:
|
|
47
|
+
readonly prorationMinor: Schema.Number;
|
|
48
|
+
readonly currency: Schema.String;
|
|
50
49
|
}>, typeof BillingError>;
|
|
51
50
|
|
|
52
51
|
export declare const changeSeatsDescriptor: ActionProcedureDescriptor<"billing.changeSeats", Schema.Struct<{
|
|
53
|
-
quantity:
|
|
52
|
+
readonly quantity: Schema.Number;
|
|
54
53
|
}>, Schema.Struct<{
|
|
55
|
-
plan:
|
|
56
|
-
quantity:
|
|
54
|
+
readonly plan: Schema.String;
|
|
55
|
+
readonly quantity: Schema.Number;
|
|
57
56
|
/** Integer minor-unit settlement: > 0 charge, < 0 credit, 0 at boundary. */
|
|
58
|
-
prorationMinor:
|
|
59
|
-
currency:
|
|
57
|
+
readonly prorationMinor: Schema.Number;
|
|
58
|
+
readonly currency: Schema.String;
|
|
60
59
|
}>, typeof BillingError>;
|
|
61
60
|
|
|
62
61
|
/**
|
|
@@ -67,85 +66,85 @@ export declare const changeSeatsDescriptor: ActionProcedureDescriptor<"billing.c
|
|
|
67
66
|
* would fail encode at the boundary).
|
|
68
67
|
*/
|
|
69
68
|
export declare const entitlementStatusDescriptor: QueryProcedureDescriptor<"billing.entitlementStatus", Schema.Struct<{}>, Schema.Struct<{
|
|
70
|
-
plan:
|
|
71
|
-
billedPlan:
|
|
72
|
-
status:
|
|
73
|
-
entitled:
|
|
74
|
-
inGrace:
|
|
75
|
-
graceEndsAt: Schema.NullOr<
|
|
76
|
-
lockedSince: Schema.NullOr<
|
|
77
|
-
lockout:
|
|
69
|
+
readonly plan: Schema.String;
|
|
70
|
+
readonly billedPlan: Schema.String;
|
|
71
|
+
readonly status: Schema.String;
|
|
72
|
+
readonly entitled: Schema.Boolean;
|
|
73
|
+
readonly inGrace: Schema.Boolean;
|
|
74
|
+
readonly graceEndsAt: Schema.NullOr<Schema.String>;
|
|
75
|
+
readonly lockedSince: Schema.NullOr<Schema.String>;
|
|
76
|
+
readonly lockout: Schema.String;
|
|
78
77
|
}>, typeof BillingError>;
|
|
79
78
|
|
|
80
79
|
/** The provider's invoice history. `hostedUrl` / `pdfUrl` are the provider's
|
|
81
80
|
* own pages — we never render an invoice ourselves. */
|
|
82
81
|
export declare const invoicesDescriptor: QueryProcedureDescriptor<"billing.invoices", Schema.Struct<{}>, Schema.Struct<{
|
|
83
|
-
invoices: Schema
|
|
84
|
-
providerInvoiceId:
|
|
85
|
-
amountMinor:
|
|
86
|
-
currency:
|
|
87
|
-
status:
|
|
88
|
-
createdAt: Schema.NullOr<
|
|
89
|
-
hostedUrl: Schema.NullOr<
|
|
90
|
-
pdfUrl: Schema.NullOr<
|
|
82
|
+
readonly invoices: Schema.$Array<Schema.Struct<{
|
|
83
|
+
readonly providerInvoiceId: Schema.String;
|
|
84
|
+
readonly amountMinor: Schema.Number;
|
|
85
|
+
readonly currency: Schema.String;
|
|
86
|
+
readonly status: Schema.String;
|
|
87
|
+
readonly createdAt: Schema.NullOr<Schema.String>;
|
|
88
|
+
readonly hostedUrl: Schema.NullOr<Schema.String>;
|
|
89
|
+
readonly pdfUrl: Schema.NullOr<Schema.String>;
|
|
91
90
|
}>>;
|
|
92
91
|
}>, typeof BillingError>;
|
|
93
92
|
|
|
94
93
|
export declare const portalUrlDescriptor: ActionProcedureDescriptor<"billing.portalUrl", Schema.Struct<{
|
|
95
|
-
returnUrl:
|
|
94
|
+
readonly returnUrl: Schema.String;
|
|
96
95
|
}>, Schema.Struct<{
|
|
97
|
-
url:
|
|
96
|
+
readonly url: Schema.String;
|
|
98
97
|
}>, typeof BillingError>;
|
|
99
98
|
|
|
100
99
|
/** What a plan/seat change would cost, from the provider's invoice preview.
|
|
101
100
|
* Quote THIS in a confirmation dialog — a locally-estimated figure differs
|
|
102
101
|
* from the charge by the provider's rounding, tax and credit balance. */
|
|
103
102
|
export declare const previewChangeDescriptor: ActionProcedureDescriptor<"billing.previewChange", Schema.Struct<{
|
|
104
|
-
plan: Schema.optional<
|
|
105
|
-
quantity: Schema.optional<
|
|
103
|
+
readonly plan: Schema.optional<Schema.String>;
|
|
104
|
+
readonly quantity: Schema.optional<Schema.Number>;
|
|
106
105
|
}>, Schema.Struct<{
|
|
107
|
-
plan:
|
|
108
|
-
quantity:
|
|
106
|
+
readonly plan: Schema.String;
|
|
107
|
+
readonly quantity: Schema.Number;
|
|
109
108
|
/** Integer minor-unit settlement: > 0 charge, < 0 credit, 0 at boundary. */
|
|
110
|
-
prorationMinor:
|
|
111
|
-
currency:
|
|
109
|
+
readonly prorationMinor: Schema.Number;
|
|
110
|
+
readonly currency: Schema.String;
|
|
112
111
|
}>, typeof BillingError>;
|
|
113
112
|
|
|
114
113
|
export declare const reportUsageDescriptor: ActionProcedureDescriptor<"billing.reportUsage", Schema.Struct<{
|
|
115
|
-
key:
|
|
116
|
-
quantity:
|
|
114
|
+
readonly key: Schema.String;
|
|
115
|
+
readonly quantity: Schema.Number;
|
|
117
116
|
}>, Schema.Struct<{
|
|
118
|
-
ok:
|
|
117
|
+
readonly ok: Schema.Boolean;
|
|
119
118
|
}>, typeof BillingError>;
|
|
120
119
|
|
|
121
120
|
/** Take back a cancellation before the period ends. */
|
|
122
121
|
export declare const resumeSubscriptionDescriptor: ActionProcedureDescriptor<"billing.resume", Schema.Struct<{}>, Schema.Struct<{
|
|
123
|
-
subscription: Schema.Struct<{
|
|
124
|
-
plan:
|
|
125
|
-
status:
|
|
126
|
-
quantity:
|
|
127
|
-
currentPeriodEnd: Schema.NullOr<
|
|
128
|
-
cancelAt: Schema.NullOr<
|
|
122
|
+
readonly subscription: Schema.Struct<{
|
|
123
|
+
readonly plan: Schema.String;
|
|
124
|
+
readonly status: Schema.String;
|
|
125
|
+
readonly quantity: Schema.Number;
|
|
126
|
+
readonly currentPeriodEnd: Schema.NullOr<Schema.String>;
|
|
127
|
+
readonly cancelAt: Schema.NullOr<Schema.String>;
|
|
129
128
|
}>;
|
|
130
129
|
}>, typeof BillingError>;
|
|
131
130
|
|
|
132
131
|
export declare const startCheckoutDescriptor: ActionProcedureDescriptor<"billing.startCheckout", Schema.Struct<{
|
|
133
|
-
plan:
|
|
134
|
-
successUrl:
|
|
135
|
-
cancelUrl:
|
|
132
|
+
readonly plan: Schema.String;
|
|
133
|
+
readonly successUrl: Schema.String;
|
|
134
|
+
readonly cancelUrl: Schema.String;
|
|
136
135
|
/** Seats to buy up front. Omit for 1. */
|
|
137
|
-
quantity: Schema.optional<
|
|
136
|
+
readonly quantity: Schema.optional<Schema.Number>;
|
|
138
137
|
}>, Schema.Struct<{
|
|
139
|
-
url:
|
|
138
|
+
readonly url: Schema.String;
|
|
140
139
|
}>, typeof BillingError>;
|
|
141
140
|
|
|
142
141
|
export declare const subscriptionDescriptor: QueryProcedureDescriptor<"billing.subscription", Schema.Struct<{}>, Schema.Struct<{
|
|
143
|
-
subscription: Schema.NullOr<Schema.Struct<{
|
|
144
|
-
plan:
|
|
145
|
-
status:
|
|
146
|
-
quantity:
|
|
147
|
-
currentPeriodEnd: Schema.NullOr<
|
|
148
|
-
cancelAt: Schema.NullOr<
|
|
142
|
+
readonly subscription: Schema.NullOr<Schema.Struct<{
|
|
143
|
+
readonly plan: Schema.String;
|
|
144
|
+
readonly status: Schema.String;
|
|
145
|
+
readonly quantity: Schema.Number;
|
|
146
|
+
readonly currentPeriodEnd: Schema.NullOr<Schema.String>;
|
|
147
|
+
readonly cancelAt: Schema.NullOr<Schema.String>;
|
|
149
148
|
}>>;
|
|
150
149
|
}>, typeof BillingError>;
|
|
151
150
|
|
|
@@ -153,12 +152,12 @@ export declare const subscriptionDescriptor: QueryProcedureDescriptor<"billing.s
|
|
|
153
152
|
* its current subscription. Call it on the return from checkout or the
|
|
154
153
|
* portal, and when a billing page loads. */
|
|
155
154
|
export declare const syncDescriptor: ActionProcedureDescriptor<"billing.sync", Schema.Struct<{}>, Schema.Struct<{
|
|
156
|
-
subscription: Schema.NullOr<Schema.Struct<{
|
|
157
|
-
plan:
|
|
158
|
-
status:
|
|
159
|
-
quantity:
|
|
160
|
-
currentPeriodEnd: Schema.NullOr<
|
|
161
|
-
cancelAt: Schema.NullOr<
|
|
155
|
+
readonly subscription: Schema.NullOr<Schema.Struct<{
|
|
156
|
+
readonly plan: Schema.String;
|
|
157
|
+
readonly status: Schema.String;
|
|
158
|
+
readonly quantity: Schema.Number;
|
|
159
|
+
readonly currentPeriodEnd: Schema.NullOr<Schema.String>;
|
|
160
|
+
readonly cancelAt: Schema.NullOr<Schema.String>;
|
|
162
161
|
}>>;
|
|
163
162
|
}>, typeof BillingError>;
|
|
164
163
|
|