@voltro/plugin-billing 0.85.0 → 0.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1116 @@
1
+ import { BillingError as e, BillingService as t, EntitlementExceeded as n } from "./types.js";
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
+ //#region src/service.ts
11
+ var C = S({ scope: "@voltro/plugin-billing" }), w = 3, T = /* @__PURE__ */ new Set([
12
+ "active",
13
+ "trialing",
14
+ "pastDue"
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)),
17
+ while: (e) => e.transient
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({
36
+ source: "billing",
37
+ message: `${n} ${t}`,
38
+ transient: !1
39
+ })), i;
40
+ try {
41
+ i = new URL(t);
42
+ } catch {
43
+ return r("is not an absolute URL");
44
+ }
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* () {
74
+ if (t.resolveEntitlementLimit !== void 0) {
75
+ let r = yield* t.resolveEntitlementLimit(e, n);
76
+ if (r !== null) return r;
77
+ }
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);
85
+ if (!Number.isFinite(i) || r <= 0) return;
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({
90
+ entitlement: t,
91
+ limit: o.limit,
92
+ used: o.used,
93
+ cost: r
94
+ }));
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({
96
+ source: "billing",
97
+ message: `usage quantity must be a finite number, got ${r}`,
98
+ transient: !1
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({
122
+ source: "billing",
123
+ message: "Usage arrived after final billing dispatch was sealed. Financial reconciliation is required before account deletion.",
124
+ transient: !1
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);
171
+ }
172
+ }), fe = () => U(), W = (t) => y.fail(new e({
173
+ source: "billing",
174
+ message: `tenant '${t}' already has a paying subscription — change its plan instead of checking out again`,
175
+ transient: !1
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({
177
+ tenantId: e,
178
+ provider: v.name,
179
+ providerSubscriptionId: t.providerSubscriptionId,
180
+ plan: t.plan ?? "free",
181
+ status: t.status,
182
+ quantity: t.quantity,
183
+ currentPeriodStart: t.currentPeriodStart,
184
+ currentPeriodEnd: t.currentPeriodEnd,
185
+ cancelAt: t.cancelAt,
186
+ pastDueSince: null,
187
+ statusEventAt: n
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({
190
+ source: "billing",
191
+ message: t,
192
+ transient: !1
193
+ }));
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 });
332
+ }
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({
338
+ source: "billing",
339
+ message: `no provider customer linked for tenant '${t}'`,
340
+ transient: !1
341
+ })) : yield* O(v.createPortalSession({
342
+ tenantId: t,
343
+ providerCustomerId: r.providerCustomerId,
344
+ returnUrl: n
345
+ }), x);
346
+ }), he = (e) => y.gen(function* () {
347
+ if (P.resolveRecipient !== void 0) {
348
+ let t = yield* P.resolveRecipient(e);
349
+ if (t !== null) return t;
350
+ }
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({
359
+ stepId: n,
360
+ locked: s,
361
+ graceEndsAt: i,
362
+ portalUrl: o
363
+ }), l = {
364
+ stepId: n,
365
+ tenantId: e.tenantId,
366
+ to: a,
367
+ plan: e.plan,
368
+ status: e.status,
369
+ pastDueSince: r,
370
+ graceEndsAt: i,
371
+ locked: s,
372
+ lockout: P.lockout,
373
+ portalUrl: o,
374
+ ...c
375
+ };
376
+ C.info("billing dunning notice", {
377
+ tenant: e.tenantId,
378
+ step: n,
379
+ locked: s,
380
+ graceEndsAt: i.toISOString(),
381
+ delivered: P.notify !== void 0 && a !== null
382
+ });
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", {
386
+ tenant: e.tenantId,
387
+ step: n,
388
+ cause: String(t)
389
+ });
390
+ }))));
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", {
396
+ tenant: e,
397
+ source: t.source,
398
+ message: t.message
399
+ }), null)))), r = t;
400
+ if (n !== null) {
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, {
403
+ status: n.status,
404
+ ...n.plan === null ? {} : { plan: n.plan },
405
+ quantity: n.quantity,
406
+ currentPeriodStart: n.currentPeriodStart,
407
+ currentPeriodEnd: n.currentPeriodEnd,
408
+ pastDueSince: a,
409
+ statusEventAt: i
410
+ }), r = {
411
+ ...t,
412
+ status: n.status,
413
+ plan: n.plan ?? t.plan,
414
+ quantity: n.quantity,
415
+ currentPeriodStart: n.currentPeriodStart,
416
+ currentPeriodEnd: n.currentPeriodEnd,
417
+ pastDueSince: a,
418
+ statusEventAt: i
419
+ };
420
+ }
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", {
431
+ tenant: n.tenantId,
432
+ message: e.message
433
+ });
434
+ }))), t += 1;
435
+ return t;
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;
440
+ let t = e.tenantId;
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) => {
443
+ switch (e._tag) {
444
+ case "subscriptionUpserted": return N.stores.subscriptions.upsert({
445
+ tenantId: e.tenantId,
446
+ provider: v.name,
447
+ providerSubscriptionId: e.providerSubscriptionId,
448
+ plan: e.plan,
449
+ status: e.status,
450
+ quantity: e.quantity ?? 1,
451
+ currentPeriodStart: e.currentPeriodStart ?? null,
452
+ currentPeriodEnd: e.currentPeriodEnd,
453
+ cancelAt: e.cancelAt,
454
+ pastDueSince: null,
455
+ statusEventAt: e.occurredAt
456
+ });
457
+ case "subscriptionCanceled": return N.stores.subscriptions.markCanceled(e.providerSubscriptionId);
458
+ case "customerLinked": return B(e.tenantId, e.providerCustomerId).pipe(y.asVoid);
459
+ }
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({
500
+ source: "billing",
501
+ message: `no subscription for tenant '${t}' to change`,
502
+ transient: !1
503
+ }));
504
+ let i = n.plan ?? r.plan, a = n.quantity ?? r.quantity;
505
+ if (n.plan !== void 0 && !b.has(i)) return yield* y.fail(new e({
506
+ source: "billing",
507
+ message: `unknown plan '${i}' — not in the plan registry`,
508
+ transient: !1
509
+ }));
510
+ if (!Number.isInteger(a) || a <= 0) return yield* y.fail(new e({
511
+ source: "billing",
512
+ message: `seat quantity must be a positive integer, got ${a}`,
513
+ transient: !1
514
+ }));
515
+ let o = yield* O(v.updateSubscription({
516
+ providerSubscriptionId: r.providerSubscriptionId,
517
+ ...n.plan === void 0 ? {} : { priceId: b.priceIdFor(i) ?? i },
518
+ ...n.quantity === void 0 ? {} : { quantity: a },
519
+ proration: "prorate"
520
+ }), x);
521
+ return yield* N.stores.subscriptions.patchByTenant(t, {
522
+ ...n.plan === void 0 ? {} : { plan: i },
523
+ quantity: o.quantity,
524
+ ...o.currentPeriodStart === null ? {} : { currentPeriodStart: o.currentPeriodStart },
525
+ ...o.currentPeriodEnd === null ? {} : { currentPeriodEnd: o.currentPeriodEnd }
526
+ }), {
527
+ tenantId: t,
528
+ plan: i,
529
+ quantity: o.quantity,
530
+ prorationMinor: o.prorationMinor,
531
+ currency: o.currency
532
+ };
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({
536
+ source: "billing",
537
+ message: `no subscription for tenant '${t}' to preview`,
538
+ transient: !1
539
+ }));
540
+ let i = n.plan ?? r.plan, a = yield* O(v.previewSubscriptionChange({
541
+ providerSubscriptionId: r.providerSubscriptionId,
542
+ ...n.plan === void 0 ? {} : { priceId: b.priceIdFor(i) ?? i },
543
+ ...n.quantity === void 0 ? {} : { quantity: n.quantity },
544
+ proration: "prorate"
545
+ }), x);
546
+ return {
547
+ tenantId: t,
548
+ plan: i,
549
+ quantity: a.quantity,
550
+ prorationMinor: a.prorationMinor,
551
+ currency: a.currency
552
+ };
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* () {
636
+ if (n === "free") {
637
+ let e = yield* Ae(t);
638
+ return {
639
+ tenantId: t,
640
+ plan: e.plan,
641
+ quantity: e.quantity,
642
+ prorationMinor: 0,
643
+ currency: b.currencyFor(e.plan)
644
+ };
645
+ }
646
+ return b.has(n) && b.priceIdFor(n) === null ? yield* y.fail(new e({
647
+ source: "billing",
648
+ message: `plan '${n}' has no provider price — it is not a self-serve plan`,
649
+ transient: !1
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({
652
+ source: "billing",
653
+ message: `no live subscription for tenant '${t}' to ${n}`,
654
+ transient: !1
655
+ })))), ke = (e, t) => y.gen(function* () {
656
+ let n = A();
657
+ return yield* G(e.tenantId, {
658
+ ...t,
659
+ providerSubscriptionId: e.providerSubscriptionId
660
+ }, n), {
661
+ ...e,
662
+ ...t,
663
+ plan: t.plan ?? e.plan,
664
+ statusEventAt: n
665
+ };
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({
669
+ providerSubscriptionId: t.providerSubscriptionId,
670
+ mode: "periodEnd"
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
+ }
713
+ }
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);
722
+ }
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
764
+ }), {
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 }), {
804
+ tenantId: null,
805
+ events: []
806
+ };
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({
826
+ providerSubscriptionId: e.providerSubscriptionId,
827
+ priceIds: t
828
+ }), x));
829
+ }
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));
836
+ }
837
+ for (let e of c) yield* q(e);
838
+ return {
839
+ tenantId: o,
840
+ events: c
841
+ };
842
+ }), Fe = (e) => y.forEach(e, (e) => {
843
+ let n = t.onEvent?.[e._tag];
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", {
846
+ event: e._tag,
847
+ cause: String(t)
848
+ });
849
+ })));
850
+ }, { discard: !0 }), Z = (e) => y.gen(function* () {
851
+ let t = yield* z(e);
852
+ if (t !== null) {
853
+ let n = yield* Pe(t.providerCustomerId, e);
854
+ yield* Fe(n.events);
855
+ }
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" ? {
858
+ _tag: "subscriptionCanceled",
859
+ tenantId: e,
860
+ providerSubscriptionId: t.providerSubscriptionId,
861
+ occurredAt: n
862
+ } : {
863
+ _tag: "subscriptionUpserted",
864
+ tenantId: e,
865
+ providerSubscriptionId: t.providerSubscriptionId,
866
+ plan: t.plan ?? "free",
867
+ status: t.status,
868
+ quantity: t.quantity,
869
+ currentPeriodStart: t.currentPeriodStart,
870
+ currentPeriodEnd: t.currentPeriodEnd,
871
+ cancelAt: t.cancelAt,
872
+ occurredAt: n
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
+ });
1004
+ return {
1005
+ service: {
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
1044
+ };
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);
1057
+ }
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;
1079
+ }
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
1091
+ };
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
1103
+ };
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
1111
+ },
1112
+ holder: N
1113
+ };
1114
+ }, j = (e) => b.succeed(t, e);
1115
+ //#endregion
1116
+ export { k as USAGE_KEY, te as USAGE_KEY_RULE, j as billingServiceLayer, A as buildBillingService, ee as makeStoreHolder, D as periodKey };