@voltro/plugin-billing 0.86.0 → 0.88.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +5484 -0
  2. package/README.md +428 -0
  3. package/THIRD-PARTY-NOTICES.md +1511 -276
  4. package/dist/catalog.js +1 -2
  5. package/dist/checkoutStore.js +164 -0
  6. package/dist/closeStore.js +124 -0
  7. package/dist/custodyInventory.js +154 -0
  8. package/dist/customerStore.js +258 -0
  9. package/dist/dunning.js +1 -2
  10. package/dist/economy.d.ts +30 -0
  11. package/dist/economy.js +265 -0
  12. package/dist/entitlement.js +1 -2
  13. package/dist/eventDelivery.js +71 -0
  14. package/dist/flushClaim.js +1 -2
  15. package/dist/index.d.ts +669 -65
  16. package/dist/index.js +230 -173
  17. package/dist/invoiceHistory.js +90 -0
  18. package/dist/invoiceHistoryJournal.js +475 -0
  19. package/dist/invoiceHistoryProgress.js +234 -0
  20. package/dist/invoiceHistoryWork.js +362 -0
  21. package/dist/invoiceSnapshot.js +65 -0
  22. package/dist/invoiceStore.js +132 -0
  23. package/dist/metering.js +1 -2
  24. package/dist/plans.js +1 -2
  25. package/dist/providerIdentity.js +23 -0
  26. package/dist/providers.js +271 -67
  27. package/dist/provision.js +1 -2
  28. package/dist/rpc.js +1 -2
  29. package/dist/service.js +892 -392
  30. package/dist/stores.js +125 -291
  31. package/dist/stripeClient.js +1 -2
  32. package/dist/stripeIdentity.js +55 -0
  33. package/dist/stripeInvoiceHistory.js +66 -0
  34. package/dist/stripeInvoiceSnapshot.js +85 -0
  35. package/dist/stripeProvider.js +305 -179
  36. package/dist/stripeRest.js +1 -2
  37. package/dist/stripeVerifiedEvent.js +28 -0
  38. package/dist/table.js +70 -48
  39. package/dist/types.d.ts +298 -27
  40. package/dist/types.js +1 -2
  41. package/dist/usageEvents.js +64 -0
  42. package/dist/web/index.js +1 -2
  43. package/dist/webhook.js +32 -40
  44. package/package.json +18 -8
@@ -0,0 +1,132 @@
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
+ // 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
+ export { p as BILLING_INVOICE_OBSERVATIONS_TABLE, h as billingInvoiceObservationsTable, m as billingRetainedInvoiceObservationsTable, E as dataStoreInvoiceStore };
package/dist/metering.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { periodKey as e } from "./service.js";
2
2
  import { Effect as t } from "effect";
