@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
package/dist/service.js CHANGED
@@ -1,24 +1,38 @@
1
1
  import { BillingError as e, BillingService as t, EntitlementExceeded as n } from "./types.js";
2
- import { evaluateEntitlement as r } from "./entitlement.js";
3
- import { DUNNING_LOCK_STEP_ID as i, defaultDunningEmail as a, dueSteps as o, episodeKeyOf as s, evaluateBillingEntitlement as c, resolveDunningConfig as l } from "./dunning.js";
4
- import { isLive as u, memoryStores as d } from "./stores.js";
5
- import { Effect as f, Layer as p, Schedule as m } from "effect";
6
- import { createLogger as h } from "@voltro/logger";
7
- //#region src/service.ts
8
- var g = h({ scope: "@voltro/plugin-billing" }), _ = 3, v = /* @__PURE__ */ new Set([
2
+ import { captureProviderAccount as r, sameProviderAccount as i } from "./providerIdentity.js";
3
+ import { evaluateEntitlement as a } from "./entitlement.js";
4
+ import { assertInvoiceSnapshot as o, invoiceSnapshotError as s } from "./invoiceSnapshot.js";
5
+ import { captureInvoiceHistoryInput as c, captureInvoiceHistoryPage as l, historyError as u } from "./invoiceHistory.js";
6
+ import { DUNNING_LOCK_STEP_ID as d, defaultDunningEmail as f, dueSteps as p, episodeKeyOf as m, evaluateBillingEntitlement as h, resolveDunningConfig as g } from "./dunning.js";
7
+ import { isLive as _, memoryStores as v } from "./stores.js";
8
+ import { Effect as y, Layer as b, Schedule as x } from "effect";
9
+ import { createLogger as S } from "@voltro/logger";
10
+ // src/service.ts
11
+ var C = S({ scope: "@voltro/plugin-billing" }), w = 3, T = /* @__PURE__ */ new Set([
9
12
  "active",
10
13
  "trialing",
11
14
  "pastDue"
12
- ]), y = (e = /* @__PURE__ */ new Date()) => `${e.getUTCFullYear()}-${String(e.getUTCMonth() + 1).padStart(2, "0")}`, b = (e) => ({ stores: e ?? d() }), x = (e, t) => e.pipe(f.retry({
13
- schedule: m.recurs(Math.max(0, t - 1)),
15
+ ]), E = /* @__PURE__ */ new Set([...T, "incomplete"]), D = (e = /* @__PURE__ */ new Date()) => `${e.getUTCFullYear()}-${String(e.getUTCMonth() + 1).padStart(2, "0")}`, ee = (e) => ({ stores: e ?? v() }), O = (e, t) => e.pipe(y.retry({
16
+ schedule: x.recurs(Math.max(0, t - 1)),
14
17
  while: (e) => e.transient
15
- })), S = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,99}$/, C = "usage key must be 1–100 characters of A–Z, a–z, 0–9, \"_\", \".\", \":\" or \"-\", starting with a letter or digit", w = (t) => {
16
- let { provider: d, plans: p } = t, m = t.attempts ?? 3, h = t.checkout, w = t.now ?? (() => /* @__PURE__ */ new Date()), T = (n, r) => n.length === 0 ? f.fail(new e({
17
- source: "billing",
18
- message: "no tenant on this call",
19
- transient: !1
20
- })) : t.admit === void 0 ? f.void : t.admit(n, r), E = t.redirectOrigins?.map((e) => new URL(e).origin), D = (t, n) => {
21
- let r = (t) => f.fail(new e({
18
+ })), k = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,99}$/, te = "usage key must be 1–100 characters of A–Z, a–z, 0–9, \"_\", \".\", \":\" or \"-\", starting with a letter or digit", A = (t) => {
19
+ let { provider: v, plans: b } = t, x = t.attempts ?? 3, S = t.checkout, A = t.now ?? (() => /* @__PURE__ */ new Date()), j = (n, r, i) => y.gen(function* () {
20
+ if (n.length === 0) return yield* y.fail(new e({
21
+ source: "billing",
22
+ message: "no tenant on this call",
23
+ transient: !1
24
+ }));
25
+ if (r !== "cancel" && (yield* N.stores.closures.getAccount(n)) !== null) return yield* y.fail(new e({
26
+ source: "billing",
27
+ message: "This billing account is closing. New paid actions are unavailable.",
28
+ transient: !1
29
+ }));
30
+ if (t.admit !== void 0 && (yield* t.admit(n, r)), yield* L(n), i !== void 0) {
31
+ let e = yield* N.stores.customers.getByTenant(n);
32
+ if (e === null || e.providerCustomerId !== i) return yield* y.fail(s("Checkout customer custody changed before dispatch or private URL replay."));
33
+ }
34
+ }), ne = t.redirectOrigins?.map((e) => new URL(e).origin), M = (t, n) => {
35
+ let r = (t) => y.fail(new e({
22
36
  source: "billing",
23
37
  message: `${n} ${t}`,
24
38
  transient: !1
@@ -28,61 +42,140 @@ var g = h({ scope: "@voltro/plugin-billing" }), _ = 3, v = /* @__PURE__ */ new S
28
42
  } catch {
29
43
  return r("is not an absolute URL");
30
44
  }
31
- return i.protocol !== "https:" && i.protocol !== "http:" ? r("must be an http(s) URL") : E !== void 0 && !E.includes(i.origin) ? r(`must point at one of the configured redirect origins, not ${i.origin}`) : f.void;
32
- }, O = b(t.stores), k = t.dunning ?? l(void 0, {}), A = (e) => O.stores.subscriptions.getByTenant(e).pipe(f.map((t) => c(t, k, w(), e))), j = (e) => A(e).pipe(f.map((e) => e.plan)), ee = (e) => O.stores.subscriptions.getByTenant(e), M = (e, n) => f.gen(function* () {
45
+ return i.protocol !== "https:" && i.protocol !== "http:" ? r("must be an http(s) URL") : ne !== void 0 && !ne.includes(i.origin) ? r(`must point at one of the configured redirect origins, not ${i.origin}`) : y.void;
46
+ }, N = ee(t.stores), P = t.dunning ?? g(void 0, {}), F, I = () => {
47
+ if (F !== void 0) return y.succeed(F);
48
+ let e = v.fetchAccountIdentity?.bind(v);
49
+ return e === void 0 ? y.fail(s("Account-qualified billing is unsupported by this provider.")) : e().pipe(y.flatMap((e) => y.try({
50
+ try: () => {
51
+ let t = Object.freeze({
52
+ provider: v.name,
53
+ account: r(e)
54
+ });
55
+ if (F !== void 0 && !i(F.account, t.account)) throw s("The provider account changed during observation.");
56
+ return F ??= t, F;
57
+ },
58
+ catch: () => s("A fixed provider account/mode is required.")
59
+ })));
60
+ }, L = (e) => I().pipe(y.flatMap((t) => N.stores.customers.assertAccount(e, t))), R = (e) => I().pipe(y.flatMap((t) => N.stores.customers.reserveAccount(e, t))), re = (e) => N.stores.customers.accountForTenant(e).pipe(y.flatMap((t) => t === null ? R(e) : y.void)), z = (e) => L(e).pipe(y.andThen(N.stores.customers.getByTenant(e))), B = (e, t) => y.gen(function* () {
61
+ let n = yield* I(), r = v.fetchCustomerSnapshot?.bind(v);
62
+ if (r === void 0) return yield* y.fail(s("Positive provider customer evidence is required."));
63
+ if ((yield* N.stores.customers.getByTenant(e)) === null && (yield* N.stores.subscriptions.listByTenant(e)).some((e) => _(e.status))) return yield* y.fail(s("A previous live subscription must be reconciled before customer relinking."));
64
+ let i = yield* O(r({
65
+ account: n.account,
66
+ providerCustomerId: t
67
+ }), x);
68
+ return yield* N.stores.customers.bind({
69
+ ...n,
70
+ tenantId: e,
71
+ providerCustomerId: t
72
+ }, i);
73
+ }), ie = (e) => N.stores.subscriptions.getByTenant(e).pipe(y.flatMap((t) => N.stores.customers.accountForTenant(e).pipe(y.flatMap((n) => t !== null && n === null ? y.fail(s("The retained subscription has no qualified billing account.")) : y.succeed(h(t, P, A(), e)))))), ae = (e) => ie(e).pipe(y.map((e) => e.plan)), oe = (e) => N.stores.subscriptions.getByTenant(e), se = (e, n) => y.gen(function* () {
33
74
  if (t.resolveEntitlementLimit !== void 0) {
34
75
  let r = yield* t.resolveEntitlementLimit(e, n);
35
76
  if (r !== null) return r;
36
77
  }
37
- let r = yield* j(e);
38
- return p.entitlementLimit(r, n);
39
- }), te = (e, t, n) => f.gen(function* () {
40
- let i = yield* M(e, t), a = yield* O.stores.usage.used(e, t, y(w()));
41
- return r(t, i, a, n);
42
- }), ne = (e, t, r) => f.gen(function* () {
43
- let i = yield* M(e, t);
78
+ let r = yield* ae(e);
79
+ return b.entitlementLimit(r, n);
80
+ }), ce = (e, t, n) => y.gen(function* () {
81
+ let r = yield* se(e, t), i = yield* N.stores.usage.used(e, t, D(A()));
82
+ return a(t, r, i, n);
83
+ }), le = (e, t, r) => y.gen(function* () {
84
+ let i = yield* se(e, t);
44
85
  if (!Number.isFinite(i) || r <= 0) return;
45
- let a = y(w()), o = yield* O.stores.usage.consume(e, t, a, r, i);
46
- if (!o.allowed) return yield* f.fail(new n({
86
+ let a = D(A());
87
+ yield* re(e);
88
+ let o = yield* N.stores.usage.consume(e, t, a, r, i);
89
+ if (!o.allowed) return yield* y.fail(new n({
47
90
  entitlement: t,
48
91
  limit: o.limit,
49
92
  used: o.used,
50
93
  cost: r
51
94
  }));
52
- }), N = (t, n, r) => S.test(n) ? Number.isFinite(r) ? r <= 0 ? f.void : O.stores.usage.increment(t, n, y(w()), r).pipe(f.asVoid) : f.fail(new e({
95
+ }), ue = (t, n, r) => k.test(n) ? Number.isFinite(r) ? r <= 0 ? y.void : re(t).pipe(y.andThen(N.stores.usage.increment(t, n, D(A()), r)), y.asVoid) : y.fail(new e({
53
96
  source: "billing",
54
97
  message: `usage quantity must be a finite number, got ${r}`,
55
98
  transient: !1
56
- })) : f.fail(new e({
99
+ })) : y.fail(new e({
100
+ source: "billing",
101
+ message: te,
102
+ transient: !1
103
+ })), de = (t) => y.gen(function* () {
104
+ if (!k.test(t.entitlementKey) || !(t.occurredAt instanceof Date) || !Number.isFinite(t.occurredAt.getTime()) || t.occurredAt.getTime() < 0 || !Number.isFinite(A().getTime())) return yield* y.fail(new e({
105
+ source: "usageEvent",
106
+ message: "Usage event requires a valid meter key and a completed occurrence timestamp.",
107
+ transient: !1
108
+ }));
109
+ if (t.occurredAt.getTime() > A().getTime()) return yield* y.fail(new e({
110
+ source: "usageEvent",
111
+ message: "Usage completion is ahead of this replica clock. Retry the same event after its completion time.",
112
+ transient: !0
113
+ }));
114
+ let n = new Date(t.occurredAt);
115
+ if ((yield* N.stores.customers.accountForTenant(t.tenantId)) === null) return yield* y.fail(s("Usage occurrence requires its already recorded original billing account."));
116
+ yield* N.stores.usage.recordEvent({
117
+ ...t,
118
+ occurredAt: n,
119
+ period: D(n)
120
+ });
121
+ }), V = () => new e({
57
122
  source: "billing",
58
- message: C,
123
+ message: "Usage arrived after final billing dispatch was sealed. Financial reconciliation is required before account deletion.",
59
124
  transient: !1
60
- })), P = () => f.gen(function* () {
61
- if (!d.supportsMeteredUsage) return;
62
- let e = yield* O.stores.usage.pending();
63
- for (let t of e) {
64
- let e = t.used - t.reportedToProvider;
65
- if (e <= 0) continue;
66
- let n = yield* O.stores.customers.getByTenant(t.tenantId);
67
- n === null && d.usageRequiresCustomer === !0 ? yield* f.logWarning("billing usage not flushed: no provider customer linked").pipe(f.annotateLogs({
68
- tenantId: t.tenantId,
69
- key: t.entitlementKey,
70
- pending: e
71
- })) : (yield* x(d.reportUsage({
72
- tenantId: t.tenantId,
73
- entitlementKey: t.entitlementKey,
74
- quantity: e,
75
- idempotencyKey: `${t.tenantId}:${t.entitlementKey}:${t.period}:${t.used}`,
76
- ...n === null ? {} : { providerCustomerId: n.providerCustomerId }
77
- }), m), yield* O.stores.usage.markReported(t.tenantId, t.entitlementKey, t.period, t.used));
125
+ }), H = (e) => N.stores.closures.getAccount(e).pipe(y.map((e) => e?.usageSealed === 1)), U = (t) => y.gen(function* () {
126
+ if (!v.supportsMeteredUsage) return;
127
+ let n = yield* N.stores.usage.pending();
128
+ for (let r of n) {
129
+ if (t !== void 0 && r.tenantId !== t) continue;
130
+ let n = r.used - r.reportedToProvider;
131
+ if (n <= 0) continue;
132
+ if (r.period !== D(A())) {
133
+ if (t !== void 0) return yield* y.fail(new e({
134
+ source: "billing",
135
+ message: "Historical usage requires original-period financial reconciliation before account deletion.",
136
+ transient: !1
137
+ }));
138
+ yield* y.logWarning("historical billing usage retained for original-period reconciliation").pipe(y.annotateLogs({
139
+ tenantId: r.tenantId,
140
+ period: r.period
141
+ }));
142
+ continue;
143
+ }
144
+ if (yield* H(r.tenantId)) {
145
+ if (t !== void 0) return yield* y.fail(V());
146
+ yield* y.logWarning("billing usage retained: final dispatch is sealed").pipe(y.annotateLogs({ tenantId: r.tenantId }));
147
+ continue;
148
+ }
149
+ let i = yield* N.stores.customers.getByTenant(r.tenantId);
150
+ if (i === null && v.usageRequiresCustomer === !0) {
151
+ if (t !== void 0) return yield* y.fail(new e({
152
+ source: "billing",
153
+ message: "Final usage requires a confirmed provider customer before account closure.",
154
+ transient: !0
155
+ }));
156
+ yield* y.logWarning("billing usage not flushed: no provider customer linked").pipe(y.annotateLogs({
157
+ tenantId: r.tenantId,
158
+ key: r.entitlementKey,
159
+ pending: n
160
+ }));
161
+ } else yield* O(y.gen(function* () {
162
+ if (yield* H(r.tenantId)) return yield* y.fail(V());
163
+ yield* v.reportUsage({
164
+ tenantId: r.tenantId,
165
+ entitlementKey: r.entitlementKey,
166
+ quantity: n,
167
+ idempotencyKey: `${r.tenantId}:${r.entitlementKey}:${r.period}:${r.used}`,
168
+ ...i === null ? {} : { providerCustomerId: i.providerCustomerId }
169
+ });
170
+ }), x), yield* N.stores.usage.markReported(r.tenantId, r.entitlementKey, r.period, r.used);
78
171
  }
79
- }), F = (t) => f.fail(new e({
172
+ }), fe = () => U(), W = (t) => y.fail(new e({
80
173
  source: "billing",
81
174
  message: `tenant '${t}' already has a paying subscription — change its plan instead of checking out again`,
82
175
  transient: !1
83
- })), I = (e, t, n) => t.status === "canceled" ? O.stores.subscriptions.markCanceled(t.providerSubscriptionId) : O.stores.subscriptions.upsert({
176
+ })), G = (e, t, n) => L(e).pipe(y.flatMap((r) => r ? t.status === "canceled" ? N.stores.subscriptions.markCanceled(t.providerSubscriptionId) : N.stores.subscriptions.upsert({
84
177
  tenantId: e,
85
- provider: d.name,
178
+ provider: v.name,
86
179
  providerSubscriptionId: t.providerSubscriptionId,
87
180
  plan: t.plan ?? "free",
88
181
  status: t.status,
@@ -92,111 +185,221 @@ var g = h({ scope: "@voltro/plugin-billing" }), _ = 3, v = /* @__PURE__ */ new S
92
185
  cancelAt: t.cancelAt,
93
186
  pastDueSince: null,
94
187
  statusEventAt: n
95
- }), L = (t) => f.gen(function* () {
96
- let n = p.priceIdFor(t.plan);
97
- if (n === null) return yield* f.fail(new e({
188
+ }) : y.fail(s("A qualified billing account is required before subscription writes.")))), pe = (t) => y.gen(function* () {
189
+ let { tenantId: n, plan: r, successUrl: i, cancelUrl: a } = t, o = t.quantity ?? 1, s = t.authorize ?? (() => y.void), c = b.priceIdFor(r), l = (t) => y.fail(new e({
98
190
  source: "billing",
99
- message: `plan '${t.plan}' has no provider priceId — not a paid plan`,
191
+ message: t,
100
192
  transient: !1
101
193
  }));
102
- if (yield* T(t.tenantId, "checkout"), t.quantity !== void 0 && (!Number.isSafeInteger(t.quantity) || t.quantity <= 0)) return yield* f.fail(new e({
103
- source: "billing",
104
- message: `seat quantity must be a positive integer, got ${t.quantity}`,
105
- transient: !1
106
- }));
107
- yield* D(t.successUrl, "successUrl"), yield* D(t.cancelUrl, "cancelUrl");
108
- let r = yield* O.stores.subscriptions.getByTenant(t.tenantId);
109
- if (r !== null && v.has(r.status)) return yield* F(t.tenantId);
110
- let i = yield* O.stores.customers.getByTenant(t.tenantId), a = i?.providerCustomerId ?? (yield* x(d.ensureCustomer({ tenantId: t.tenantId }), m));
111
- i === null && (yield* O.stores.customers.upsert({
112
- tenantId: t.tenantId,
113
- provider: d.name,
114
- providerCustomerId: a
115
- }));
116
- let o = (yield* x(d.listSubscriptions({ providerCustomerId: a }), m)).filter((e) => v.has(e.status));
117
- if (o.length > 0) {
118
- for (let e of o) yield* I(t.tenantId, e, w());
119
- return yield* F(t.tenantId);
194
+ if (c === null) return yield* l("The selected plan has no provider checkout price.");
195
+ if (!Number.isSafeInteger(o) || o < 1) return yield* l("Seat quantity must be a positive integer.");
196
+ yield* j(n, "checkout"), yield* s(), yield* M(i, "successUrl"), yield* M(a, "cancelUrl");
197
+ let u = yield* N.stores.subscriptions.getByTenant(n);
198
+ if (u !== null && E.has(u.status)) return yield* W(n);
199
+ let d = [...new Set(b.meteredPriceIdsFor(r))].sort(), f = S?.adjustableQuantity === void 0 ? void 0 : {
200
+ min: S.adjustableQuantity.min,
201
+ max: S.adjustableQuantity.max
202
+ }, p = S?.trialDays, m = yield* y.tryPromise({
203
+ try: async () => {
204
+ let e = new TextEncoder().encode(JSON.stringify([
205
+ n,
206
+ v.name,
207
+ r,
208
+ c,
209
+ d,
210
+ o,
211
+ i,
212
+ a,
213
+ f ?? null,
214
+ p ?? null
215
+ ])), t = await globalThis.crypto.subtle.digest("SHA-256", e);
216
+ return [...new Uint8Array(t)].map((e) => e.toString(16).padStart(2, "0")).join("");
217
+ },
218
+ catch: () => new e({
219
+ source: "billing",
220
+ message: "Checkout request identity could not be established.",
221
+ transient: !1
222
+ })
223
+ });
224
+ for (let e = 0; e < 4; e++) {
225
+ yield* j(n, "checkout"), yield* s(), yield* R(n);
226
+ let e = new Date(A());
227
+ if (!Number.isFinite(e.getTime())) return yield* l("The checkout clock could not be confirmed.");
228
+ let t = yield* N.stores.checkouts.reserve({
229
+ tenantId: n,
230
+ provider: v.name,
231
+ requestDigest: m,
232
+ plan: r,
233
+ quantity: o,
234
+ createdAt: e,
235
+ expiresAt: /* @__PURE__ */ new Date(Math.floor((e.getTime() + 36e5) / 1e3) * 1e3)
236
+ });
237
+ if (t.provider !== v.name) return yield* l("The checkout provider changed. Its previous operation needs reconciliation.");
238
+ let u = t.providerCustomerId, h = yield* z(n);
239
+ if (h !== null && (h.provider !== v.name || u !== null && h.providerCustomerId !== u)) return yield* l("The checkout customer binding changed. Its operation needs reconciliation.");
240
+ if (t.requestDigest === m && u === null) {
241
+ yield* j(n, "checkout"), yield* s(), u = h?.providerCustomerId ?? (yield* O(s().pipe(y.andThen(j(n, "checkout")), y.andThen(v.ensureCustomer({ tenantId: n }))), x));
242
+ let e = yield* N.stores.checkouts.patch(t.id, { providerCustomerId: u });
243
+ if (e.active !== 1 || e.providerCustomerId !== u) return yield* l("Checkout ownership changed during customer resolution.");
244
+ let r = yield* z(n);
245
+ if (r !== null && (r.provider !== v.name || r.providerCustomerId !== u)) return yield* l("The current customer binding requires reconciliation.");
246
+ yield* s(), r === null && (yield* B(n, u));
247
+ }
248
+ if (u !== null) {
249
+ let e = yield* z(n);
250
+ if (e === null) yield* B(n, u);
251
+ else if (e.providerCustomerId !== u) return yield* l("Checkout customer custody differs from the retained operation.");
252
+ }
253
+ let g = yield* O(v.findCheckoutSession({
254
+ tenantId: n,
255
+ operationId: t.id,
256
+ ...u === null ? {} : { providerCustomerId: u }
257
+ }), x), _ = g.receipt;
258
+ if (_ !== null) {
259
+ if (_.operationId !== t.id || _.tenantId !== n || _.providerCustomerId === null || u !== null && _.providerCustomerId !== u || _.expiresAt.getTime() !== t.expiresAt.getTime() || _.id === "") return yield* l("The provider checkout receipt does not match its retained operation.");
260
+ if (u === null && (u = _.providerCustomerId, yield* N.stores.checkouts.patch(t.id, { providerCustomerId: u })), _.status === "expired") {
261
+ yield* N.stores.checkouts.patch(t.id, {
262
+ phase: "expired",
263
+ providerSessionId: _.id
264
+ });
265
+ continue;
266
+ }
267
+ if (_.status === "complete") {
268
+ if (yield* N.stores.checkouts.patch(t.id, {
269
+ phase: "completed",
270
+ providerSessionId: _.id
271
+ }), _.providerSubscriptionId === null) return yield* l("Completed checkout settlement is not yet confirmed.");
272
+ if ((yield* O(v.fetchSubscription(_.providerSubscriptionId), x))?.status === "canceled") {
273
+ yield* N.stores.checkouts.patch(t.id, { phase: "retired" });
274
+ continue;
275
+ }
276
+ return yield* W(n);
277
+ }
278
+ let e = yield* N.stores.checkouts.patch(t.id, {
279
+ phase: "ready",
280
+ providerSessionId: _.id
281
+ });
282
+ return t.requestDigest === m ? _.url === null || g.observedAt !== null && g.observedAt.getTime() >= _.expiresAt.getTime() ? yield* l("The current checkout URL could not be confirmed.") : e.active !== 1 || e.phase !== "ready" ? yield* l("Checkout state changed before its URL could be returned.") : (yield* j(n, "checkout", u ?? void 0), { url: _.url }) : yield* l("Another checkout is still open for this organization.");
283
+ }
284
+ if (g.observedAt !== null && g.observedAt.getTime() > t.expiresAt.getTime()) {
285
+ yield* N.stores.checkouts.patch(t.id, { phase: "expired" });
286
+ continue;
287
+ }
288
+ if (t.requestDigest !== m) return yield* l("An earlier checkout request is unresolved. Check its current state first.");
289
+ if (u === null || A().getTime() >= t.expiresAt.getTime()) return yield* l("The earlier checkout needs provider deadline/receipt reconciliation.");
290
+ let b = (yield* O(v.listSubscriptions({ providerCustomerId: u }), x)).filter((e) => E.has(e.status));
291
+ if (b.length > 0) {
292
+ for (let e of b) yield* G(n, e, A());
293
+ return yield* W(n);
294
+ }
295
+ let S = yield* N.stores.checkouts.patch(t.id, { phase: "creating" });
296
+ if (S.active !== 1 || S.phase === "completed") return yield* l("Checkout state changed before provider dispatch.");
297
+ yield* j(n, "checkout", u ?? void 0), yield* s(), yield* O(s().pipe(y.andThen(j(n, "checkout", u ?? void 0)), y.andThen(v.expireOpenCheckouts({
298
+ providerCustomerId: u,
299
+ exceptOperationId: t.id
300
+ }))), x), yield* j(n, "checkout", u ?? void 0), yield* s();
301
+ let C = yield* O(s().pipe(y.andThen(j(n, "checkout", u ?? void 0)), y.andThen(v.createCheckoutSession({
302
+ tenantId: n,
303
+ priceId: c,
304
+ meteredPriceIds: d,
305
+ successUrl: i,
306
+ cancelUrl: a,
307
+ quantity: o,
308
+ providerCustomerId: u,
309
+ idempotencyKey: t.id,
310
+ expiresAt: t.expiresAt,
311
+ ...f === void 0 ? {} : { adjustableQuantity: f },
312
+ ...p === void 0 ? {} : { trialDays: p }
313
+ }))), x);
314
+ if (C.operationId !== t.id || C.tenantId !== n || C.providerCustomerId !== u || C.expiresAt.getTime() !== t.expiresAt.getTime() || !C.id) return yield* l("Checkout creation returned an inconsistent provider receipt.");
315
+ if (C.status === "expired") {
316
+ yield* N.stores.checkouts.patch(t.id, {
317
+ phase: "expired",
318
+ providerSessionId: C.id
319
+ });
320
+ continue;
321
+ }
322
+ if (C.status === "complete") return yield* N.stores.checkouts.patch(t.id, {
323
+ phase: "completed",
324
+ providerSessionId: C.id
325
+ }), yield* W(n);
326
+ if (C.url === null) return yield* l("Checkout creation returned no confirmed URL.");
327
+ let w = yield* N.stores.checkouts.patch(t.id, {
328
+ phase: "ready",
329
+ providerSessionId: C.id
330
+ });
331
+ return w.active !== 1 || w.phase !== "ready" ? yield* l("Checkout state changed before its URL could be returned.") : (yield* j(n, "checkout", u ?? void 0), { url: C.url });
120
332
  }
121
- return yield* x(d.expireOpenCheckouts({ providerCustomerId: a }), m), yield* x(d.createCheckoutSession({
122
- tenantId: t.tenantId,
123
- priceId: n,
124
- meteredPriceIds: p.meteredPriceIdsFor(t.plan),
125
- successUrl: t.successUrl,
126
- cancelUrl: t.cancelUrl,
127
- providerCustomerId: a,
128
- ...t.quantity === void 0 ? {} : { quantity: t.quantity },
129
- ...h?.adjustableQuantity === void 0 ? {} : { adjustableQuantity: h.adjustableQuantity },
130
- ...h?.trialDays === void 0 ? {} : { trialDays: h.trialDays }
131
- }), m);
132
- }), R = (t, n) => f.gen(function* () {
133
- yield* T(t, "portal"), yield* D(n, "returnUrl");
134
- let r = yield* O.stores.customers.getByTenant(t);
135
- return r === null ? yield* f.fail(new e({
333
+ return yield* l("Checkout operations changed concurrently. Check current billing state before retrying.");
334
+ }), me = (t, n) => y.gen(function* () {
335
+ yield* j(t, "portal"), yield* M(n, "returnUrl");
336
+ let r = yield* z(t);
337
+ return r === null ? yield* y.fail(new e({
136
338
  source: "billing",
137
339
  message: `no provider customer linked for tenant '${t}'`,
138
340
  transient: !1
139
- })) : yield* x(d.createPortalSession({
341
+ })) : yield* O(v.createPortalSession({
140
342
  tenantId: t,
141
343
  providerCustomerId: r.providerCustomerId,
142
344
  returnUrl: n
143
- }), m);
144
- }), z = (e) => f.gen(function* () {
145
- if (k.resolveRecipient !== void 0) {
146
- let t = yield* k.resolveRecipient(e);
345
+ }), x);
346
+ }), he = (e) => y.gen(function* () {
347
+ if (P.resolveRecipient !== void 0) {
348
+ let t = yield* P.resolveRecipient(e);
147
349
  if (t !== null) return t;
148
350
  }
149
- if (d.customerEmail === void 0) return null;
150
- let t = yield* O.stores.customers.getByTenant(e);
151
- return t === null ? null : yield* d.customerEmail(t.providerCustomerId);
152
- }), B = (e) => {
153
- let t = k.portalReturnUrl;
154
- return t === void 0 ? f.succeed(null) : R(e, t).pipe(f.map((e) => e.url), f.catch(() => f.succeed(null)));
155
- }, V = (e, t, n, r, i) => f.gen(function* () {
156
- let o = yield* z(e.tenantId).pipe(f.catch(() => f.succeed(null))), s = yield* B(e.tenantId), c = t.lockedSince !== null, l = a({
351
+ if (v.customerEmail === void 0) return null;
352
+ let t = yield* z(e);
353
+ return t === null ? null : yield* v.customerEmail(t.providerCustomerId);
354
+ }), ge = (e) => {
355
+ let t = P.portalReturnUrl;
356
+ return t === void 0 ? y.succeed(null) : me(e, t).pipe(y.map((e) => e.url), y.catch(() => y.succeed(null)));
357
+ }, _e = (e, t, n, r, i) => y.gen(function* () {
358
+ let a = yield* he(e.tenantId).pipe(y.catch(() => y.succeed(null))), o = yield* ge(e.tenantId), s = t.lockedSince !== null, c = f({
157
359
  stepId: n,
158
- locked: c,
360
+ locked: s,
159
361
  graceEndsAt: i,
160
- portalUrl: s
161
- }), u = {
362
+ portalUrl: o
363
+ }), l = {
162
364
  stepId: n,
163
365
  tenantId: e.tenantId,
164
- to: o,
366
+ to: a,
165
367
  plan: e.plan,
166
368
  status: e.status,
167
369
  pastDueSince: r,
168
370
  graceEndsAt: i,
169
- locked: c,
170
- lockout: k.lockout,
171
- portalUrl: s,
172
- ...l
371
+ locked: s,
372
+ lockout: P.lockout,
373
+ portalUrl: o,
374
+ ...c
173
375
  };
174
- g.info("billing dunning notice", {
376
+ C.info("billing dunning notice", {
175
377
  tenant: e.tenantId,
176
378
  step: n,
177
- locked: c,
379
+ locked: s,
178
380
  graceEndsAt: i.toISOString(),
179
- delivered: k.notify !== void 0 && o !== null
381
+ delivered: P.notify !== void 0 && a !== null
180
382
  });
181
- let d = k.notify;
182
- d !== void 0 && (yield* d(u).pipe(f.catchCause((t) => f.sync(() => {
183
- g.warn("billing dunning notify failed", {
383
+ let u = P.notify;
384
+ u !== void 0 && (yield* u(l).pipe(y.catchCause((t) => y.sync(() => {
385
+ C.warn("billing dunning notify failed", {
184
386
  tenant: e.tenantId,
185
387
  step: n,
186
388
  cause: String(t)
187
389
  });
188
390
  }))));
189
- }), H = (e) => f.gen(function* () {
190
- let t = yield* O.stores.subscriptions.getByTenant(e);
191
- if (t === null) return c(null, k, w(), e);
192
- let n = yield* d.fetchSubscription(t.providerSubscriptionId).pipe(f.catch((t) => f.sync(() => (g.warn("billing dunning reconcile: provider unreachable, not escalating", {
391
+ }), K = (e) => y.gen(function* () {
392
+ yield* L(e);
393
+ let t = yield* N.stores.subscriptions.getByTenant(e);
394
+ if (t === null) return h(null, P, A(), e);
395
+ let n = yield* v.fetchSubscription(t.providerSubscriptionId).pipe(y.catch((t) => y.sync(() => (C.warn("billing dunning reconcile: provider unreachable, not escalating", {
193
396
  tenant: e,
194
397
  source: t.source,
195
398
  message: t.message
196
399
  }), null)))), r = t;
197
400
  if (n !== null) {
198
- let i = w(), a = n.status === "pastDue" ? t.status === "pastDue" && t.pastDueSince !== null ? t.pastDueSince : i : null;
199
- yield* O.stores.subscriptions.patchByTenant(e, {
401
+ let i = A(), a = n.status === "pastDue" ? t.status === "pastDue" && t.pastDueSince !== null ? t.pastDueSince : i : null;
402
+ yield* N.stores.subscriptions.patchByTenant(e, {
200
403
  status: n.status,
201
404
  ...n.plan === null ? {} : { plan: n.plan },
202
405
  quantity: n.quantity,
@@ -215,32 +418,32 @@ var g = h({ scope: "@voltro/plugin-billing" }), _ = 3, v = /* @__PURE__ */ new S
215
418
  statusEventAt: i
216
419
  };
217
420
  }
218
- let a = c(r, k, w(), e);
219
- if (!k.enabled || r.status !== "pastDue" || r.pastDueSince === null) return a;
220
- let l = r.pastDueSince, u = s(l), p = a.graceEndsAt ?? l, m = o(k.steps, l, w()).map((e) => e.id);
221
- a.lockedSince !== null && m.push(i);
222
- for (let t of m) (yield* O.stores.dunningNotices.claim(e, u, t, w())) && (yield* V(r, a, t, l, p));
223
- return a;
224
- }), re = () => f.gen(function* () {
225
- let e = yield* O.stores.subscriptions.listByStatus("pastDue"), t = 0;
226
- for (let n of e) yield* H(n.tenantId).pipe(f.catch((e) => f.sync(() => {
227
- g.warn("billing dunning sweep: tenant failed", {
421
+ let i = h(r, P, A(), e);
422
+ if (!P.enabled || r.status !== "pastDue" || r.pastDueSince === null) return i;
423
+ let a = r.pastDueSince, o = m(a), s = i.graceEndsAt ?? a, c = p(P.steps, a, A()).map((e) => e.id);
424
+ i.lockedSince !== null && c.push(d);
425
+ for (let t of c) (yield* N.stores.dunningNotices.claim(e, o, t, A())) && (yield* _e(r, i, t, a, s));
426
+ return i;
427
+ }), ve = () => y.gen(function* () {
428
+ let e = yield* N.stores.subscriptions.listByStatus("pastDue"), t = 0;
429
+ for (let n of e) yield* K(n.tenantId).pipe(y.catch((e) => y.sync(() => {
430
+ C.warn("billing dunning sweep: tenant failed", {
228
431
  tenant: n.tenantId,
229
432
  message: e.message
230
433
  });
231
434
  }))), t += 1;
232
435
  return t;
233
- }), U = (e) => {
234
- if (!k.enabled) return f.void;
235
- if (e._tag === "invoicePaymentFailed") return H(e.tenantId).pipe(f.asVoid);
236
- if (e._tag !== "invoicePaid" && e._tag !== "subscriptionUpserted") return f.void;
436
+ }), q = (e) => {
437
+ if (!P.enabled) return y.void;
438
+ if (e._tag === "invoicePaymentFailed") return K(e.tenantId).pipe(y.asVoid);
439
+ if (e._tag !== "invoicePaid" && e._tag !== "subscriptionUpserted") return y.void;
237
440
  let t = e.tenantId;
238
- return O.stores.subscriptions.getByTenant(t).pipe(f.flatMap((e) => e !== null && (e.status === "pastDue" || e.pastDueSince !== null) ? H(t).pipe(f.asVoid) : f.void));
239
- }, ie = (e) => {
441
+ return N.stores.subscriptions.getByTenant(t).pipe(y.flatMap((e) => e !== null && (e.status === "pastDue" || e.pastDueSince !== null) ? K(t).pipe(y.asVoid) : y.void));
442
+ }, ye = (e) => {
240
443
  switch (e._tag) {
241
- case "subscriptionUpserted": return O.stores.subscriptions.upsert({
444
+ case "subscriptionUpserted": return N.stores.subscriptions.upsert({
242
445
  tenantId: e.tenantId,
243
- provider: d.name,
446
+ provider: v.name,
244
447
  providerSubscriptionId: e.providerSubscriptionId,
245
448
  plan: e.plan,
246
449
  status: e.status,
@@ -251,58 +454,71 @@ var g = h({ scope: "@voltro/plugin-billing" }), _ = 3, v = /* @__PURE__ */ new S
251
454
  pastDueSince: null,
252
455
  statusEventAt: e.occurredAt
253
456
  });
254
- case "subscriptionCanceled": return O.stores.subscriptions.markCanceled(e.providerSubscriptionId);
255
- case "invoicePaid": return O.stores.invoices.upsert({
256
- tenantId: e.tenantId,
257
- provider: d.name,
258
- providerInvoiceId: e.providerInvoiceId,
259
- amountMinor: e.amountMinor,
260
- currency: e.currency,
261
- status: "paid",
262
- statusEventAt: e.occurredAt
263
- });
264
- case "invoicePaymentFailed": return O.stores.invoices.upsert({
265
- tenantId: e.tenantId,
266
- provider: d.name,
267
- providerInvoiceId: e.providerInvoiceId,
268
- amountMinor: e.amountMinor,
269
- currency: e.currency,
270
- status: "open",
271
- statusEventAt: e.occurredAt
272
- });
273
- case "customerLinked": return O.stores.customers.upsert({
274
- tenantId: e.tenantId,
275
- provider: d.name,
276
- providerCustomerId: e.providerCustomerId
277
- });
278
- case "customerUnlinked": return O.stores.customers.removeByProviderCustomerId(e.providerCustomerId).pipe(f.asVoid);
457
+ case "subscriptionCanceled": return N.stores.subscriptions.markCanceled(e.providerSubscriptionId);
458
+ case "customerLinked": return B(e.tenantId, e.providerCustomerId).pipe(y.asVoid);
279
459
  }
280
- }, ae = (e) => ie(e).pipe(f.andThen(U(e))), W = (t, n) => f.gen(function* () {
281
- yield* T(t, "change"), yield* Q(t);
282
- let r = yield* O.stores.subscriptions.getByTenant(t);
283
- if (r === null) return yield* f.fail(new e({
460
+ }, be = (e) => {
461
+ if (e._tag === "customerUnlinked") return y.gen(function* () {
462
+ let t = yield* I(), n = v.fetchCustomerSnapshot?.bind(v);
463
+ if (n === void 0) return yield* y.fail(s("Positive customer deletion evidence is required."));
464
+ let r = yield* n({
465
+ account: t.account,
466
+ providerCustomerId: e.providerCustomerId
467
+ }), i = yield* N.stores.customers.retire(t, r);
468
+ return {
469
+ ...e,
470
+ tenantId: i?.tenantId ?? null
471
+ };
472
+ });
473
+ if (e._tag !== "invoicePaid" && e._tag !== "invoicePaymentFailed") return (e._tag === "customerLinked" ? y.void : z(e.tenantId).pipe(y.flatMap((e) => e === null ? y.fail(s("A qualified current customer is required before subscription events.")) : y.void))).pipe(y.andThen(ye(e)), y.andThen(q(e)), y.as(e));
474
+ let t = {
475
+ ...e,
476
+ occurredAt: e.occurredAt === null ? null : new Date(e.occurredAt)
477
+ };
478
+ return y.gen(function* () {
479
+ let e = t.providerCustomerId;
480
+ if (typeof e != "string" || e.length === 0 || e.trim() !== e) return yield* y.fail(s("An exact provider customer identity is required."));
481
+ let n = yield* N.stores.customers.getByProviderCustomerId(e, yield* I());
482
+ if (n === null) return yield* y.fail(s("The invoice customer must be linked before reconciliation.", !0));
483
+ if (n.provider !== v.name) return yield* y.fail(s("The invoice customer belongs to a different provider."));
484
+ let r = n.tenantId;
485
+ yield* J(r, t.providerInvoiceId, e);
486
+ let i = {
487
+ _tag: t._tag,
488
+ tenantId: r,
489
+ providerInvoiceId: t.providerInvoiceId,
490
+ amountMinor: t.amountMinor,
491
+ currency: t.currency,
492
+ occurredAt: t.occurredAt
493
+ }, a = yield* N.stores.customers.getByTenant(r);
494
+ return n.retiredAt === null && a?.id === n.id && (yield* q(i)), i;
495
+ });
496
+ }, xe = (t, n) => y.gen(function* () {
497
+ yield* j(t, "change"), yield* Z(t);
498
+ let r = yield* N.stores.subscriptions.getByTenant(t);
499
+ if (r === null) return yield* y.fail(new e({
284
500
  source: "billing",
285
501
  message: `no subscription for tenant '${t}' to change`,
286
502
  transient: !1
287
503
  }));
288
504
  let i = n.plan ?? r.plan, a = n.quantity ?? r.quantity;
289
- if (n.plan !== void 0 && !p.has(i)) return yield* f.fail(new e({
505
+ if (n.plan !== void 0 && !b.has(i)) return yield* y.fail(new e({
290
506
  source: "billing",
291
507
  message: `unknown plan '${i}' — not in the plan registry`,
292
508
  transient: !1
293
509
  }));
294
- if (!Number.isInteger(a) || a <= 0) return yield* f.fail(new e({
510
+ if (!Number.isInteger(a) || a <= 0) return yield* y.fail(new e({
295
511
  source: "billing",
296
512
  message: `seat quantity must be a positive integer, got ${a}`,
297
513
  transient: !1
298
514
  }));
299
- let o = yield* x(d.updateSubscription({
515
+ let o = yield* O(v.updateSubscription({
300
516
  providerSubscriptionId: r.providerSubscriptionId,
301
- ...n.plan === void 0 ? {} : { priceId: p.priceIdFor(i) ?? i },
517
+ ...n.plan === void 0 ? {} : { priceId: b.priceIdFor(i) ?? i },
302
518
  ...n.quantity === void 0 ? {} : { quantity: a },
303
519
  proration: "prorate"
304
- }), m);
305
- return yield* O.stores.subscriptions.patchByTenant(t, {
520
+ }), x);
521
+ return yield* N.stores.subscriptions.patchByTenant(t, {
306
522
  ...n.plan === void 0 ? {} : { plan: i },
307
523
  quantity: o.quantity,
308
524
  ...o.currentPeriodStart === null ? {} : { currentPeriodStart: o.currentPeriodStart },
@@ -314,19 +530,19 @@ var g = h({ scope: "@voltro/plugin-billing" }), _ = 3, v = /* @__PURE__ */ new S
314
530
  prorationMinor: o.prorationMinor,
315
531
  currency: o.currency
316
532
  };
317
- }), oe = (t, n) => f.gen(function* () {
318
- let r = yield* O.stores.subscriptions.getByTenant(t);
319
- if (r === null) return yield* f.fail(new e({
533
+ }), Se = (t, n) => y.gen(function* () {
534
+ let r = yield* N.stores.subscriptions.getByTenant(t);
535
+ if (r === null) return yield* y.fail(new e({
320
536
  source: "billing",
321
537
  message: `no subscription for tenant '${t}' to preview`,
322
538
  transient: !1
323
539
  }));
324
- let i = n.plan ?? r.plan, a = yield* x(d.previewSubscriptionChange({
540
+ let i = n.plan ?? r.plan, a = yield* O(v.previewSubscriptionChange({
325
541
  providerSubscriptionId: r.providerSubscriptionId,
326
- ...n.plan === void 0 ? {} : { priceId: p.priceIdFor(i) ?? i },
542
+ ...n.plan === void 0 ? {} : { priceId: b.priceIdFor(i) ?? i },
327
543
  ...n.quantity === void 0 ? {} : { quantity: n.quantity },
328
544
  proration: "prorate"
329
- }), m);
545
+ }), x);
330
546
  return {
331
547
  tenantId: t,
332
548
  plan: i,
@@ -334,32 +550,111 @@ var g = h({ scope: "@voltro/plugin-billing" }), _ = 3, v = /* @__PURE__ */ new S
334
550
  prorationMinor: a.prorationMinor,
335
551
  currency: a.currency
336
552
  };
337
- }), se = (e) => f.gen(function* () {
338
- let t = yield* O.stores.customers.getByTenant(e);
339
- return t === null ? [] : yield* x(d.listInvoices({ providerCustomerId: t.providerCustomerId }), m);
340
- }), ce = (t, n) => f.gen(function* () {
553
+ }), Ce = (e) => y.gen(function* () {
554
+ let t = yield* z(e);
555
+ return t === null ? [] : yield* O(v.listInvoices({ providerCustomerId: t.providerCustomerId }), x);
556
+ }), we = (t, n, r = N.stores) => y.gen(function* () {
557
+ let a = v.fetchInvoiceSnapshot?.bind(v), c = v.fetchAccountIdentity?.bind(v);
558
+ if (a === void 0 || c === void 0) return yield* y.fail(s("Complete account-owned invoice evidence is unsupported."));
559
+ if (N.stores !== r) return yield* y.fail(s("Billing stores changed before invoice observation.", !0));
560
+ let l = yield* I();
561
+ if (l.provider !== t.provider || !i(l.account, t.account)) return yield* y.fail(s("The invoice account differs from retained custody."));
562
+ let u = yield* c();
563
+ if (!i(u, t.account)) return yield* y.fail(s("The provider account changed before invoice observation."));
564
+ let d = yield* O(a({
565
+ providerCustomerId: t.providerCustomerId,
566
+ providerInvoiceId: n
567
+ }), x);
568
+ yield* y.try({
569
+ try: () => {
570
+ if (o(d), d.providerInvoiceId !== n || d.providerCustomerId !== t.providerCustomerId || d.livemode !== t.account.livemode) throw s("The invoice does not match retained account/customer custody.");
571
+ },
572
+ catch: (t) => t instanceof e ? t : s("The provider invoice could not be confirmed.")
573
+ });
574
+ let f = yield* c(), p = yield* r.customers.getByProviderCustomerId(t.providerCustomerId, l);
575
+ return N.stores !== r || !i(f, t.account) || p === null || p.id !== t.id || p.tenantId !== t.tenantId ? yield* y.fail(s("Invoice account/customer custody changed during observation.", !0)) : d;
576
+ }), Te = (t, n, a) => y.gen(function* () {
577
+ let o = N.stores;
578
+ if (!t || t.trim() !== t) return yield* y.fail(u("Exact tenant/customer identities are required."));
579
+ let s = yield* y.try({
580
+ try: () => {
581
+ let e = a?.cursor, t = e === void 0 ? void 0 : Object.freeze({
582
+ ...r(e),
583
+ providerCustomerId: e.providerCustomerId,
584
+ afterInvoiceId: e.afterInvoiceId
585
+ }), n = a?.limit;
586
+ return Object.freeze({
587
+ ...t === void 0 ? {} : { cursor: t },
588
+ ...n === void 0 ? {} : { limit: n }
589
+ });
590
+ },
591
+ catch: (t) => t instanceof e ? t : u()
592
+ }), d = yield* I(), f = yield* y.try({
593
+ try: () => c({
594
+ account: d.account,
595
+ providerCustomerId: n,
596
+ ...s
597
+ }),
598
+ catch: (t) => t instanceof e ? t : u()
599
+ }), p = yield* o.customers.getByProviderCustomerId(f.providerCustomerId, d);
600
+ if (p === null || p.tenantId !== t || p.provider !== v.name) return yield* y.fail(u("Positive current or retained customer custody is required."));
601
+ let m = v.listInvoiceHistory?.bind(v), h = v.fetchAccountIdentity?.bind(v);
602
+ if (m === void 0 || h === void 0) return yield* y.fail(u("Complete account-owned invoice history is unsupported."));
603
+ let g = yield* h();
604
+ if (!i(g, p.account)) return yield* y.fail(u("The provider account differs before invoice history access."));
605
+ let _ = yield* O(m(f), x), b = yield* y.try({
606
+ try: () => l(f, _),
607
+ catch: (t) => t instanceof e ? t : u()
608
+ }), S = yield* h(), C = yield* o.customers.getByProviderCustomerId(f.providerCustomerId, d);
609
+ return N.stores !== o || !i(S, p.account) || C === null || C.id !== p.id || C.tenantId !== t ? yield* y.fail(u("Invoice history account/customer custody changed during observation.", !0)) : b;
610
+ }), Ee = (e, t) => y.gen(function* () {
611
+ if (!e || e.trim() !== e || !t || t.trim() !== t) return yield* y.fail(s("Exact tenant/invoice identities are required."));
612
+ let n = yield* z(e);
613
+ return n === null ? yield* y.fail(s("A qualified current customer is required.")) : yield* we(n, t);
614
+ }), J = (e, t, n) => y.gen(function* () {
615
+ let r = N.stores;
616
+ if (!e || e.trim() !== e || !t || t.trim() !== t) return yield* y.fail(s("Exact tenant/invoice identities are required."));
617
+ let i = yield* I();
618
+ for (let a = 0; a < 3; a++) {
619
+ if (N.stores !== r) return yield* y.fail(s("Billing stores changed during invoice reconciliation.", !0));
620
+ let a = n === void 0 ? yield* z(e) : yield* r.customers.getByProviderCustomerId(n, i);
621
+ if (a === null || a.tenantId !== e) return yield* y.fail(s("Positive current or retained customer custody is required.", !0));
622
+ let o = Object.freeze({
623
+ tenantId: e,
624
+ provider: i.provider,
625
+ ...i.account,
626
+ providerCustomerId: a.providerCustomerId,
627
+ providerInvoiceId: t
628
+ }), c = yield* r.invoices.get(o), l = yield* we(a, t, r);
629
+ if (N.stores !== r) return yield* y.fail(s("Billing stores changed before native invoice persistence.", !0));
630
+ if (!(yield* r.invoices.complete(o, c, l))) continue;
631
+ let u = yield* r.invoices.get(o);
632
+ return u === null || u.revision <= (c?.revision ?? 0) ? yield* y.fail(s("Invoice persistence could not be confirmed.", !0)) : u;
633
+ }
634
+ return yield* y.fail(s("Concurrent invoice observation requires retry.", !0));
635
+ }), De = (t, n) => y.gen(function* () {
341
636
  if (n === "free") {
342
- let e = yield* q(t);
637
+ let e = yield* Ae(t);
343
638
  return {
344
639
  tenantId: t,
345
640
  plan: e.plan,
346
641
  quantity: e.quantity,
347
642
  prorationMinor: 0,
348
- currency: p.currencyFor(e.plan)
643
+ currency: b.currencyFor(e.plan)
349
644
  };
350
645
  }
351
- return p.has(n) && p.priceIdFor(n) === null ? yield* f.fail(new e({
646
+ return b.has(n) && b.priceIdFor(n) === null ? yield* y.fail(new e({
352
647
  source: "billing",
353
648
  message: `plan '${n}' has no provider price — it is not a self-serve plan`,
354
649
  transient: !1
355
- })) : yield* W(t, { plan: n });
356
- }), G = (t, n) => O.stores.subscriptions.getByTenant(t).pipe(f.flatMap((r) => r !== null && u(r.status) ? f.succeed(r) : f.fail(new e({
650
+ })) : yield* xe(t, { plan: n });
651
+ }), Oe = (t, n) => N.stores.subscriptions.getByTenant(t).pipe(y.flatMap((r) => r !== null && _(r.status) ? y.succeed(r) : y.fail(new e({
357
652
  source: "billing",
358
653
  message: `no live subscription for tenant '${t}' to ${n}`,
359
654
  transient: !1
360
- })))), K = (e, t) => f.gen(function* () {
361
- let n = w();
362
- return yield* I(e.tenantId, {
655
+ })))), ke = (e, t) => y.gen(function* () {
656
+ let n = A();
657
+ return yield* G(e.tenantId, {
363
658
  ...t,
364
659
  providerSubscriptionId: e.providerSubscriptionId
365
660
  }, n), {
@@ -368,106 +663,198 @@ var g = h({ scope: "@voltro/plugin-billing" }), _ = 3, v = /* @__PURE__ */ new S
368
663
  plan: t.plan ?? e.plan,
369
664
  statusEventAt: n
370
665
  };
371
- }), q = (e) => f.gen(function* () {
372
- yield* T(e, "cancel"), yield* Q(e);
373
- let t = yield* G(e, "cancel"), n = yield* x(d.cancelSubscription({
666
+ }), Ae = (e) => y.gen(function* () {
667
+ yield* j(e, "cancel"), yield* Z(e);
668
+ let t = yield* Oe(e, "cancel"), n = yield* O(v.cancelSubscription({
374
669
  providerSubscriptionId: t.providerSubscriptionId,
375
670
  mode: "periodEnd"
376
- }), m);
377
- return yield* K(t, n);
378
- }), le = (e) => f.gen(function* () {
379
- yield* T(e, "resume"), yield* Q(e);
380
- let t = yield* G(e, "resume"), n = yield* x(d.resumeSubscription(t.providerSubscriptionId), m);
381
- return yield* K(t, n);
382
- }), J = (e) => f.gen(function* () {
383
- yield* P();
384
- let t = yield* O.stores.customers.getByTenant(e), n = yield* O.stores.subscriptions.listByTenant(e), r = new Set(n.filter((e) => u(e.status)).map((e) => e.providerSubscriptionId));
385
- if (t !== null) {
386
- let e = yield* x(d.listSubscriptions({ providerCustomerId: t.providerCustomerId }), m);
387
- for (let t of e) r.add(t.providerSubscriptionId);
671
+ }), x);
672
+ return yield* ke(t, n);
673
+ }), je = (e) => y.gen(function* () {
674
+ yield* j(e, "resume"), yield* Z(e);
675
+ let t = yield* Oe(e, "resume"), n = yield* O(v.resumeSubscription(t.providerSubscriptionId), x);
676
+ return yield* ke(t, n);
677
+ }), Y = (t) => y.gen(function* () {
678
+ yield* L(t), yield* R(t);
679
+ let n = () => new e({
680
+ source: "billing",
681
+ message: "Billing closure is pending provider confirmation. Retry reconciliation before completing account deletion.",
682
+ transient: !0
683
+ });
684
+ if (t.length === 0) return yield* y.fail(n());
685
+ yield* N.stores.closures.reserveAccount(t, v.name, A());
686
+ let r = yield* z(t), i = yield* N.stores.checkouts.getActive(t);
687
+ if (r !== null && r.provider !== v.name || i !== null && i.provider !== v.name || r !== null && i?.providerCustomerId !== null && i?.providerCustomerId !== void 0 && r.providerCustomerId !== i.providerCustomerId) return yield* y.fail(n());
688
+ let a = /* @__PURE__ */ new Set();
689
+ r !== null && a.add(r.providerCustomerId), i?.providerCustomerId && a.add(i.providerCustomerId);
690
+ for (let e of a) yield* O(v.expireOpenCheckouts({ providerCustomerId: e }), x);
691
+ let o = null;
692
+ if (i !== null) {
693
+ let e = yield* O(v.findCheckoutSession({
694
+ tenantId: t,
695
+ operationId: i.id,
696
+ ...i.providerCustomerId === null ? {} : { providerCustomerId: i.providerCustomerId }
697
+ }), x), r = e.receipt;
698
+ if (r === null) {
699
+ if (e.observedAt === null || !Number.isFinite(e.observedAt.getTime()) || e.observedAt.getTime() < i.expiresAt.getTime()) return yield* y.fail(n());
700
+ yield* N.stores.checkouts.patch(i.id, { phase: "expired" });
701
+ } else {
702
+ if (r.operationId !== i.id || r.tenantId !== t || !r.id || r.providerCustomerId === null || r.expiresAt.getTime() !== i.expiresAt.getTime() || i.providerCustomerId !== null && r.providerCustomerId !== i.providerCustomerId || i.providerSessionId !== null && r.id !== i.providerSessionId) return yield* y.fail(n());
703
+ if (a.add(r.providerCustomerId), yield* N.stores.checkouts.patch(i.id, {
704
+ providerCustomerId: r.providerCustomerId,
705
+ providerSessionId: r.id
706
+ }), r.status === "open") return yield* O(v.expireOpenCheckouts({ providerCustomerId: r.providerCustomerId }), x), yield* y.fail(n());
707
+ if (r.status === "expired") yield* N.stores.checkouts.patch(i.id, { phase: "expired" });
708
+ else {
709
+ if (r.providerSubscriptionId === null) return yield* y.fail(n());
710
+ o = r.providerSubscriptionId, yield* N.stores.checkouts.patch(i.id, { phase: "completed" });
711
+ }
712
+ }
388
713
  }
389
- let i = [];
390
- for (let t of r) {
391
- let n = yield* x(d.cancelSubscription({
392
- providerSubscriptionId: t,
393
- mode: "close"
394
- }), m);
395
- yield* I(e, {
396
- ...n,
397
- providerSubscriptionId: t
398
- }, w()), i.push(t);
714
+ yield* U(t), yield* N.stores.closures.sealUsage(t);
715
+ let s = yield* N.stores.usage.pending();
716
+ if (v.supportsMeteredUsage && s.some((e) => e.tenantId === t && e.used > e.reportedToProvider)) return yield* y.fail(V());
717
+ let c = yield* N.stores.subscriptions.listByTenant(t), l = new Set(c.filter((e) => _(e.status)).map((e) => e.providerSubscriptionId));
718
+ o !== null && l.add(o);
719
+ for (let e of a) {
720
+ let t = yield* O(v.listSubscriptions({ providerCustomerId: e }), x);
721
+ for (let e of t) l.add(e.providerSubscriptionId);
399
722
  }
400
- return g.info("billing account closed", {
401
- tenant: e,
402
- canceled: i.length
723
+ let u = [], d = !1;
724
+ for (let e of l) {
725
+ let r = yield* O(v.fetchSubscription(e), x);
726
+ if (r === null) {
727
+ d = !0;
728
+ continue;
729
+ }
730
+ let i = r;
731
+ if (r.status !== "canceled") {
732
+ let r = yield* N.stores.closures.getSubscription(t, e);
733
+ if (r === null) {
734
+ let n = yield* O(v.prepareSubscriptionClose(e), x);
735
+ r = yield* N.stores.closures.reserveSubscription({
736
+ tenantId: t,
737
+ provider: v.name,
738
+ providerSubscriptionId: e,
739
+ cancelAt: n.cancelAt,
740
+ createdAt: A()
741
+ });
742
+ }
743
+ if (r.provider !== v.name || r.providerSubscriptionId !== e || r.cancelAt === null) return yield* y.fail(n());
744
+ i = yield* O(v.cancelSubscription({
745
+ providerSubscriptionId: e,
746
+ mode: "close",
747
+ closeOperation: {
748
+ id: r.id,
749
+ cancelAt: r.cancelAt
750
+ }
751
+ }), x);
752
+ }
753
+ yield* G(t, {
754
+ ...i,
755
+ providerSubscriptionId: e
756
+ }, A()), i.status === "canceled" ? u.push(e) : d = !0;
757
+ }
758
+ for (let e of a) (yield* O(v.listSubscriptions({ providerCustomerId: e }), x)).some((e) => e.status !== "canceled") && (d = !0);
759
+ if (d) return yield* y.fail(n());
760
+ let f = yield* N.stores.usage.pending();
761
+ return v.supportsMeteredUsage && f.some((e) => e.tenantId === t && e.used > e.reportedToProvider) ? yield* y.fail(V()) : (i !== null && o !== null && (yield* N.stores.checkouts.patch(i.id, { phase: "retired" })), C.info("billing account closed", {
762
+ tenant: t,
763
+ canceled: u.length
403
764
  }), {
404
- tenantId: e,
405
- canceled: i
406
- };
407
- }), Y = (e) => {
408
- let n = t.isTenantClosed;
409
- return n === void 0 ? f.succeed(!1) : n(e).pipe(f.catch((t) => f.sync(() => (g.warn("billing: could not tell whether a tenant is closed — leaving it", {
410
- tenant: e,
411
- message: t.message
412
- }), !1))));
413
- }, X = (e, t) => f.gen(function* () {
414
- let n = w(), r = yield* x(d.listSubscriptions({ providerCustomerId: e }), m), i = yield* O.stores.customers.getByProviderCustomerId(e), a = i?.tenantId ?? t ?? r.find((e) => e.tenantId !== null)?.tenantId ?? null;
415
- if (a === null) return g.warn("billing sync: customer names no tenant — nothing to attribute it to", { customer: e }), {
765
+ tenantId: t,
766
+ canceled: u
767
+ });
768
+ }), Me = (e) => y.gen(function* () {
769
+ return (yield* N.stores.closures.getAccount(e)) === null ? t.isTenantClosed === void 0 ? !1 : yield* t.isTenantClosed(e) : !0;
770
+ }), Ne = (e) => Me(e).pipe(y.catch((t) => y.sync(() => (C.warn("billing: could not tell whether a tenant is closed — leaving it", {
771
+ tenant: e,
772
+ message: t.message
773
+ }), !1)))), X = (e) => y.gen(function* () {
774
+ let t = v.fetchSubscriptionSnapshot;
775
+ if (t === void 0) return yield* y.fail(s("Customer-qualified subscription observations are required."));
776
+ let n = yield* I(), r = v.fetchAccountIdentity;
777
+ if (r === void 0) return yield* y.fail(s("Provider account observations are required."));
778
+ let a = yield* r();
779
+ if (!i(a, n.account)) return yield* y.fail(s("The provider account changed before subscription observation."));
780
+ let o = yield* O(t(e), x), c = yield* r();
781
+ return !i(c, n.account) || o === null || o.providerSubscriptionId !== e || o.providerCustomerId === null ? yield* y.fail(s("A complete subscription observation in the retained provider account is required.")) : o;
782
+ }), Pe = (t, n) => y.gen(function* () {
783
+ let r = A(), i = yield* N.stores.customers.getByProviderCustomerId(t, yield* I());
784
+ if (i !== null && i.retiredAt !== null) {
785
+ let e = [];
786
+ for (let n of yield* N.stores.subscriptions.listByTenant(i.tenantId)) {
787
+ let a = yield* X(n.providerSubscriptionId);
788
+ if (a.providerCustomerId !== t) continue;
789
+ if (a.status !== "canceled") return yield* y.fail(s("A retired customer still has an unsettled provider subscription."));
790
+ let o = yield* N.stores.customers.getByProviderCustomerId(t, yield* I());
791
+ if (o === null || o.id !== i.id || o.tenantId !== i.tenantId || o.retiredAt === null) return yield* y.fail(s("Retired customer custody changed during reconciliation."));
792
+ yield* G(i.tenantId, a, r), e.push(Q(i.tenantId, a, r));
793
+ }
794
+ for (let t of e) yield* q(t);
795
+ return {
796
+ tenantId: i.tenantId,
797
+ events: e
798
+ };
799
+ }
800
+ let a = yield* O(v.listSubscriptions({ providerCustomerId: t }), x);
801
+ if (a.some((e) => e.providerCustomerId !== t)) return yield* y.fail(s("The subscription listing names another customer."));
802
+ let o = i?.tenantId ?? n ?? a.find((e) => e.tenantId !== null)?.tenantId ?? null;
803
+ if (o === null) return C.warn("billing sync: customer names no tenant — nothing to attribute it to", { customer: t }), {
416
804
  tenantId: null,
417
805
  events: []
418
806
  };
419
- let o = [];
420
- i === null && (yield* O.stores.customers.upsert({
421
- tenantId: a,
422
- provider: d.name,
423
- providerCustomerId: e
424
- }), o.push({
425
- _tag: "customerLinked",
426
- tenantId: a,
427
- providerCustomerId: e,
428
- occurredAt: n
429
- })), r.some((e) => v.has(e.status) && e.cancelAt === null) && (yield* Y(a)) && (g.error("billing sync: a closed tenant still has a paying subscription — closing it", { tenant: a }), yield* J(a), r = yield* x(d.listSubscriptions({ providerCustomerId: e }), m));
430
- for (let e of r) if (yield* I(a, e, n), o.push($(a, e, n)), e.plan !== null && v.has(e.status)) {
431
- let t = p.meteredPriceIdsFor(e.plan).filter((t) => !e.priceIds.includes(t));
432
- t.length > 0 && (yield* x(d.addSubscriptionPrices({
807
+ let c = [], l = yield* z(o);
808
+ if (i !== null && i.provider !== v.name || l !== null && (l.provider !== v.name || l.providerCustomerId !== t)) return yield* y.fail(new e({
809
+ source: "billing.syncCustomer",
810
+ message: "Customer discovery cannot replace existing native billing custody.",
811
+ transient: !1
812
+ }));
813
+ if (i === null) {
814
+ if (yield* Me(o)) return yield* y.fail(s("Closed tenant customer custody requires reconciliation."));
815
+ yield* B(o, t), c.push({
816
+ _tag: "customerLinked",
817
+ tenantId: o,
818
+ providerCustomerId: t,
819
+ occurredAt: r
820
+ });
821
+ }
822
+ a.some((e) => T.has(e.status) && e.cancelAt === null) && (yield* Ne(o)) && (C.error("billing sync: a closed tenant still has a paying subscription — closing it", { tenant: o }), yield* Y(o), a = yield* O(v.listSubscriptions({ providerCustomerId: t }), x));
823
+ for (let e of a) if (yield* G(o, e, r), c.push(Q(o, e, r)), e.plan !== null && T.has(e.status)) {
824
+ let t = b.meteredPriceIdsFor(e.plan).filter((t) => !e.priceIds.includes(t));
825
+ t.length > 0 && (yield* O(v.addSubscriptionPrices({
433
826
  providerSubscriptionId: e.providerSubscriptionId,
434
827
  priceIds: t
435
- }), m));
828
+ }), x));
436
829
  }
437
- let s = new Set(r.map((e) => e.providerSubscriptionId));
438
- for (let e of yield* O.stores.subscriptions.listByTenant(a)) {
439
- if (!u(e.status) || s.has(e.providerSubscriptionId)) continue;
440
- let t = yield* x(d.fetchSubscription(e.providerSubscriptionId), m), r = t === null ? {
441
- ...e,
442
- status: "canceled",
443
- providerSubscriptionId: e.providerSubscriptionId
444
- } : {
445
- ...t,
446
- providerSubscriptionId: e.providerSubscriptionId
447
- };
448
- yield* I(a, r, n), o.push($(a, r, n));
830
+ let u = new Set(a.map((e) => e.providerSubscriptionId));
831
+ for (let e of yield* N.stores.subscriptions.listByTenant(o)) {
832
+ if (!_(e.status) || u.has(e.providerSubscriptionId)) continue;
833
+ let n = yield* X(e.providerSubscriptionId);
834
+ if (n.providerCustomerId !== t) return yield* y.fail(s("The subscription belongs to another customer."));
835
+ yield* G(o, n, r), c.push(Q(o, n, r));
449
836
  }
450
- for (let e of o) yield* U(e);
837
+ for (let e of c) yield* q(e);
451
838
  return {
452
- tenantId: a,
453
- events: o
839
+ tenantId: o,
840
+ events: c
454
841
  };
455
- }), Z = (e) => f.forEach(e, (e) => {
842
+ }), Fe = (e) => y.forEach(e, (e) => {
456
843
  let n = t.onEvent?.[e._tag];
457
- return n === void 0 ? f.void : n(e).pipe(f.catchCause((t) => f.sync(() => {
458
- g.error("billing: onEvent failed for a correction — the next sync of this tenant retries it", {
844
+ return n === void 0 ? y.void : n(e).pipe(y.catchCause((t) => y.sync(() => {
845
+ C.error("billing: onEvent failed for a correction — the next sync of this tenant retries it", {
459
846
  event: e._tag,
460
847
  cause: String(t)
461
848
  });
462
849
  })));
463
- }, { discard: !0 }), Q = (e) => f.gen(function* () {
464
- let t = yield* O.stores.customers.getByTenant(e);
850
+ }, { discard: !0 }), Z = (e) => y.gen(function* () {
851
+ let t = yield* z(e);
465
852
  if (t !== null) {
466
- let n = yield* X(t.providerCustomerId, e);
467
- yield* Z(n.events);
853
+ let n = yield* Pe(t.providerCustomerId, e);
854
+ yield* Fe(n.events);
468
855
  }
469
- return yield* O.stores.subscriptions.getByTenant(e);
470
- }), ue = (e, t) => e !== void 0 && e.status === t.status && e.plan === (t.plan ?? "free") && e.quantity === t.quantity && (e.cancelAt?.getTime() ?? null) === (t.cancelAt?.getTime() ?? null), $ = (e, t, n) => t.status === "canceled" ? {
856
+ return yield* N.stores.subscriptions.getByTenant(e);
857
+ }), Ie = (e, t) => e !== void 0 && e.status === t.status && e.plan === (t.plan ?? "free") && e.quantity === t.quantity && (e.cancelAt?.getTime() ?? null) === (t.cancelAt?.getTime() ?? null), Q = (e, t, n) => t.status === "canceled" ? {
471
858
  _tag: "subscriptionCanceled",
472
859
  tenantId: e,
473
860
  providerSubscriptionId: t.providerSubscriptionId,
@@ -483,133 +870,246 @@ var g = h({ scope: "@voltro/plugin-billing" }), _ = 3, v = /* @__PURE__ */ new S
483
870
  currentPeriodEnd: t.currentPeriodEnd,
484
871
  cancelAt: t.cancelAt,
485
872
  occurredAt: n
486
- };
873
+ }, Le = () => y.gen(function* () {
874
+ let e = A(), t = yield* O(v.listSubscriptions({}), x), n = yield* N.stores.customers.list(yield* I()), r = new Map(n.map((e) => [e.providerCustomerId, e.tenantId])), i = /* @__PURE__ */ new Set(), a = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Set(), s = [], c = /* @__PURE__ */ new Set(), l = /* @__PURE__ */ new Map(), u = 0, d = 0, f = (e) => {
875
+ let t = l.get(e);
876
+ return t === void 0 ? Ne(e).pipe(y.tap((t) => y.sync(() => {
877
+ l.set(e, t);
878
+ }))) : y.succeed(t);
879
+ };
880
+ for (let n of t) {
881
+ o.add(n.providerSubscriptionId);
882
+ let t = null;
883
+ n.providerCustomerId !== null && (t = yield* N.stores.customers.getByProviderCustomerId(n.providerCustomerId, yield* I()));
884
+ let l = t === null ? null : t.tenantId, p = n.providerCustomerId === null ? null : r.get(n.providerCustomerId) ?? null, m = l === null ? p === null ? n.tenantId : p : l;
885
+ if (m === null) {
886
+ u += 1, C.warn("billing reconcile: provider subscription names no tenant", { subscription: n.providerSubscriptionId });
887
+ continue;
888
+ }
889
+ if (t !== null && t.retiredAt !== null) {
890
+ u++, C.warn("billing reconcile: retired customer subscriptions require financial reconciliation", {
891
+ tenant: t.tenantId,
892
+ customer: t.providerCustomerId
893
+ });
894
+ continue;
895
+ }
896
+ if (n.providerCustomerId !== null && r.get(n.providerCustomerId) !== m && (yield* B(m, n.providerCustomerId), r.set(n.providerCustomerId, m), s.push({
897
+ _tag: "customerLinked",
898
+ tenantId: m,
899
+ providerCustomerId: n.providerCustomerId,
900
+ occurredAt: e
901
+ }), i.add(m)), T.has(n.status) && n.cancelAt === null && !c.has(m) && (yield* f(m))) {
902
+ C.error("billing reconcile: a closed tenant still has a paying subscription — closing it", {
903
+ tenant: m,
904
+ subscription: n.providerSubscriptionId
905
+ }), (yield* Y(m).pipe(y.map(() => !0), y.catch((e) => y.sync(() => (C.error("billing reconcile: closing a closed tenant failed — retried next run", {
906
+ tenant: m,
907
+ message: e.message
908
+ }), !1))))) && c.add(m), i.add(m);
909
+ continue;
910
+ }
911
+ if (c.has(m)) continue;
912
+ T.has(n.status) && a.set(m, (a.get(m) ?? 0) + 1);
913
+ let h = yield* N.stores.subscriptions.listByTenant(m);
914
+ Ie(h.find((e) => e.providerSubscriptionId === n.providerSubscriptionId), n) || (yield* G(m, n, e), s.push(Q(m, n, e)), i.add(m));
915
+ let g = n.plan;
916
+ if (g !== null && T.has(n.status)) {
917
+ let e = b.meteredPriceIdsFor(g).filter((e) => !n.priceIds.includes(e));
918
+ e.length > 0 && (yield* O(v.addSubscriptionPrices({
919
+ providerSubscriptionId: n.providerSubscriptionId,
920
+ priceIds: e
921
+ }), x), d += 1);
922
+ }
923
+ }
924
+ let p = 0, m = [];
925
+ for (let e of [
926
+ "active",
927
+ "trialing",
928
+ "pastDue",
929
+ "incomplete"
930
+ ]) m.push(...yield* N.stores.subscriptions.listByStatus(e));
931
+ let h = [];
932
+ for (let t of m) {
933
+ if (o.has(t.providerSubscriptionId)) continue;
934
+ p += 1;
935
+ let n = yield* O(v.fetchSubscription(t.providerSubscriptionId), x);
936
+ if (n === null) {
937
+ h.push(t);
938
+ continue;
939
+ }
940
+ let r = {
941
+ ...n,
942
+ providerSubscriptionId: t.providerSubscriptionId
943
+ };
944
+ yield* G(t.tenantId, r, e), s.push(Q(t.tenantId, r, e)), i.add(t.tenantId);
945
+ }
946
+ let g = h.length >= w && h.length * 2 >= m.length;
947
+ if (g) C.error("billing reconcile: the provider knows none of these live subscriptions — wrong account or provider? Nothing was canceled", {
948
+ unknown: h.length,
949
+ live: m.length,
950
+ provider: v.name
951
+ });
952
+ else for (let t of h) yield* N.stores.subscriptions.markCanceled(t.providerSubscriptionId), s.push(Q(t.tenantId, {
953
+ ...t,
954
+ status: "canceled",
955
+ providerSubscriptionId: t.providerSubscriptionId
956
+ }, e)), i.add(t.tenantId);
957
+ let _ = [...a].filter(([, e]) => e > 1).map(([e]) => e);
958
+ for (let e of _) C.error("billing reconcile: tenant has more than one paying subscription — billed twice, resolve by hand", { tenant: e });
959
+ let S = {
960
+ checked: t.length + p,
961
+ corrected: [...i],
962
+ duplicates: _,
963
+ unattributed: u,
964
+ meteredItemsRestored: d,
965
+ closedTenants: [...c],
966
+ refusedUnknown: g ? h.length : 0,
967
+ events: s
968
+ };
969
+ return C.info("billing reconcile from provider", {
970
+ checked: S.checked,
971
+ corrected: S.corrected.length,
972
+ duplicates: _.length,
973
+ unattributed: u,
974
+ meteredItemsRestored: d,
975
+ closedTenants: S.closedTenants.length
976
+ }), yield* Fe(s), S;
977
+ }), Re = (e, t) => xe(e, { quantity: t }), $ = (e, t, n) => y.gen(function* () {
978
+ if (![
979
+ e,
980
+ t,
981
+ n
982
+ ].every((e) => typeof e == "string" && e.length > 0 && e.length <= 191 && e.trim() === e && e.isWellFormed())) return yield* y.fail(u("Exact owned invoice reference identities are required."));
983
+ let r = N.stores, i = yield* I(), a = yield* r.customers.getByProviderCustomerId(t, i);
984
+ if (a === null || a.tenantId !== e || a.provider !== v.name) return yield* y.fail(u("Positive current or retained customer custody is required."));
985
+ let o = yield* r.historyWork.claim(a, n, A());
986
+ if (o.kind !== "claimed") return {
987
+ kind: o.kind,
988
+ work: o.work
989
+ };
990
+ let s = o.attempt, c = r.historyWork.hold(s, () => A().getTime());
991
+ return yield* y.gen(function* () {
992
+ if (N.stores !== r) return yield* y.fail(u("Billing stores changed before invoice reference dispatch.", !0));
993
+ let n = yield* J(e, o.work.providerInvoiceId, t).pipe(y.timeout("5 minutes"));
994
+ if (N.stores !== r) return yield* y.fail(u("Billing stores changed during invoice reference observation.", !0));
995
+ let i = yield* r.historyWork.observed(a, s, n, A());
996
+ return i === null || i.state !== "observed" && i.state !== "deferred" ? yield* y.fail(u("Invoice reference acknowledgement no longer owns its attempt.", !0)) : {
997
+ kind: i.state === "observed" ? "observed" : "deferred",
998
+ work: i
999
+ };
1000
+ }).pipe(y.catch(() => y.gen(function* () {
1001
+ return yield* r.historyWork.failed(a, s, A()), yield* y.fail(u("The invoice reference remains unconfirmed and retryable.", !0));
1002
+ })), y.ensuring(y.sync(c)));
1003
+ });
487
1004
  return {
488
1005
  service: {
489
- subscription: ee,
490
- plan: j,
491
- entitlementStatus: A,
492
- reconcileDunning: H,
493
- dunningSweep: re,
494
- checkEntitlement: te,
495
- consumeEntitlement: ne,
496
- reportUsage: N,
497
- flushUsage: P,
498
- startCheckout: L,
499
- portalUrl: R,
500
- applyEvent: ae,
501
- changePlan: ce,
502
- changeSeats: (e, t) => W(e, { quantity: t }),
503
- previewChange: oe,
504
- invoices: se,
505
- cancelSubscription: q,
506
- resumeSubscription: le,
507
- closeAccount: J,
508
- reconcileFromProvider: () => f.gen(function* () {
509
- let e = w(), t = yield* x(d.listSubscriptions({}), m), n = yield* O.stores.customers.list(), r = new Map(n.map((e) => [e.providerCustomerId, e.tenantId])), i = /* @__PURE__ */ new Set(), a = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Set(), s = [], c = /* @__PURE__ */ new Set(), l = /* @__PURE__ */ new Map(), u = 0, h = 0, y = (e) => {
510
- let t = l.get(e);
511
- return t === void 0 ? Y(e).pipe(f.tap((t) => f.sync(() => {
512
- l.set(e, t);
513
- }))) : f.succeed(t);
1006
+ subscription: oe,
1007
+ plan: ae,
1008
+ entitlementStatus: ie,
1009
+ reconcileDunning: K,
1010
+ dunningSweep: ve,
1011
+ checkEntitlement: ce,
1012
+ consumeEntitlement: le,
1013
+ reportUsage: ue,
1014
+ reportUsageEvent: de,
1015
+ flushUsage: fe,
1016
+ startCheckout: pe,
1017
+ portalUrl: me,
1018
+ applyEvent: be,
1019
+ changePlan: De,
1020
+ changeSeats: Re,
1021
+ previewChange: Se,
1022
+ invoices: Ce,
1023
+ invoiceSnapshot: Ee,
1024
+ invoiceHistory: Te,
1025
+ reconcileInvoice: J,
1026
+ processInvoiceHistoryReference: $,
1027
+ reconcileInvoiceHistory: () => y.gen(function* () {
1028
+ let e = N.stores, t = () => N.stores === e ? y.void : y.fail(u("Billing stores changed during the invoice-history pass.", !0));
1029
+ if (v.listInvoiceHistory === void 0 || v.fetchInvoiceSnapshot === void 0) return yield* y.fail(u("Complete account-owned invoice history and observations are required."));
1030
+ let n = yield* I(), r = yield* e.historyProgress.account(n, A());
1031
+ yield* t();
1032
+ let i = yield* e.customers.inventory(n, {
1033
+ ...r.afterCustodyId === null ? {} : { afterCustodyId: r.afterCustodyId },
1034
+ limit: 1
1035
+ }), a = i.customers[0];
1036
+ if (a === void 0) return yield* t(), {
1037
+ kind: (r.afterCustodyId === null ? r : yield* e.historyProgress.advanceAccount(r, null, A())) === null ? "contended" : "idle",
1038
+ customerCustodyId: null,
1039
+ admittedPages: 0,
1040
+ observed: 0,
1041
+ deferred: 0,
1042
+ busy: 0,
1043
+ unconfirmed: 0
514
1044
  };
515
- for (let n of t) {
516
- o.add(n.providerSubscriptionId);
517
- let t = n.tenantId ?? (n.providerCustomerId === null ? null : r.get(n.providerCustomerId) ?? null);
518
- if (t === null) {
519
- u += 1, g.warn("billing reconcile: provider subscription names no tenant", { subscription: n.providerSubscriptionId });
520
- continue;
1045
+ let o = yield* y.gen(function* () {
1046
+ yield* t();
1047
+ let n = yield* e.historyProgress.customer(a, A()), r = yield* e.historyJournal.begin(a), i = 0;
1048
+ if (!r.exhausted) {
1049
+ yield* t();
1050
+ let n = yield* Te(a.tenantId, a.providerCustomerId, {
1051
+ limit: 100,
1052
+ ...r.cursor === null ? {} : { cursor: r.cursor }
1053
+ });
1054
+ yield* t();
1055
+ let o = yield* e.historyJournal.commitPage(r, n);
1056
+ o === null ? r = yield* e.historyJournal.begin(a) : (r = o, i = 1);
521
1057
  }
522
- if (v.has(n.status) && n.cancelAt === null && !c.has(t) && (yield* y(t))) {
523
- g.error("billing reconcile: a closed tenant still has a paying subscription — closing it", {
524
- tenant: t,
525
- subscription: n.providerSubscriptionId
526
- }), (yield* J(t).pipe(f.map(() => !0), f.catch((e) => f.sync(() => (g.error("billing reconcile: closing a closed tenant failed — retried next run", {
527
- tenant: t,
528
- message: e.message
529
- }), !1))))) && c.add(t), i.add(t);
530
- continue;
531
- }
532
- if (c.has(t)) continue;
533
- v.has(n.status) && a.set(t, (a.get(t) ?? 0) + 1);
534
- let l = yield* O.stores.subscriptions.listByTenant(t);
535
- ue(l.find((e) => e.providerSubscriptionId === n.providerSubscriptionId), n) || (yield* I(t, n, e), s.push($(t, n, e)), i.add(t)), n.providerCustomerId !== null && r.get(n.providerCustomerId) !== t && (yield* O.stores.customers.upsert({
536
- tenantId: t,
537
- provider: d.name,
538
- providerCustomerId: n.providerCustomerId
539
- }), r.set(n.providerCustomerId, t), s.push({
540
- _tag: "customerLinked",
541
- tenantId: t,
542
- providerCustomerId: n.providerCustomerId,
543
- occurredAt: e
544
- }), i.add(t));
545
- let _ = n.plan;
546
- if (_ !== null && v.has(n.status)) {
547
- let e = p.meteredPriceIdsFor(_).filter((e) => !n.priceIds.includes(e));
548
- e.length > 0 && (yield* x(d.addSubscriptionPrices({
549
- providerSubscriptionId: n.providerSubscriptionId,
550
- priceIds: e
551
- }), m), h += 1);
1058
+ let o = { ...n.cursors }, s = 0, c = 0, l = 0, u = 0;
1059
+ for (let i of [
1060
+ "unprepared",
1061
+ "ready",
1062
+ "working"
1063
+ ]) {
1064
+ yield* t();
1065
+ let d = n.walkId === r.id ? o[i] : null, f = yield* e.historyWork.pending(a, i, A(), {
1066
+ limit: 1,
1067
+ ...d === null ? {} : { cursor: {
1068
+ lane: i,
1069
+ walkId: r.id,
1070
+ afterId: d
1071
+ } }
1072
+ });
1073
+ for (let e of f.entries) {
1074
+ yield* t();
1075
+ let n = yield* $(a.tenantId, a.providerCustomerId, e.reference.id).pipe(y.catch(() => y.succeed(null)));
1076
+ n === null ? u++ : n.kind === "observed" ? s++ : n.kind === "deferred" ? c++ : l++;
1077
+ }
1078
+ o[i] = f.nextCursor?.afterId ?? null;
552
1079
  }
553
- }
554
- let b = 0, S = [];
555
- for (let e of [
556
- "active",
557
- "trialing",
558
- "pastDue",
559
- "incomplete"
560
- ]) S.push(...yield* O.stores.subscriptions.listByStatus(e));
561
- let C = [];
562
- for (let t of S) {
563
- if (o.has(t.providerSubscriptionId)) continue;
564
- b += 1;
565
- let n = yield* x(d.fetchSubscription(t.providerSubscriptionId), m);
566
- if (n === null) {
567
- C.push(t);
568
- continue;
569
- }
570
- let r = {
571
- ...n,
572
- providerSubscriptionId: t.providerSubscriptionId
1080
+ yield* t();
1081
+ let d = yield* e.historyProgress.advanceCustomer(n, {
1082
+ walkId: r.id,
1083
+ cursors: o
1084
+ }, A());
1085
+ return {
1086
+ admittedPages: i,
1087
+ observed: s,
1088
+ deferred: c,
1089
+ busy: l + +(d === null),
1090
+ unconfirmed: u
573
1091
  };
574
- yield* I(t.tenantId, r, e), s.push($(t.tenantId, r, e)), i.add(t.tenantId);
575
- }
576
- let T = C.length >= _ && C.length * 2 >= S.length;
577
- if (T) g.error("billing reconcile: the provider knows none of these live subscriptions — wrong account or provider? Nothing was canceled", {
578
- unknown: C.length,
579
- live: S.length,
580
- provider: d.name
581
- });
582
- else for (let t of C) yield* O.stores.subscriptions.markCanceled(t.providerSubscriptionId), s.push($(t.tenantId, {
583
- ...t,
584
- status: "canceled",
585
- providerSubscriptionId: t.providerSubscriptionId
586
- }, e)), i.add(t.tenantId);
587
- let E = [...a].filter(([, e]) => e > 1).map(([e]) => e);
588
- for (let e of E) g.error("billing reconcile: tenant has more than one paying subscription — billed twice, resolve by hand", { tenant: e });
589
- let D = {
590
- checked: t.length + b,
591
- corrected: [...i],
592
- duplicates: E,
593
- unattributed: u,
594
- meteredItemsRestored: h,
595
- closedTenants: [...c],
596
- refusedUnknown: T ? C.length : 0,
597
- events: s
1092
+ }).pipe(y.catch(() => y.succeed({
1093
+ admittedPages: 0,
1094
+ observed: 0,
1095
+ deferred: 0,
1096
+ busy: 0,
1097
+ unconfirmed: 1
1098
+ })));
1099
+ return yield* t(), {
1100
+ kind: (yield* e.historyProgress.advanceAccount(r, i.nextCustodyId, A())) === null ? "contended" : "advanced",
1101
+ customerCustodyId: a.id,
1102
+ ...o
598
1103
  };
599
- return g.info("billing reconcile from provider", {
600
- checked: D.checked,
601
- corrected: D.corrected.length,
602
- duplicates: E.length,
603
- unattributed: u,
604
- meteredItemsRestored: h,
605
- closedTenants: D.closedTenants.length
606
- }), yield* Z(s), D;
607
- }),
608
- syncCustomer: X,
609
- syncTenant: Q
1104
+ }).pipe(y.timeout("2 minutes"), y.mapError((t) => t instanceof e ? t : u("The bounded invoice-history pass timed out and remains retryable.", !0))),
1105
+ cancelSubscription: Ae,
1106
+ resumeSubscription: je,
1107
+ closeAccount: Y,
1108
+ reconcileFromProvider: Le,
1109
+ syncCustomer: Pe,
1110
+ syncTenant: Z
610
1111
  },
611
- holder: O
1112
+ holder: N
612
1113
  };
613
- }, T = (e) => p.succeed(t, e);
614
- //#endregion
615
- export { S as USAGE_KEY, C as USAGE_KEY_RULE, T as billingServiceLayer, w as buildBillingService, b as makeStoreHolder, y as periodKey };
1114
+ }, j = (e) => b.succeed(t, e);
1115
+ export { k as USAGE_KEY, te as USAGE_KEY_RULE, j as billingServiceLayer, A as buildBillingService, ee as makeStoreHolder, D as periodKey };