@voltro/plugin-billing 0.86.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.
- package/CHANGELOG.md +5321 -0
- package/README.md +428 -0
- package/THIRD-PARTY-NOTICES.md +860 -221
- package/dist/checkoutStore.js +165 -0
- package/dist/closeStore.js +125 -0
- package/dist/custodyInventory.js +155 -0
- package/dist/customerStore.js +259 -0
- package/dist/economy.d.ts +30 -0
- package/dist/economy.js +266 -0
- package/dist/eventDelivery.js +72 -0
- package/dist/index.d.ts +669 -65
- package/dist/index.js +229 -171
- package/dist/invoiceHistory.js +91 -0
- package/dist/invoiceHistoryJournal.js +476 -0
- package/dist/invoiceHistoryProgress.js +235 -0
- package/dist/invoiceHistoryWork.js +363 -0
- package/dist/invoiceSnapshot.js +66 -0
- package/dist/invoiceStore.js +133 -0
- package/dist/providerIdentity.js +24 -0
- package/dist/providers.js +270 -65
- package/dist/service.js +891 -390
- package/dist/stores.js +124 -289
- package/dist/stripeIdentity.js +56 -0
- package/dist/stripeInvoiceHistory.js +67 -0
- package/dist/stripeInvoiceSnapshot.js +86 -0
- package/dist/stripeProvider.js +304 -177
- package/dist/stripeVerifiedEvent.js +29 -0
- package/dist/table.js +69 -46
- package/dist/types.d.ts +298 -27
- package/dist/usageEvents.js +65 -0
- package/dist/webhook.js +31 -38
- package/package.json +17 -7
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 {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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";
|
|
7
10
|
//#region src/service.ts
|
|
8
|
-
var
|
|
11
|
+
var C = S({ scope: "@voltro/plugin-billing" }), w = 3, T = /* @__PURE__ */ new Set([
|
|
9
12
|
"active",
|
|
10
13
|
"trialing",
|
|
11
14
|
"pastDue"
|
|
12
|
-
]),
|
|
13
|
-
schedule:
|
|
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
|
-
})),
|
|
16
|
-
let { provider:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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") :
|
|
32
|
-
},
|
|
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*
|
|
38
|
-
return
|
|
39
|
-
}),
|
|
40
|
-
let
|
|
41
|
-
return
|
|
42
|
-
}),
|
|
43
|
-
let i = yield*
|
|
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 =
|
|
46
|
-
|
|
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
|
-
}),
|
|
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
|
-
})) :
|
|
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:
|
|
123
|
+
message: "Usage arrived after final billing dispatch was sealed. Financial reconciliation is required before account deletion.",
|
|
59
124
|
transient: !1
|
|
60
|
-
})),
|
|
61
|
-
if (!
|
|
62
|
-
let
|
|
63
|
-
for (let
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
}),
|
|
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
|
-
})),
|
|
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:
|
|
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
|
-
}),
|
|
96
|
-
let n =
|
|
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:
|
|
191
|
+
message: t,
|
|
100
192
|
transient: !1
|
|
101
193
|
}));
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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*
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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*
|
|
341
|
+
})) : yield* O(v.createPortalSession({
|
|
140
342
|
tenantId: t,
|
|
141
343
|
providerCustomerId: r.providerCustomerId,
|
|
142
344
|
returnUrl: n
|
|
143
|
-
}),
|
|
144
|
-
}),
|
|
145
|
-
if (
|
|
146
|
-
let t = yield*
|
|
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 (
|
|
150
|
-
let t = yield*
|
|
151
|
-
return t === null ? null : yield*
|
|
152
|
-
}),
|
|
153
|
-
let t =
|
|
154
|
-
return t === void 0 ?
|
|
155
|
-
},
|
|
156
|
-
let
|
|
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:
|
|
360
|
+
locked: s,
|
|
159
361
|
graceEndsAt: i,
|
|
160
|
-
portalUrl:
|
|
161
|
-
}),
|
|
362
|
+
portalUrl: o
|
|
363
|
+
}), l = {
|
|
162
364
|
stepId: n,
|
|
163
365
|
tenantId: e.tenantId,
|
|
164
|
-
to:
|
|
366
|
+
to: a,
|
|
165
367
|
plan: e.plan,
|
|
166
368
|
status: e.status,
|
|
167
369
|
pastDueSince: r,
|
|
168
370
|
graceEndsAt: i,
|
|
169
|
-
locked:
|
|
170
|
-
lockout:
|
|
171
|
-
portalUrl:
|
|
172
|
-
...
|
|
371
|
+
locked: s,
|
|
372
|
+
lockout: P.lockout,
|
|
373
|
+
portalUrl: o,
|
|
374
|
+
...c
|
|
173
375
|
};
|
|
174
|
-
|
|
376
|
+
C.info("billing dunning notice", {
|
|
175
377
|
tenant: e.tenantId,
|
|
176
378
|
step: n,
|
|
177
|
-
locked:
|
|
379
|
+
locked: s,
|
|
178
380
|
graceEndsAt: i.toISOString(),
|
|
179
|
-
delivered:
|
|
381
|
+
delivered: P.notify !== void 0 && a !== null
|
|
180
382
|
});
|
|
181
|
-
let
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
}),
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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 =
|
|
199
|
-
yield*
|
|
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
|
|
219
|
-
if (!
|
|
220
|
-
let
|
|
221
|
-
|
|
222
|
-
for (let t of
|
|
223
|
-
return
|
|
224
|
-
}),
|
|
225
|
-
let e = yield*
|
|
226
|
-
for (let n of e) yield*
|
|
227
|
-
|
|
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
|
-
}),
|
|
234
|
-
if (!
|
|
235
|
-
if (e._tag === "invoicePaymentFailed") return
|
|
236
|
-
if (e._tag !== "invoicePaid" && e._tag !== "subscriptionUpserted") return
|
|
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
|
|
239
|
-
},
|
|
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
|
|
444
|
+
case "subscriptionUpserted": return N.stores.subscriptions.upsert({
|
|
242
445
|
tenantId: e.tenantId,
|
|
243
|
-
provider:
|
|
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
|
|
255
|
-
case "
|
|
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
|
-
},
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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 && !
|
|
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*
|
|
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*
|
|
515
|
+
let o = yield* O(v.updateSubscription({
|
|
300
516
|
providerSubscriptionId: r.providerSubscriptionId,
|
|
301
|
-
...n.plan === void 0 ? {} : { priceId:
|
|
517
|
+
...n.plan === void 0 ? {} : { priceId: b.priceIdFor(i) ?? i },
|
|
302
518
|
...n.quantity === void 0 ? {} : { quantity: a },
|
|
303
519
|
proration: "prorate"
|
|
304
|
-
}),
|
|
305
|
-
return yield*
|
|
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
|
-
}),
|
|
318
|
-
let r = yield*
|
|
319
|
-
if (r === null) return yield*
|
|
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*
|
|
540
|
+
let i = n.plan ?? r.plan, a = yield* O(v.previewSubscriptionChange({
|
|
325
541
|
providerSubscriptionId: r.providerSubscriptionId,
|
|
326
|
-
...n.plan === void 0 ? {} : { priceId:
|
|
542
|
+
...n.plan === void 0 ? {} : { priceId: b.priceIdFor(i) ?? i },
|
|
327
543
|
...n.quantity === void 0 ? {} : { quantity: n.quantity },
|
|
328
544
|
proration: "prorate"
|
|
329
|
-
}),
|
|
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
|
-
}),
|
|
338
|
-
let t = yield*
|
|
339
|
-
return t === null ? [] : yield*
|
|
340
|
-
}),
|
|
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*
|
|
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:
|
|
643
|
+
currency: b.currencyFor(e.plan)
|
|
349
644
|
};
|
|
350
645
|
}
|
|
351
|
-
return
|
|
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*
|
|
356
|
-
}),
|
|
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
|
-
})))),
|
|
361
|
-
let n =
|
|
362
|
-
return yield*
|
|
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
|
-
}),
|
|
372
|
-
yield*
|
|
373
|
-
let t = yield*
|
|
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
|
-
}),
|
|
377
|
-
return yield*
|
|
378
|
-
}),
|
|
379
|
-
yield*
|
|
380
|
-
let t = yield*
|
|
381
|
-
return yield*
|
|
382
|
-
}),
|
|
383
|
-
yield*
|
|
384
|
-
let
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
yield*
|
|
396
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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:
|
|
405
|
-
canceled:
|
|
406
|
-
};
|
|
407
|
-
}),
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
|
420
|
-
i
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
})
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
}),
|
|
828
|
+
}), x));
|
|
436
829
|
}
|
|
437
|
-
let
|
|
438
|
-
for (let e of yield*
|
|
439
|
-
if (!
|
|
440
|
-
let
|
|
441
|
-
|
|
442
|
-
|
|
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
|
|
837
|
+
for (let e of c) yield* q(e);
|
|
451
838
|
return {
|
|
452
|
-
tenantId:
|
|
453
|
-
events:
|
|
839
|
+
tenantId: o,
|
|
840
|
+
events: c
|
|
454
841
|
};
|
|
455
|
-
}),
|
|
842
|
+
}), Fe = (e) => y.forEach(e, (e) => {
|
|
456
843
|
let n = t.onEvent?.[e._tag];
|
|
457
|
-
return n === void 0 ?
|
|
458
|
-
|
|
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 }),
|
|
464
|
-
let t = yield*
|
|
850
|
+
}, { discard: !0 }), Z = (e) => y.gen(function* () {
|
|
851
|
+
let t = yield* z(e);
|
|
465
852
|
if (t !== null) {
|
|
466
|
-
let n = yield*
|
|
467
|
-
yield*
|
|
853
|
+
let n = yield* Pe(t.providerCustomerId, e);
|
|
854
|
+
yield* Fe(n.events);
|
|
468
855
|
}
|
|
469
|
-
return yield*
|
|
470
|
-
}),
|
|
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,247 @@ 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:
|
|
490
|
-
plan:
|
|
491
|
-
entitlementStatus:
|
|
492
|
-
reconcileDunning:
|
|
493
|
-
dunningSweep:
|
|
494
|
-
checkEntitlement:
|
|
495
|
-
consumeEntitlement:
|
|
496
|
-
reportUsage:
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
|
|
516
|
-
|
|
517
|
-
let
|
|
518
|
-
if (
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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:
|
|
1112
|
+
holder: N
|
|
612
1113
|
};
|
|
613
|
-
},
|
|
1114
|
+
}, j = (e) => b.succeed(t, e);
|
|
614
1115
|
//#endregion
|
|
615
|
-
export {
|
|
1116
|
+
export { k as USAGE_KEY, te as USAGE_KEY_RULE, j as billingServiceLayer, A as buildBillingService, ee as makeStoreHolder, D as periodKey };
|