3
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
4
+ // src/metering.ts
5
5
  var s = "_voltro_ai_usage", c = (e) => {
6
6
  for (let [t, n] of Object.entries(e)) {
7
7
  if (t.length === 0) throw Error("billing metering: a meter key must be non-empty");
@@ -70,5 +70,4 @@ var s = "_voltro_ai_usage", c = (e) => {
70
70
  s > 0 && (yield* n.reportUsage(i, e, s).pipe(t.catch(() => t.void)));
71
71
  }
72
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
73
  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 CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_PLAN as e } from "./types.js";
2
- //#region src/plans.ts
2
+ // src/plans.ts
3
3
  var t = (e) => e === void 0 ? 0 : e === "unlimited" ? Infinity : e, n = (e) => {
4
4
  let n = /* @__PURE__ */ new Map();
5
5
  for (let [t, r] of Object.entries(e)) r.priceId !== void 0 && n.set(r.priceId, t);
@@ -20,5 +20,4 @@ var t = (e) => e === void 0 ? 0 : e === "unlimited" ? Infinity : e, n = (e) => {
20
20
  planIds: () => Object.keys(e)
21
21
  };
22
22
  }, r = () => n({ [e]: { entitlements: {} } });
23
- //#endregion
24
23
  export { n as buildPlanRegistry, r as emptyPlanRegistry };
@@ -0,0 +1,23 @@
1
+ import { BillingError as e } from "./types.js";
2
+ // 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
+ export { r as captureProviderAccount, a as captureProviderCustomer, n as exactProviderIdentifier, t as providerIdentityError, i as sameProviderAccount };
package/dist/providers.js CHANGED
@@ -1,10 +1,41 @@
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) => {
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
+ // 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) => {
6
37
  let n = {
7
- ...o.get(e) ?? {
38
+ ...x.get(e) ?? {
8
39
  status: "active",
9
40
  plan: null,
10
41
  quantity: 1,
@@ -14,72 +45,241 @@ var i = (e) => {
14
45
  },
15
46
  ...t
16
47
  };
17
- o.set(e, n);
18
- let r = s.get(e);
19
- return r !== void 0 && s.set(e, {
48
+ x.set(e, n);
49
+ let r = S.get(e);
50
+ return r !== void 0 && S.set(e, {
20
51
  ...r,
21
52
  ...t
22
53
  }), n;
23
- }, y = (e) => typeof e == "object" && !!e && typeof e._tag == "string";
54
+ }, B = (e) => typeof e == "object" && !!e && typeof e._tag == "string";
24
55
  return {
25
56
  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,
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,
36
123
  setUndelivered: (e) => {
37
- g = e;
124
+ L = structuredClone(e);
38
125
  },
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;
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;
46
164
  }),
47
- expireOpenCheckouts: (e) => r.sync(() => (m.push(e.providerCustomerId), 0)),
48
165
  listSubscription: (e) => {
49
- s.set(e.providerSubscriptionId, e), o.set(e.providerSubscriptionId, e);
166
+ S.set(e.providerSubscriptionId, e), x.set(e.providerSubscriptionId, e);
50
167
  },
51
168
  setSubscription: (e, t) => {
52
- t === null ? o.delete(e) : o.set(e, t);
169
+ t === null ? x.delete(e) : x.set(e, t);
53
170
  },
54
171
  setCustomerEmail: (e, t) => {
55
- t === null ? c.delete(e) : c.set(e, t);
172
+ t === null ? C.delete(e) : C.set(e, t);
56
173
  },
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) });
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
+ };
66
181
  }),
67
- resumeSubscription: (e) => r.sync(() => (u.push(e), v(e, { cancelAt: null }))),
68
- addSubscriptionPrices: (e) => r.sync(() => {
69
- d.push({
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({
70
210
  providerSubscriptionId: e.providerSubscriptionId,
71
211
  priceIds: e.priceIds
72
212
  });
73
- let t = s.get(e.providerSubscriptionId);
74
- t !== void 0 && v(e.providerSubscriptionId, { priceIds: [...t.priceIds, ...e.priceIds] });
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
+ })
75
277
  }),
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);
278
+ createPortalSession: (e) => h.succeed({ url: `${_}/portal?customer=${encodeURIComponent(e.providerCustomerId)}` }),
279
+ reportUsage: (e) => h.sync(() => {
280
+ v.push(e);
81
281
  }),
