@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.
@@ -0,0 +1,479 @@
1
+ import { BillingError as e } from "./types.js";
2
+ import { invoiceHistoryPreview as t } from "./invoiceHistory.js";
3
+ import { Stripe as n, makeStripeClient as r, periodOf as i, quantityOf as a, stripeCall as o, unixToDate as s } from "./stripeClient.js";
4
+ import { stripeInvoiceSnapshot as c } from "./stripeInvoiceSnapshot.js";
5
+ import { stripeAccountIdentity as l, stripeCustomerSnapshot as u } from "./stripeIdentity.js";
6
+ import { stripeInvoiceHistory as d } from "./stripeInvoiceHistory.js";
7
+ import { verifyStripeBillingEvent as f } from "./stripeVerifiedEvent.js";
8
+ import { Effect as p } from "effect";
9
+ //#region src/stripeProvider.ts
10
+ var m = async (e) => {
11
+ let t = e ?? globalThis.crypto.randomUUID();
12
+ return "voltro-billing-" + [...new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(t))).slice(0, 8)].map((e) => String.fromCharCode(97 + e % 26)).join("");
13
+ }, h = (e) => {
14
+ let t = e.status === "open" ? "open" : e.status === "complete" ? "complete" : e.status === "expired" ? "expired" : null;
15
+ if (t === null) throw Error("Checkout provider status is unavailable.");
16
+ let n = /* @__PURE__ */ new Date(e.expires_at * 1e3);
17
+ if (!Number.isFinite(n.getTime())) throw Error("Checkout provider deadline is invalid.");
18
+ return {
19
+ id: e.id,
20
+ operationId: e.metadata?.voltroBillingOperation ?? null,
21
+ tenantId: e.client_reference_id,
22
+ providerCustomerId: typeof e.customer == "string" ? e.customer : e.customer?.id ?? null,
23
+ providerSubscriptionId: typeof e.subscription == "string" ? e.subscription : e.subscription?.id ?? null,
24
+ status: t,
25
+ url: e.url,
26
+ expiresAt: n
27
+ };
28
+ }, g = {
29
+ active: "active",
30
+ trialing: "trialing",
31
+ past_due: "pastDue",
32
+ unpaid: "pastDue",
33
+ canceled: "canceled",
34
+ incomplete: "incomplete",
35
+ incomplete_expired: "canceled",
36
+ paused: "pastDue"
37
+ }, _ = (e) => {
38
+ let t = e.metadata?.tenantId;
39
+ if (typeof t == "string" && t.length > 0) return t;
40
+ let n = e.client_reference_id;
41
+ return typeof n == "string" && n.length > 0 ? n : null;
42
+ }, v = (e) => {
43
+ let t = e.items?.data ?? [];
44
+ return t.find((e) => e.price?.recurring?.usage_type !== "metered") ?? t[0];
45
+ }, y = (e) => {
46
+ let t = v(e)?.price;
47
+ return typeof t?.id == "string" ? t.id : null;
48
+ }, b = (e) => typeof e == "string" ? e : typeof e?.id == "string" ? e.id : null, x = (e, t) => {
49
+ let n = y(e), r = n !== null && t ? t(n) : null;
50
+ if (r !== null) return r;
51
+ let i = e.metadata?.plan;
52
+ return typeof i == "string" && i.length > 0 ? i : null;
53
+ }, S = [
54
+ "checkout.session.completed",
55
+ "customer.created",
56
+ "customer.deleted",
57
+ "customer.subscription.created",
58
+ "customer.subscription.deleted",
59
+ "customer.subscription.paused",
60
+ "customer.subscription.resumed",
61
+ "customer.subscription.updated",
62
+ "invoice.paid",
63
+ "invoice.payment_failed"
64
+ ], C = (t, n) => {
65
+ let r = s(t.created);
66
+ switch (t.type) {
67
+ case "customer.subscription.created":
68
+ case "customer.subscription.updated":
69
+ case "customer.subscription.resumed":
70
+ case "customer.subscription.paused": {
71
+ let e = t.data.object, o = _(e), c = x(e, n), l = g[e.status];
72
+ if (o === null || c === null || l === void 0) return null;
73
+ let u = i(e);
74
+ return {
75
+ _tag: "subscriptionUpserted",
76
+ tenantId: o,
77
+ providerSubscriptionId: e.id,
78
+ plan: c,
79
+ status: l,
80
+ quantity: a(e),
81
+ currentPeriodStart: u.start,
82
+ currentPeriodEnd: u.end,
83
+ cancelAt: s(e.cancel_at),
84
+ occurredAt: r
85
+ };
86
+ }
87
+ case "customer.subscription.deleted": {
88
+ let e = t.data.object, n = _(e);
89
+ return n === null ? null : {
90
+ _tag: "subscriptionCanceled",
91
+ tenantId: n,
92
+ providerSubscriptionId: e.id,
93
+ occurredAt: r
94
+ };
95
+ }
96
+ case "invoice.paid":
97
+ case "invoice.payment_failed": {
98
+ let n = t.data.object, i = D(n);
99
+ if (i === null || i.length === 0 || typeof n.id != "string" || n.id.length === 0) throw new e({
100
+ source: "stripe.normalizeEvent",
101
+ message: "The invoice trigger requires exact customer and invoice identities.",
102
+ transient: !1
103
+ });
104
+ return {
105
+ _tag: t.type === "invoice.paid" ? "invoicePaid" : "invoicePaymentFailed",
106
+ providerCustomerId: i,
107
+ providerInvoiceId: n.id,
108
+ amountMinor: n.amount_due,
109
+ currency: n.currency,
110
+ occurredAt: r
111
+ };
112
+ }
113
+ case "checkout.session.completed": {
114
+ let e = t.data.object, n = _(e), i = b(e.customer);
115
+ return n === null || i === null ? null : {
116
+ _tag: "customerLinked",
117
+ tenantId: n,
118
+ providerCustomerId: i,
119
+ occurredAt: r
120
+ };
121
+ }
122
+ case "customer.deleted": {
123
+ let e = t.data.object;
124
+ return {
125
+ _tag: "customerUnlinked",
126
+ tenantId: _(e),
127
+ providerCustomerId: e.id,
128
+ occurredAt: r
129
+ };
130
+ }
131
+ case "customer.created": {
132
+ let e = t.data.object, n = _(e);
133
+ return n === null ? null : {
134
+ _tag: "customerLinked",
135
+ tenantId: n,
136
+ providerCustomerId: e.id,
137
+ occurredAt: r
138
+ };
139
+ }
140
+ default: return null;
141
+ }
142
+ }, w = {
143
+ prorate: "create_prorations",
144
+ none: "none",
145
+ always_invoice: "always_invoice"
146
+ }, T = (y) => {
147
+ let T = r(y), A = y.planForPriceId, j = (e) => {
148
+ let t = i(e);
149
+ return {
150
+ status: g[e.status] ?? "incomplete",
151
+ plan: x(e, A),
152
+ quantity: a(e),
153
+ currentPeriodStart: t.start,
154
+ currentPeriodEnd: t.end,
155
+ cancelAt: s(e.cancel_at)
156
+ };
157
+ }, M = (e) => ({
158
+ ...j(e),
159
+ providerSubscriptionId: e.id,
160
+ providerCustomerId: b(e.customer),
161
+ tenantId: _(e),
162
+ priceIds: (e.items?.data ?? []).map((e) => e.price?.id).filter((e) => typeof e == "string")
163
+ }), N = y.closeSettleSeconds ?? 600;
164
+ if (!Number.isSafeInteger(N) || N < 0) throw TypeError("closeSettleSeconds must be a nonnegative integer.");
165
+ let P = (e) => {
166
+ let t = e.lastResponse.headers.date, n = typeof t == "string" ? Date.parse(t) : NaN;
167
+ if (!Number.isFinite(n)) throw Error("Provider clock evidence is required for billing closure.");
168
+ return n;
169
+ }, F = async (e) => {
170
+ let t = e.providerSubscriptionId;
171
+ if (e.mode === "periodEnd") return T.subscriptions.update(t, {
172
+ cancel_at_period_end: !0,
173
+ expand: ["items.data.price"]
174
+ });
175
+ let n = e.closeOperation;
176
+ if (!n.id || !Number.isFinite(n.cancelAt.getTime()) || n.cancelAt.getTime() % 1e3 != 0) throw Error("A retained close operation and provider deadline are required.");
177
+ let r = await T.subscriptions.retrieve(t, { expand: ["items.data.price"] });
178
+ if (r.status === "canceled") return r;
179
+ let i = n.cancelAt.getTime() / 1e3;
180
+ if (r.cancel_at !== null && r.cancel_at <= i) return r;
181
+ let a = "voltro-close:" + n.id;
182
+ return P(r) >= n.cancelAt.getTime() ? T.subscriptions.cancel(t, {
183
+ invoice_now: !0,
184
+ prorate: !0
185
+ }, { idempotencyKey: a + ":cancel" }) : T.subscriptions.update(t, {
186
+ cancel_at: i,
187
+ proration_behavior: "create_prorations",
188
+ expand: ["items.data.price"]
189
+ }, { idempotencyKey: a + ":schedule" });
190
+ }, I = (e) => T.subscriptions.retrieve(e, { expand: ["items.data.price"] }), L = (e, t) => {
191
+ let n = i(e), r = v(e)?.price;
192
+ return {
193
+ plan: x(e, A) ?? "free",
194
+ quantity: a(e),
195
+ prorationMinor: t,
196
+ currency: (r?.currency ?? "usd").toLowerCase(),
197
+ currentPeriodStart: n.start,
198
+ currentPeriodEnd: n.end
199
+ };
200
+ }, R = (e, t) => {
201
+ let n = v(e)?.id;
202
+ return {
203
+ items: [{
204
+ ...n === void 0 ? {} : { id: n },
205
+ ...t.priceId === void 0 ? {} : { price: t.priceId },
206
+ ...t.quantity === void 0 ? {} : { quantity: t.quantity }
207
+ }],
208
+ proration_behavior: w[t.proration ?? "prorate"],
209
+ ...t.priceId !== void 0 && A ? { metadata: {
210
+ ...e.metadata,
211
+ plan: A(t.priceId) ?? ""
212
+ } } : {}
213
+ };
214
+ };
215
+ return {
216
+ name: "stripe",
217
+ fetchAccountIdentity: () => l(T),
218
+ fetchCustomerSnapshot: (e) => u(T, e),
219
+ supportsMeteredUsage: y.supportsMeteredUsage ?? !0,
220
+ usageRequiresCustomer: !0,
221
+ createCheckoutSession: (e) => o("stripe.checkout.sessions.create", async () => h(await T.checkout.sessions.create({
222
+ mode: "subscription",
223
+ integration_identifier: await m(e.idempotencyKey),
224
+ line_items: [{
225
+ price: e.priceId,
226
+ quantity: e.quantity ?? 1,
227
+ ...e.adjustableQuantity ? { adjustable_quantity: {
228
+ enabled: !0,
229
+ minimum: e.adjustableQuantity.min,
230
+ maximum: e.adjustableQuantity.max
231
+ } } : {}
232
+ }, ...(e.meteredPriceIds ?? []).map((e) => ({ price: e }))],
233
+ success_url: e.successUrl,
234
+ cancel_url: e.cancelUrl,
235
+ client_reference_id: e.tenantId,
236
+ metadata: {
237
+ tenantId: e.tenantId,
238
+ ...e.idempotencyKey === void 0 ? {} : { voltroBillingOperation: e.idempotencyKey }
239
+ },
240
+ subscription_data: {
241
+ metadata: { tenantId: e.tenantId },
242
+ ...e.trialDays === void 0 ? {} : { trial_period_days: e.trialDays }
243
+ },
244
+ ...e.providerCustomerId === void 0 ? {} : { customer: e.providerCustomerId },
245
+ ...e.expiresAt === void 0 ? {} : { expires_at: Math.floor(e.expiresAt.getTime() / 1e3) },
246
+ automatic_tax: { enabled: !0 },
247
+ tax_id_collection: { enabled: !0 },
248
+ ...e.providerCustomerId === void 0 ? {} : { customer_update: {
249
+ name: "auto",
250
+ address: "auto"
251
+ } },
252
+ allow_promotion_codes: !0
253
+ }, e.idempotencyKey === void 0 ? void 0 : { idempotencyKey: e.idempotencyKey }))),
254
+ findCheckoutSession: (e) => o("stripe.checkout.sessions.find", async () => {
255
+ let t = null, n = 0, r, i = null;
256
+ do {
257
+ let a = await T.checkout.sessions.list({
258
+ ...e.providerCustomerId === void 0 ? {} : { customer: e.providerCustomerId },
259
+ limit: 100,
260
+ ...r === void 0 ? {} : { starting_after: r }
261
+ });
262
+ if (r === void 0) {
263
+ let e = a.lastResponse.headers.date, t = typeof e == "string" ? new Date(e) : null;
264
+ i = t !== null && Number.isFinite(t.getTime()) ? t : null;
265
+ }
266
+ for (let r of a.data) {
267
+ if (++n > 1e4) throw Error("Checkout history is too large to establish an unambiguous operation receipt.");
268
+ if (r.metadata?.voltroBillingOperation !== e.operationId) continue;
269
+ let i = h(r);
270
+ if (i.tenantId !== e.tenantId || e.providerCustomerId !== void 0 && i.providerCustomerId !== e.providerCustomerId || t !== null) throw Error("Checkout receipt does not match a unique tenant operation.");
271
+ t = i;
272
+ }
273
+ if (!a.has_more) return {
274
+ receipt: t,
275
+ observedAt: i
276
+ };
277
+ let o = a.data.at(-1)?.id;
278
+ if (!o || o === r) throw Error("Checkout history did not advance.");
279
+ r = o;
280
+ } while (1);
281
+ }),
282
+ createPortalSession: (e) => o("stripe.billingPortal.sessions.create", async () => ({ url: (await T.billingPortal.sessions.create({
283
+ customer: e.providerCustomerId,
284
+ return_url: e.returnUrl
285
+ })).url })),
286
+ reportUsage: (t) => {
287
+ let n = t.providerCustomerId;
288
+ return n === void 0 ? p.fail(new e({
289
+ source: "stripe.billing.meterEvents.create",
290
+ message: `no Stripe customer linked for tenant '${t.tenantId}'`,
291
+ transient: !1
292
+ })) : o("stripe.billing.meterEvents.create", async () => {
293
+ try {
294
+ await T.billing.meterEvents.create({
295
+ event_name: t.entitlementKey,
296
+ identifier: t.idempotencyKey ?? `${t.tenantId}:${t.entitlementKey}:${t.periodEnd?.toISOString() ?? "current"}`,
297
+ payload: {
298
+ value: String(t.quantity),
299
+ stripe_customer_id: n
300
+ }
301
+ });
302
+ } catch (e) {
303
+ if (E(e)) return;
304
+ throw e;
305
+ }
306
+ });
307
+ },
308
+ updateSubscription: (e) => o("stripe.subscriptions.update", async () => {
309
+ let t = await I(e.providerSubscriptionId), n = await T.subscriptions.update(e.providerSubscriptionId, R(t, e)), r = await k(T, n);
310
+ return L(n, r);
311
+ }),
312
+ previewSubscriptionChange: (e) => o("stripe.invoices.createPreview", async () => {
313
+ let t = await I(e.providerSubscriptionId), n = R(t, e), r = (await T.invoices.createPreview({
314
+ subscription: e.providerSubscriptionId,
315
+ subscription_details: {
316
+ items: n.items,
317
+ ...n.proration_behavior === void 0 ? {} : { proration_behavior: n.proration_behavior }
318
+ }
319
+ })).lines.data.filter((e) => e.parent?.subscription_item_details?.proration === !0).reduce((e, t) => e + t.amount, 0);
320
+ return {
321
+ ...L(t, r),
322
+ ...e.quantity === void 0 ? {} : { quantity: e.quantity },
323
+ ...e.priceId !== void 0 && A ? { plan: A(e.priceId) ?? "free" } : {}
324
+ };
325
+ }),
326
+ fetchSubscriptionSnapshot: (e) => o("stripe.subscriptions.retrieve", async () => {
327
+ try {
328
+ return M(await I(e));
329
+ } catch (e) {
330
+ if (e instanceof n.errors.StripeInvalidRequestError && e.statusCode === 404) return null;
331
+ throw e;
332
+ }
333
+ }),
334
+ fetchSubscription: (e) => o("stripe.subscriptions.retrieve", async () => {
335
+ try {
336
+ return j(await I(e));
337
+ } catch (e) {
338
+ if (e instanceof n.errors.StripeInvalidRequestError && e.statusCode === 404) return null;
339
+ throw e;
340
+ }
341
+ }),
342
+ listSubscriptions: (e) => o("stripe.subscriptions.list", async () => {
343
+ let t = [];
344
+ for await (let n of T.subscriptions.list({
345
+ limit: 100,
346
+ expand: ["data.items.data.price"],
347
+ ...e.providerCustomerId === void 0 ? {} : { customer: e.providerCustomerId }
348
+ })) t.push(M(n));
349
+ return t;
350
+ }),
351
+ prepareSubscriptionClose: (e) => o("stripe.subscriptions.closeQuote", async () => {
352
+ let t = await T.subscriptions.retrieve(e, { expand: ["items.data.price"] });
353
+ return { cancelAt: new Date(P(t) + N * 1e3) };
354
+ }),
355
+ cancelSubscription: (e) => o("stripe.subscriptions.cancel", async () => j(await F(e))),
356
+ resumeSubscription: (e) => o("stripe.subscriptions.update", async () => j(await T.subscriptions.update(e, {
357
+ cancel_at_period_end: !1,
358
+ expand: ["items.data.price"]
359
+ }))),
360
+ ensureCustomer: (e) => o("stripe.customers.create", async () => {
361
+ let t = (await T.customers.search({
362
+ query: `metadata['tenantId']:'${e.tenantId.replace(/['\\]/g, "")}'`,
363
+ limit: 2
364
+ })).data.find((t) => t.metadata?.tenantId === e.tenantId);
365
+ return t === void 0 ? (await T.customers.create({ metadata: { tenantId: e.tenantId } }, { idempotencyKey: `voltro-billing-customer:${e.tenantId}` })).id : t.id;
366
+ }),
367
+ expireOpenCheckouts: (e) => o("stripe.checkout.sessions.expire", async () => {
368
+ let t = 0;
369
+ for await (let n of T.checkout.sessions.list({
370
+ customer: e.providerCustomerId,
371
+ status: "open",
372
+ limit: 100
373
+ })) (e.exceptOperationId === void 0 || n.metadata?.voltroBillingOperation !== e.exceptOperationId) && (await T.checkout.sessions.expire(n.id), t += 1);
374
+ return t;
375
+ }),
376
+ addSubscriptionPrices: (e) => o("stripe.subscriptionItems.create", async () => {
377
+ for (let t of e.priceIds) await T.subscriptionItems.create({
378
+ subscription: e.providerSubscriptionId,
379
+ price: t,
380
+ proration_behavior: "none"
381
+ });
382
+ }),
383
+ customerEmail: (e) => o("stripe.customers.retrieve", async () => {
384
+ let t = await T.customers.retrieve(e);
385
+ return t.deleted === !0 ? null : typeof t.email == "string" && t.email.length > 0 ? t.email : null;
386
+ }),
387
+ fetchInvoiceSnapshot: (e) => c(T, e),
388
+ listInvoiceHistory: (e) => d(T, e),
389
+ listInvoices: (e) => l(T).pipe(p.flatMap((t) => d(T, {
390
+ account: t,
391
+ providerCustomerId: e.providerCustomerId,
392
+ limit: e.limit ?? 20
393
+ })), p.map((e) => e.invoices.map(t))),
394
+ verifyWebhook: (e) => f(T, e),
395
+ eventContext: (e) => O(e) ? {
396
+ providerCustomerId: D(e.data.object),
397
+ createdAt: s(e.created)
398
+ } : {
399
+ providerCustomerId: null,
400
+ createdAt: null
401
+ },
402
+ listUndeliveredEvents: (t) => p.gen(function* () {
403
+ yield* p.try({
404
+ try: () => {
405
+ if (!Number.isInteger(t.limit) || t.limit < 1 || t.limit > 100 || t.cursor !== null && (!t.cursor || t.cursor.trim() !== t.cursor)) throw Error("Invalid event page input");
406
+ },
407
+ catch: () => new e({
408
+ source: "stripe.events.list",
409
+ message: "A bounded page and exact cursor are required.",
410
+ transient: !1
411
+ })
412
+ });
413
+ let n = yield* l(T), r = yield* o("stripe.events.list", () => T.events.list({
414
+ delivery_success: !1,
415
+ types: [...S],
416
+ limit: t.limit,
417
+ ...t.cursor === null ? {} : { starting_after: t.cursor }
418
+ }));
419
+ return yield* p.try({
420
+ try: () => {
421
+ if (!Array.isArray(r.data) || r.data.length > t.limit || typeof r.has_more != "boolean" || r.has_more && r.data.length === 0) throw Error("Incomplete event page");
422
+ let e = r.data.map((e) => {
423
+ let t = {
424
+ id: e.id,
425
+ providerCustomerId: D(e.data.object),
426
+ createdAt: s(e.created)
427
+ };
428
+ if (e.livemode !== n.livemode || e.account !== void 0 && e.account !== null && e.account !== n.providerAccountId) return {
429
+ ...t,
430
+ event: null,
431
+ rejected: !0
432
+ };
433
+ try {
434
+ return {
435
+ ...t,
436
+ event: C(e, A)
437
+ };
438
+ } catch {
439
+ return {
440
+ ...t,
441
+ event: null,
442
+ rejected: !0
443
+ };
444
+ }
445
+ }), i = r.has_more ? e.at(-1)?.id ?? null : null;
446
+ if (r.has_more && (i === null || i === t.cursor)) throw Error("Nonadvancing event page");
447
+ return {
448
+ events: e,
449
+ nextCursor: i
450
+ };
451
+ },
452
+ catch: () => new e({
453
+ source: "stripe.events.list",
454
+ message: "The provider event page could not be confirmed.",
455
+ transient: !0
456
+ })
457
+ });
458
+ }),
459
+ normalizeEvent: (t) => p.try({
460
+ try: () => O(t) ? C(t, A) : null,
461
+ catch: (t) => t instanceof e ? t : new e({
462
+ source: "stripe.normalizeEvent",
463
+ message: "The provider event could not be normalized.",
464
+ transient: !1
465
+ })
466
+ })
467
+ };
468
+ }, E = (e) => e instanceof n.errors.StripeInvalidRequestError && typeof e.message == "string" && e.message.startsWith("An event already exists with identifier"), D = (e) => {
469
+ let t = e;
470
+ return t.object === "customer" ? typeof t.id == "string" ? t.id : null : b(t.customer ?? null);
471
+ }, O = (e) => typeof e == "object" && !!e && typeof e.type == "string" && typeof e.data == "object", k = async (e, t) => {
472
+ try {
473
+ 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);
474
+ } catch {
475
+ return 0;
476
+ }
477
+ };
478
+ //#endregion
479
+ export { S as STRIPE_WEBHOOK_EVENTS, E as isDuplicateMeterEvent, C as normalizeStripeEvent, T as stripeProvider };
@@ -0,0 +1,52 @@
1
+ import { STRIPE_API_VERSION as e } from "./catalog.js";
2
+ //#region src/stripeRest.ts
3
+ var t = (e) => {
4
+ let t = [], n = (e, r) => {
5
+ if (r !== void 0) {
6
+ if (r === null) t.push(`${encodeURIComponent(e)}=`);
7
+ else if (Array.isArray(r)) r.forEach((t, r) => n(`${e}[${r}]`, t));
8
+ else if (typeof r == "object") for (let [t, i] of Object.entries(r)) n(`${e}[${t}]`, i);
9
+ else t.push(`${encodeURIComponent(e)}=${encodeURIComponent(String(r))}`);
10
+ }
11
+ };
12
+ for (let [t, r] of Object.entries(e)) n(t, r);
13
+ return t.join("&");
14
+ }, n = (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]"), r = class extends Error {
15
+ status;
16
+ constructor(e, t) {
17
+ super(t), this.status = e;
18
+ }
19
+ }, i = (i, a = fetch) => {
20
+ let o = async (o, s, c = {}) => {
21
+ if (!/^\/v[12]\/[A-Za-z0-9_/.-]+$/.test(s)) throw new r(0, `refusing request path ${JSON.stringify(s)}`);
22
+ let l = t(c), u = await a(`https://api.stripe.com${s}${o === "GET" && l.length > 0 ? `?${l}` : ""}`, {
23
+ method: o,
24
+ headers: {
25
+ authorization: `Bearer ${i}`,
26
+ "stripe-version": e,
27
+ ...o === "POST" ? { "content-type": "application/x-www-form-urlencoded" } : {}
28
+ },
29
+ ...o === "POST" ? { body: l } : {}
30
+ }), d = await u.json();
31
+ if (!u.ok) throw new r(u.status, n(d.error?.message ?? `HTTP ${u.status}`));
32
+ return d;
33
+ };
34
+ return {
35
+ get: (e, t) => o("GET", e, t),
36
+ post: (e, t) => o("POST", e, t),
37
+ list: async (e, t = {}) => {
38
+ let n = [], r;
39
+ for (;;) {
40
+ let i = await o("GET", e, {
41
+ limit: 100,
42
+ ...t,
43
+ ...r === void 0 ? {} : { starting_after: r }
44
+ });
45
+ if (n.push(...i.data), !i.has_more || i.data.length === 0) return n;
46
+ r = i.data[i.data.length - 1].id;
47
+ }
48
+ }
49
+ };
50
+ };
51
+ //#endregion
52
+ export { r as StripeRestError, t as formEncode, n as redactStripeKeys, i as stripeRest };
@@ -0,0 +1,29 @@
1
+ import { BillingError as e } from "./types.js";
2
+ import { stripeCall as t } from "./stripeClient.js";
3
+ import { stripeAccountIdentity as n } from "./stripeIdentity.js";
4
+ import { Effect as r } from "effect";
5
+ import { canonicalJson as i } from "@voltro/protocol";
6
+ //#region src/stripeVerifiedEvent.ts
7
+ var a = (a, o) => r.gen(function* () {
8
+ let s = yield* r.try({
9
+ try: () => a.webhooks.constructEvent(Buffer.from(o.rawBody), o.signatureHeader, o.secret),
10
+ catch: () => new e({
11
+ source: "stripe.webhooks.constructEvent",
12
+ message: "The webhook signature could not be verified.",
13
+ transient: !1
14
+ })
15
+ }), c = yield* t("stripe.events.retrieve", () => a.events.retrieve(s.id)), l = yield* n(a);
16
+ return yield* r.try({
17
+ try: () => {
18
+ if (c.id !== s.id || c.type !== s.type || c.created !== s.created || c.api_version !== s.api_version || c.livemode !== s.livemode || c.livemode !== l.livemode || (c.account ?? null) !== (s.account ?? null) || c.account !== void 0 && c.account !== null && c.account !== l.providerAccountId || i(JSON.parse(JSON.stringify(c.data))) !== i(JSON.parse(JSON.stringify(s.data)))) throw Error("Event context mismatch");
19
+ return c;
20
+ },
21
+ catch: () => new e({
22
+ source: "stripe.webhooks.account",
23
+ message: "The signed event does not match the configured provider account/mode.",
24
+ transient: !1
25
+ })
26
+ });
27
+ });
28
+ //#endregion
29
+ export { a as verifyStripeBillingEvent };
package/dist/table.js ADDED
@@ -0,0 +1,77 @@
1
+ import { billingCustomerAccountsTable as e, billingCustomerCustodyTable as t } from "./customerStore.js";
2
+ import { billingInvoiceHistoryPagesTable as n, billingInvoiceHistoryReferencesTable as r, billingInvoiceHistoryWalksTable as i } from "./invoiceHistoryJournal.js";
3
+ import { BILLING_INVOICE_OBSERVATIONS_TABLE as a, billingInvoiceObservationsTable as o, billingRetainedInvoiceObservationsTable as s } from "./invoiceStore.js";
4
+ import { billingInvoiceHistoryWorkTable as c } from "./invoiceHistoryWork.js";
5
+ import { billingInvoiceHistoryDispatchTable as l, billingInvoiceHistoryScanAccountsTable as u } from "./invoiceHistoryProgress.js";
6
+ import { billingUsageEventsTable as d } from "./usageEvents.js";
7
+ import { billingCheckoutOperationsTable as f } from "./checkoutStore.js";
8
+ import { billingCloseOperationsTable as p } from "./closeStore.js";
9
+ import { billingDunningNoticesTable as m } from "./dunning.js";
10
+ import { id as h, integer as g, table as _, text as v, timestamp as y } from "@voltro/database";
11
+ //#region src/table.ts
12
+ var b = "_voltro_billing_customers", x = "_voltro_billing_subscriptions", S = "_voltro_billing_invoices", C = "_voltro_billing_usage", w = _(b, {
13
+ id: h({ prefix: "bcust" }),
14
+ tenantId: v(),
15
+ provider: v(),
16
+ providerCustomerId: v(),
17
+ createdAt: y(),
18
+ updatedAt: y()
19
+ }).index("byBillingCustomerTenant", ["tenantId"]).index("byBillingProviderCustomer", ["providerCustomerId"]), T = _(x, {
20
+ id: h({ prefix: "bsub" }),
21
+ tenantId: v(),
22
+ provider: v(),
23
+ providerSubscriptionId: v(),
24
+ plan: v(),
25
+ status: v(),
26
+ quantity: g().default(1),
27
+ currentPeriodStart: y().nullable(),
28
+ currentPeriodEnd: y().nullable(),
29
+ cancelAt: y().nullable(),
30
+ pastDueSince: y().nullable(),
31
+ statusEventAt: y().nullable(),
32
+ createdAt: y(),
33
+ updatedAt: y()
34
+ }).index("byBillingSubscriptionTenant", ["tenantId"]).index("byBillingProviderSubscription", ["providerSubscriptionId"]).index("byBillingSubscriptionStatus", ["status"]), E = _(S, {
35
+ id: h({ prefix: "binv" }),
36
+ tenantId: v(),
37
+ provider: v(),
38
+ providerInvoiceId: v(),
39
+ amountMinor: g(),
40
+ currency: v(),
41
+ status: v(),
42
+ statusEventAt: y().nullable(),
43
+ createdAt: y()
44
+ }).index("byBillingInvoiceTenant", ["tenantId"]).index("byBillingProviderInvoice", ["providerInvoiceId"]), D = _(C, {
45
+ id: h({ prefix: "buse" }),
46
+ tenantId: v().maxLength(191),
47
+ entitlementKey: v().maxLength(191),
48
+ period: v().maxLength(16),
49
+ used: g().default(0),
50
+ reportedToProvider: g().default(0),
51
+ updatedAt: y()
52
+ }).unique("byBillingUsageWindow", [
53
+ "tenantId",
54
+ "entitlementKey",
55
+ "period"
56
+ ]), O = () => [
57
+ m,
58
+ d,
59
+ p,
60
+ f,
61
+ w,
62
+ e,
63
+ t,
64
+ i,
65
+ n,
66
+ r,
67
+ c,
68
+ u,
69
+ l,
70
+ T,
71
+ E,
72
+ o,
73
+ s,
74
+ D
75
+ ];
76
+ //#endregion
77
+ export { b as BILLING_CUSTOMERS_TABLE, S as BILLING_INVOICES_TABLE, a as BILLING_INVOICE_OBSERVATIONS_TABLE, x as BILLING_SUBSCRIPTIONS_TABLE, C as BILLING_USAGE_TABLE, w as billingCustomersTable, o as billingInvoiceObservationsTable, E as billingInvoicesTable, s as billingRetainedInvoiceObservationsTable, T as billingSubscriptionsTable, O as billingTables, D as billingUsageTable };