82
- updateSubscription: (e) => r.sync(() => (i.push(e), {
282
+ updateSubscription: (e) => h.sync(() => (y.push(e), {
83
283
  plan: e.priceId ?? "mock-plan",
84
284
  quantity: e.quantity ?? 1,
85
285
  prorationMinor: 0,
@@ -87,7 +287,7 @@ var i = (e) => {
87
287
  currentPeriodStart: null,
88
288
  currentPeriodEnd: null
89
289
  })),
90
- previewSubscriptionChange: (e) => r.succeed({
290
+ previewSubscriptionChange: (e) => h.succeed({
91
291
  plan: e.priceId ?? "mock-plan",
92
292
  quantity: e.quantity ?? 1,
93
293
  prorationMinor: 0,
@@ -95,39 +295,43 @@ var i = (e) => {
95
295
  currentPeriodStart: null,
96
296
  currentPeriodEnd: null
97
297
  }),
98
- listInvoices: () => r.succeed([]),
99
- normalizeEvent: (e) => r.succeed(y(e) ? e : null)
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)
100
305
  };
101
- }, a = /* @__PURE__ */ new Set([
306
+ }, _ = /* @__PURE__ */ new Set([
102
307
  "",
103
308
  "development",
104
309
  "dev",
105
310
  "test"
106
- ]), o = (e) => !a.has((e ?? "").trim().toLowerCase()), s = (e) => {
107
- if (o(e.env.NODE_ENV)) {
311
+ ]), v = (e) => !_.has((e ?? "").trim().toLowerCase()), y = (e) => {
312
+ if (v(e.env.NODE_ENV)) {
108
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.");
109
314
  if (e.resolvedName === "stripe") {
110
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.");
111
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.");
112
317
  }
113
318
  }
114
- }, c = (e) => {
115
- let { provider: t, env: r } = e;
319
+ }, b = (e) => {
320
+ let { provider: t, env: n } = e;
116
321
  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;
322
+ if ((t ?? (n.STRIPE_SECRET_KEY ? "stripe" : "mock")) === "stripe") {
323
+ let t = e.apiKey ?? n.STRIPE_SECRET_KEY;
119
324
  if (!t) throw Error("billingPlugin: provider 'stripe' needs an apiKey (option or STRIPE_SECRET_KEY env)");
120
- return n({
325
+ return m({
121
326
  apiKey: t,
122
327
  ...e.planForPriceId ? { planForPriceId: e.planForPriceId } : {}
123
328
  });
124
329
  }
125
- return i();
126
- }, l = (e) => e.name === "mock" ? [] : [{
330
+ return g();
331
+ }, x = (e) => e.name === "mock" ? [] : [{
127
332
  destination: e.name === "stripe" ? "api.stripe.com" : `runtime:the '${e.name}' billing provider`,
128
333
  purpose: "customers, subscriptions, checkout and metered usage",
129
334
  data: ["personal", "metadata"],
130
335
  disable: "without STRIPE_SECRET_KEY the mock provider answers, and nothing leaves (production refuses to boot on it)"
131
336
  }];
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 };
337
+ 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 };
package/dist/provision.js CHANGED
@@ -3,7 +3,7 @@ import { redactStripeKeys as s, stripeRest as c } from "./stripeRest.js";
3
3
  import { execFileSync as l } from "node:child_process";
4
4
  import { chmodSync as u, existsSync as d, readFileSync as f, realpathSync as p, writeFileSync as m } from "node:fs";
5
5
  import { dirname as h, resolve as g } from "node:path";
6
- //#region src/provision.ts
6
+ // src/provision.ts
7
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
8
  let u = c.environments[l.env], d = await s.get("/v1/tax/settings"), f = l.env === "live";
9
9
  if (d.livemode !== f) throw Error(`the key reaches a ${d.livemode ? "LIVE" : "test"} account, not ${l.env}`);
@@ -149,5 +149,4 @@ var _ = (e) => e.filter((e) => e.applied !== !0 && (e.kind === "create" || e.kin
149
149
  let p = _(l.findings), h = l.findings.filter((e) => e.kind === "manual");
150
150
  return console.log(` ${p.length} open, ${h.length} manual`), p.length > 0 || a.strict && h.length > 0 ? 1 : 0;
151
151
  };
152
- //#endregion
153
152
  export { x as envOutRefusal, y as mergeEnvFile, _ as openFindings, S as parseProvisionArgs, v as provisionStripeCatalog, C as resolveProvisionKey, T as runStripeProvision };
package/dist/rpc.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { BillingError as e } from "./types.js";
2
2
  import { Schema as t } from "effect";
3
3
  import { defineAction as n, defineQuery as r } from "@voltro/protocol";
4
- //#region src/rpc.ts
4
+ // src/rpc.ts
5
5
  var i = n({
6
6
  name: "billing.startCheckout",
7
7
  input: t.Struct({
@@ -211,5 +211,4 @@ var i = n({
211
211
  }
212
212
  }
213
213
  ];
214
- //#endregion
215
214
  export { v as billingRpcClientImports, h as cancelSubscriptionDescriptor, p as changePlanDescriptor, m as changeSeatsDescriptor, c as entitlementStatusDescriptor, f as invoicesDescriptor, a as portalUrlDescriptor, d as previewChangeDescriptor, l as reportUsageDescriptor, _ as resumeSubscriptionDescriptor, i as startCheckoutDescriptor, s as subscriptionDescriptor, g as syncDescriptor };