@odla-ai/chapter 0.54.2 → 0.56.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/README.md +32 -0
- package/dist/{chunk-SPEL4OGE.js → chunk-IQZWCG42.js} +581 -362
- package/dist/chunk-IQZWCG42.js.map +1 -0
- package/dist/index.cjs +522 -266
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -3
- package/dist/index.d.ts +30 -3
- package/dist/index.js +518 -262
- package/dist/index.js.map +1 -1
- package/dist/ui/index.js +1 -1
- package/dist/ui/member/index.d.ts +12 -1
- package/dist/ui/member/index.js +1 -1
- package/dist/worker/index.cjs +1110 -616
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +22 -1
- package/dist/worker/index.d.ts +22 -1
- package/dist/worker/index.js +1103 -609
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-SPEL4OGE.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -122,8 +122,8 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
122
122
|
if (app.requireDisclaimerAck && !acked) {
|
|
123
123
|
return { ok: false, error: "disclaimerAck is required" };
|
|
124
124
|
}
|
|
125
|
-
const
|
|
126
|
-
const row2 = { id:
|
|
125
|
+
const id11 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
|
|
126
|
+
const row2 = { id: id11, status: chapter.pipeline.initial, createdAt: opts.now };
|
|
127
127
|
for (const f of [...app.required, ...app.optional]) {
|
|
128
128
|
if (fieldStates[f]?.visible === false) continue;
|
|
129
129
|
if (typeof fields[f] === "string") row2[f] = fields[f].trim();
|
|
@@ -157,12 +157,12 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
157
157
|
}
|
|
158
158
|
if (acked) row2.disclaimerAckAt = opts.now;
|
|
159
159
|
const ops = [
|
|
160
|
-
{ t: "update", ns: "applications", id:
|
|
160
|
+
{ t: "update", ns: "applications", id: id11, attrs: row2 }
|
|
161
161
|
];
|
|
162
162
|
if (opts.admissionGrant?.decision === "waive_interview") {
|
|
163
163
|
ops.push({ t: "update", ns: "admissionGrants", id: opts.admissionGrant.id, attrs: {
|
|
164
164
|
consumedAt: opts.now,
|
|
165
|
-
consumedApplicationId:
|
|
165
|
+
consumedApplicationId: id11
|
|
166
166
|
} });
|
|
167
167
|
}
|
|
168
168
|
try {
|
|
@@ -170,7 +170,7 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
170
170
|
ops,
|
|
171
171
|
opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
|
|
172
172
|
);
|
|
173
|
-
return { ok: true, id:
|
|
173
|
+
return { ok: true, id: id11, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
|
|
174
174
|
} catch (error) {
|
|
175
175
|
if (error.code === "unique_violation") return {
|
|
176
176
|
ok: false,
|
|
@@ -559,6 +559,7 @@ var subscriptionCheckoutIntentsEntity = {
|
|
|
559
559
|
interval: attr5("string", { indexed: true }),
|
|
560
560
|
billingTerms: attr5("json", { optional: true }),
|
|
561
561
|
paymentQuote: attr5("json", { optional: true }),
|
|
562
|
+
paymentReceipt: attr5("json", { optional: true }),
|
|
562
563
|
membershipOffer: attr5("json", { optional: true }),
|
|
563
564
|
purchaserEmailNormalized: attr5("string", { indexed: true }),
|
|
564
565
|
requestedCustomerId: attr5("string", { indexed: true, optional: true }),
|
|
@@ -1705,9 +1706,9 @@ var APPLICATION_LABELS = {
|
|
|
1705
1706
|
phone: "Phone",
|
|
1706
1707
|
state: "State"
|
|
1707
1708
|
};
|
|
1708
|
-
function humanize(
|
|
1709
|
-
const text6 =
|
|
1710
|
-
return text6 ? `${text6[0].toUpperCase()}${text6.slice(1)}` :
|
|
1709
|
+
function humanize(id11) {
|
|
1710
|
+
const text6 = id11.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
1711
|
+
return text6 ? `${text6[0].toUpperCase()}${text6.slice(1)}` : id11;
|
|
1711
1712
|
}
|
|
1712
1713
|
function applicationDefs(chapter) {
|
|
1713
1714
|
try {
|
|
@@ -1716,23 +1717,23 @@ function applicationDefs(chapter) {
|
|
|
1716
1717
|
return {};
|
|
1717
1718
|
}
|
|
1718
1719
|
}
|
|
1719
|
-
function applicationType(
|
|
1720
|
+
function applicationType(id11, def) {
|
|
1720
1721
|
if (def && ["string", "email", "url", "enum"].includes(def.type)) return def.type;
|
|
1721
|
-
return
|
|
1722
|
+
return id11 === "email" ? "email" : id11 === "linkedin" ? "url" : "string";
|
|
1722
1723
|
}
|
|
1723
1724
|
function applicationFlowFields(chapter, application = chapter.application) {
|
|
1724
1725
|
const defs = applicationDefs(chapter);
|
|
1725
|
-
return [...application.required, ...application.optional].map((
|
|
1726
|
-
const def = defs[
|
|
1727
|
-
const conditions = application.conditions[
|
|
1728
|
-
const type = applicationType(
|
|
1726
|
+
return [...application.required, ...application.optional].map((id11, order2) => {
|
|
1727
|
+
const def = defs[id11];
|
|
1728
|
+
const conditions = application.conditions[id11];
|
|
1729
|
+
const type = applicationType(id11, def);
|
|
1729
1730
|
return {
|
|
1730
|
-
id:
|
|
1731
|
-
label: def?.label ?? APPLICATION_LABELS[
|
|
1731
|
+
id: id11,
|
|
1732
|
+
label: def?.label ?? APPLICATION_LABELS[id11] ?? humanize(id11),
|
|
1732
1733
|
type,
|
|
1733
|
-
required: application.required.includes(
|
|
1734
|
+
required: application.required.includes(id11),
|
|
1734
1735
|
order: order2,
|
|
1735
|
-
maxLength: application.maxLen[
|
|
1736
|
+
maxLength: application.maxLen[id11] ?? application.defaultMaxLen,
|
|
1736
1737
|
...type === "enum" && def?.options ? { options: [...def.options] } : {},
|
|
1737
1738
|
...conditions ? { conditions: { ...conditions } } : {},
|
|
1738
1739
|
...type === "email" ? { emailIdentity: "literal" } : {}
|
|
@@ -1742,17 +1743,17 @@ function applicationFlowFields(chapter, application = chapter.application) {
|
|
|
1742
1743
|
function resolveFormationFlowFields(crm, formation) {
|
|
1743
1744
|
if (!formation.enabled) return [];
|
|
1744
1745
|
const def = crm.type(formation.type);
|
|
1745
|
-
return [...formation.required, ...formation.optional].map((
|
|
1746
|
-
const field2 = def.fields[
|
|
1746
|
+
return [...formation.required, ...formation.optional].map((id11, order2) => {
|
|
1747
|
+
const field2 = def.fields[id11];
|
|
1747
1748
|
return {
|
|
1748
|
-
id:
|
|
1749
|
-
label: field2.label ?? humanize(
|
|
1749
|
+
id: id11,
|
|
1750
|
+
label: field2.label ?? humanize(id11),
|
|
1750
1751
|
type: field2.type,
|
|
1751
|
-
required: formation.required.includes(
|
|
1752
|
+
required: formation.required.includes(id11),
|
|
1752
1753
|
order: order2,
|
|
1753
|
-
maxLength: formation.maxLen[
|
|
1754
|
+
maxLength: formation.maxLen[id11] ?? formation.defaultMaxLen,
|
|
1754
1755
|
...field2.options ? { options: [...field2.options] } : {},
|
|
1755
|
-
...
|
|
1756
|
+
...id11 === def.emailField || field2.type === "email" ? { emailIdentity: "literal" } : {}
|
|
1756
1757
|
};
|
|
1757
1758
|
});
|
|
1758
1759
|
}
|
|
@@ -2056,12 +2057,12 @@ function resolveNetworkPersonRegistry(mode, input, targetIds) {
|
|
|
2056
2057
|
if (input.mode !== "shadow") throw new Error('defineChapter.networkPersonRegistry.mode: must be "shadow"');
|
|
2057
2058
|
const chapterIds = input.chapterIds ? [...input.chapterIds] : [...targetIds];
|
|
2058
2059
|
if (chapterIds.length === 0) throw new Error("defineChapter.networkPersonRegistry.chapterIds: at least one chapter is required");
|
|
2059
|
-
if (new Set(chapterIds).size !== chapterIds.length || chapterIds.some((
|
|
2060
|
+
if (new Set(chapterIds).size !== chapterIds.length || chapterIds.some((id11) => !SLUG2.test(id11))) {
|
|
2060
2061
|
throw new Error("defineChapter.networkPersonRegistry.chapterIds: expected distinct lowercase slugs");
|
|
2061
2062
|
}
|
|
2062
|
-
const unknown = chapterIds.filter((
|
|
2063
|
+
const unknown = chapterIds.filter((id11) => !targetIds.includes(id11));
|
|
2063
2064
|
if (unknown.length) {
|
|
2064
|
-
throw new Error(`defineChapter.networkPersonRegistry.chapterIds: ${unknown.map((
|
|
2065
|
+
throw new Error(`defineChapter.networkPersonRegistry.chapterIds: ${unknown.map((id11) => `"${id11}"`).join(", ")} are not configured network targets`);
|
|
2065
2066
|
}
|
|
2066
2067
|
return { mode: "shadow", chapterIds };
|
|
2067
2068
|
}
|
|
@@ -2184,9 +2185,9 @@ function isResolvedCrm(x) {
|
|
|
2184
2185
|
}
|
|
2185
2186
|
function defineChapter(config) {
|
|
2186
2187
|
if (!config || typeof config !== "object") throw new Error("defineChapter: a config object is required");
|
|
2187
|
-
const { id:
|
|
2188
|
-
if (typeof
|
|
2189
|
-
throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(
|
|
2188
|
+
const { id: id11, name } = config;
|
|
2189
|
+
if (typeof id11 !== "string" || !SLUG7.test(id11)) {
|
|
2190
|
+
throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id11)}`);
|
|
2190
2191
|
}
|
|
2191
2192
|
if (typeof name !== "string" || name.trim() === "") throw new Error("defineChapter.name: a non-empty name is required");
|
|
2192
2193
|
const mode = config.mode ?? "chapter";
|
|
@@ -2293,7 +2294,7 @@ function defineChapter(config) {
|
|
|
2293
2294
|
};
|
|
2294
2295
|
const chapter = {
|
|
2295
2296
|
config,
|
|
2296
|
-
id:
|
|
2297
|
+
id: id11,
|
|
2297
2298
|
name,
|
|
2298
2299
|
mode,
|
|
2299
2300
|
brand,
|
|
@@ -2708,14 +2709,14 @@ function facts(snapshots) {
|
|
|
2708
2709
|
["payment_intent.updated", snapshots.payments?.objects ?? []]
|
|
2709
2710
|
];
|
|
2710
2711
|
return groups2.flatMap(([eventType, objects]) => objects.map((object2) => {
|
|
2711
|
-
const
|
|
2712
|
+
const id11 = typeof object2.id === "string" ? object2.id : "invalid";
|
|
2712
2713
|
return normalizeNetworkCommercialStripeEvent({
|
|
2713
|
-
id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${
|
|
2714
|
+
id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id11}`,
|
|
2714
2715
|
type: eventType,
|
|
2715
2716
|
created: typeof object2.created === "number" ? object2.created : 0,
|
|
2716
2717
|
livemode: object2.livemode === true,
|
|
2717
2718
|
data: { object: object2 }
|
|
2718
|
-
}, `stripe-snapshot:${eventType}:${
|
|
2719
|
+
}, `stripe-snapshot:${eventType}:${id11}`);
|
|
2719
2720
|
}));
|
|
2720
2721
|
}
|
|
2721
2722
|
var selected = (row2) => ({
|
|
@@ -3128,7 +3129,7 @@ function findTier(tiers2, tierId) {
|
|
|
3128
3129
|
if (!tierId) return tiers2.length === 1 ? tiers2[0] : null;
|
|
3129
3130
|
return tiers2.find((tier) => tier.id === tierId) ?? null;
|
|
3130
3131
|
}
|
|
3131
|
-
function joinConfigTiers(tiers2, defaults = {}, founding = /* @__PURE__ */ new Map()) {
|
|
3132
|
+
function joinConfigTiers(tiers2, defaults = {}, founding = /* @__PURE__ */ new Map(), seats = /* @__PURE__ */ new Map()) {
|
|
3132
3133
|
return tiers2.map((tier) => ({
|
|
3133
3134
|
id: tier.id,
|
|
3134
3135
|
name: tier.name,
|
|
@@ -3137,7 +3138,8 @@ function joinConfigTiers(tiers2, defaults = {}, founding = /* @__PURE__ */ new M
|
|
|
3137
3138
|
interval: tier.interval ?? defaults.interval ?? "year",
|
|
3138
3139
|
blurb: tier.blurb ?? "",
|
|
3139
3140
|
free: tierIsFree(tier),
|
|
3140
|
-
...founding.has(tier.id) ? { founding: founding.get(tier.id) } : {}
|
|
3141
|
+
...founding.has(tier.id) ? { founding: founding.get(tier.id) } : {},
|
|
3142
|
+
...seats.has(tier.id) ? { seatOffer: seats.get(tier.id) } : {}
|
|
3141
3143
|
}));
|
|
3142
3144
|
}
|
|
3143
3145
|
|
|
@@ -3424,8 +3426,8 @@ async function hash(value) {
|
|
|
3424
3426
|
async function membershipDiscountCouponId(input, policyId) {
|
|
3425
3427
|
return policyId ? `odla_m_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.skuId, policyId])}` : void 0;
|
|
3426
3428
|
}
|
|
3427
|
-
async function load(db, ns,
|
|
3428
|
-
return (await db.query({ [ns]: { $: { where: { id:
|
|
3429
|
+
async function load(db, ns, id11) {
|
|
3430
|
+
return (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
|
|
3429
3431
|
}
|
|
3430
3432
|
async function membershipOfferIdForRequest(input) {
|
|
3431
3433
|
return `offer_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.applicationId, input.requestId])}`;
|
|
@@ -3491,6 +3493,7 @@ async function previewMembershipCommercialOffer(db, input) {
|
|
|
3491
3493
|
...contract.founding && contract.discountPolicy ? { cohortLabel: contract.discountPolicy.cohortLabel } : {},
|
|
3492
3494
|
dueTodayCents: contract.dueTodayCents,
|
|
3493
3495
|
renewalCents: contract.renewalCents,
|
|
3496
|
+
...offering.seatPriceCents !== void 0 ? { seatPriceCents: offering.seatPriceCents } : {},
|
|
3494
3497
|
...discount ? { discountDuration: discount.duration === "once" ? "first_term" : "while_active" } : {},
|
|
3495
3498
|
...contract.initialTermEndsAt !== void 0 ? { initialTermEndsAt: contract.initialTermEndsAt } : {}
|
|
3496
3499
|
};
|
|
@@ -3562,20 +3565,20 @@ import { stripeRequest as stripeRequest2 } from "@odla-ai/stripe";
|
|
|
3562
3565
|
|
|
3563
3566
|
// src/commercial-evidence-guard.ts
|
|
3564
3567
|
async function commercialEvidenceGuard(db, eventId, digest, code = "membership_payment_evidence_quarantined") {
|
|
3565
|
-
const ns = "networkCommercialExceptions",
|
|
3566
|
-
const row2 = (await db.query({ [ns]: { $: { where: { id:
|
|
3567
|
-
if (!row2) return { ns, id:
|
|
3568
|
+
const ns = "networkCommercialExceptions", id11 = `network-commercial-exception:${eventId}:collision`;
|
|
3569
|
+
const row2 = (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
|
|
3570
|
+
if (!row2) return { ns, id: id11, exists: false };
|
|
3568
3571
|
if (row2.status !== "resolved" || row2.resolvedDigest !== digest || typeof digest !== "string") {
|
|
3569
3572
|
throw Object.assign(new Error(code), { code });
|
|
3570
3573
|
}
|
|
3571
|
-
return { ns, id:
|
|
3574
|
+
return { ns, id: id11, exists: true, equals: { status: "resolved", resolvedDigest: digest, updatedAt: row2.updatedAt } };
|
|
3572
3575
|
}
|
|
3573
3576
|
|
|
3574
3577
|
// src/membership-authority-state.ts
|
|
3575
3578
|
var membershipFail = (code) => {
|
|
3576
3579
|
throw Object.assign(new Error(code), { code });
|
|
3577
3580
|
};
|
|
3578
|
-
var membershipLoad = async (db, ns,
|
|
3581
|
+
var membershipLoad = async (db, ns, id11) => (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
|
|
3579
3582
|
var membershipScopeKeys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId"];
|
|
3580
3583
|
var membershipScope = (input) => Object.fromEntries(membershipScopeKeys.map((key3) => [key3, input[key3]]));
|
|
3581
3584
|
function requireMembershipScope(row2, input) {
|
|
@@ -3586,18 +3589,18 @@ async function loadMembershipAuthorityState(db, input) {
|
|
|
3586
3589
|
if (!row2 || row2.mode !== "authoritative" || row2.chapterId !== input.chapterId || row2.environment !== input.environment || !Number.isSafeInteger(row2.revision) || Number(row2.revision) < 1) return membershipFail("membership_authority_unavailable");
|
|
3587
3590
|
return row2;
|
|
3588
3591
|
}
|
|
3589
|
-
var membershipUpdate = (ns,
|
|
3592
|
+
var membershipUpdate = (ns, id11, attrs) => ({ t: "update", ns, id: id11, attrs });
|
|
3590
3593
|
var membershipClock = (now) => {
|
|
3591
3594
|
if (!Number.isSafeInteger(now) || now < 0) membershipFail("invalid_membership_clock");
|
|
3592
3595
|
};
|
|
3593
3596
|
|
|
3594
3597
|
// src/membership-restart-payment.ts
|
|
3595
3598
|
async function membershipRestartPayment(db, target, member, now) {
|
|
3596
|
-
const
|
|
3599
|
+
const id11 = String(member.id), restart = await membershipLoad(db, "membershipRestart", id11);
|
|
3597
3600
|
requireMembershipScope(restart, target);
|
|
3598
|
-
const entitlement = await membershipLoad(db, "networkEntitlement", `${
|
|
3601
|
+
const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
|
|
3599
3602
|
requireMembershipScope(entitlement, target);
|
|
3600
|
-
if (entitlement.membershipId !==
|
|
3603
|
+
if (entitlement.membershipId !== id11 || entitlement.kind !== "primary") membershipFail("membership_restart_conflict");
|
|
3601
3604
|
if (restart.status !== "ready" || member.status !== "restarting" || member.restartId !== restart.restartId || member.memberNumber !== restart.memberNumber || !Number.isSafeInteger(member.journeyApprovedAt)) membershipFail("membership_restart_conflict");
|
|
3602
3605
|
return {
|
|
3603
3606
|
restartId: String(restart.restartId),
|
|
@@ -3614,49 +3617,117 @@ async function membershipRestartPayment(db, target, member, now) {
|
|
|
3614
3617
|
graceEndsAt: 0
|
|
3615
3618
|
},
|
|
3616
3619
|
ops: [
|
|
3617
|
-
membershipUpdate("membershipRestart",
|
|
3618
|
-
{ t: "retract", ns: "networkMembership", id:
|
|
3620
|
+
membershipUpdate("membershipRestart", id11, { status: "complete" }),
|
|
3621
|
+
{ t: "retract", ns: "networkMembership", id: id11, attrs: ["refundRequestedAt", "cancellationRequestedAt", "discountPolicyVersionId", "discountSnapshot"] }
|
|
3619
3622
|
],
|
|
3620
3623
|
guards: [
|
|
3621
|
-
{ ns: "networkMembership", id:
|
|
3622
|
-
{ ns: "membershipRestart", id:
|
|
3624
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { status: "restarting", revision: member.revision, restartId: restart.restartId } },
|
|
3625
|
+
{ ns: "membershipRestart", id: id11, exists: true, equals: { status: "ready", restartId: restart.restartId } },
|
|
3623
3626
|
{ ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision, status: entitlement.status } }
|
|
3624
3627
|
]
|
|
3625
3628
|
};
|
|
3626
3629
|
}
|
|
3627
3630
|
|
|
3631
|
+
// src/checkout-named-seat.ts
|
|
3632
|
+
function checkoutNamedSeatSelection(value, purchaserEmail) {
|
|
3633
|
+
const row2 = value;
|
|
3634
|
+
const name = typeof row2?.recipientName === "string" ? row2.recipientName.trim().replace(/\s+/g, " ") : "";
|
|
3635
|
+
const email = normalizeChapterEmail(typeof row2?.recipientEmail === "string" ? row2.recipientEmail : "");
|
|
3636
|
+
const purchaser = normalizeChapterEmail(purchaserEmail);
|
|
3637
|
+
if (!row2 || typeof row2 !== "object" || Array.isArray(row2) || name.length < 2 || name.length > 160 || /[\u0000-\u001f\u007f]/.test(row2.recipientName) || email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(purchaser) || email === purchaser) {
|
|
3638
|
+
throw Object.assign(new Error("Enter a different recipient's name and email."), { code: "invalid_named_seat_recipient" });
|
|
3639
|
+
}
|
|
3640
|
+
return { recipientName: name, recipientEmail: email };
|
|
3641
|
+
}
|
|
3642
|
+
async function checkoutNamedSeatIdentity(target, email) {
|
|
3643
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([
|
|
3644
|
+
target.appId,
|
|
3645
|
+
target.chapterId,
|
|
3646
|
+
target.environment,
|
|
3647
|
+
target.runtime,
|
|
3648
|
+
email
|
|
3649
|
+
])));
|
|
3650
|
+
return {
|
|
3651
|
+
seatId: (await membershipOfferIdForRequest({ ...target, requestId: "named_seat" })).replace(/^offer_/, "seat_"),
|
|
3652
|
+
membershipId: (await membershipOfferIdForRequest({ ...target, requestId: "checkout_binding" })).replace(/^offer_/, "membership_"),
|
|
3653
|
+
recipientClaimId: `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`
|
|
3654
|
+
};
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3628
3657
|
// src/membership-checkout-binding.ts
|
|
3629
3658
|
var keys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId", "offerId", "offerDigest", "quoteDigest", "priceId"];
|
|
3630
3659
|
var reject2 = (code) => {
|
|
3631
3660
|
throw Object.assign(new Error(code), { code });
|
|
3632
3661
|
};
|
|
3633
|
-
var load2 = async (db, ns,
|
|
3662
|
+
var load2 = async (db, ns, id11) => (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
|
|
3634
3663
|
async function membershipCheckoutBindingId(input) {
|
|
3635
3664
|
return (await membershipOfferIdForRequest({ ...input, requestId: "checkout_binding" })).replace(/^offer_/, "checkout_");
|
|
3636
3665
|
}
|
|
3637
3666
|
async function verifyMembershipCheckoutBinding(value, input) {
|
|
3638
3667
|
const row2 = value;
|
|
3639
3668
|
if (!row2 || typeof row2 !== "object" || row2.id !== await membershipCheckoutBindingId(input) || keys.some((key3) => row2[key3] !== input[key3]) || !Number.isSafeInteger(row2.boundAt) || row2.boundAt < 0) return reject2("membership_checkout_conflict");
|
|
3640
|
-
|
|
3669
|
+
if (Boolean(row2.namedSeat) !== Boolean(input.namedSeat)) return reject2("membership_checkout_conflict");
|
|
3670
|
+
if (input.namedSeat) {
|
|
3671
|
+
const expected = {
|
|
3672
|
+
...checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail),
|
|
3673
|
+
purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail)
|
|
3674
|
+
};
|
|
3675
|
+
const identity2 = await checkoutNamedSeatIdentity(input, expected.recipientEmail);
|
|
3676
|
+
if (!row2.namedSeat || Object.entries({ ...expected, ...identity2 }).some(([key3, value2]) => row2.namedSeat[key3] !== value2) || !/^price_[A-Za-z0-9_]+$/.test(row2.namedSeat.priceId) || row2.namedSeat.priceId === input.priceId || !Number.isSafeInteger(row2.namedSeat.amountCents) || row2.namedSeat.amountCents < 0) return reject2("membership_checkout_conflict");
|
|
3677
|
+
}
|
|
3678
|
+
return {
|
|
3679
|
+
id: row2.id,
|
|
3680
|
+
...Object.fromEntries(keys.map((key3) => [key3, input[key3]])),
|
|
3681
|
+
boundAt: row2.boundAt,
|
|
3682
|
+
...row2.namedSeat ? { namedSeat: row2.namedSeat } : {}
|
|
3683
|
+
};
|
|
3641
3684
|
}
|
|
3642
|
-
async function bindMembershipCheckout(db, input, now) {
|
|
3685
|
+
async function bindMembershipCheckout(db, input, now, seatPriceId) {
|
|
3643
3686
|
if (!Number.isSafeInteger(now) || now < 0 || !/^[a-f0-9]{64}$/.test(input.offerDigest) || !/^[a-f0-9]{64}$/.test(input.quoteDigest) || !/^price_[A-Za-z0-9_]+$/.test(input.priceId)) return reject2("invalid_membership_checkout");
|
|
3644
3687
|
const stateId = `${input.chapterId}:${input.environment}`;
|
|
3645
3688
|
const state = await load2(db, "membershipAuthorityState", stateId);
|
|
3646
3689
|
if (!state || state.mode !== "authoritative" || state.chapterId !== input.chapterId || state.environment !== input.environment || !Number.isSafeInteger(state.revision) || Number(state.revision) < 1) return reject2("membership_authority_unavailable");
|
|
3647
|
-
const
|
|
3648
|
-
const existing = await load2(db, "membershipOfferCheckout",
|
|
3690
|
+
const id11 = await membershipCheckoutBindingId(input);
|
|
3691
|
+
const existing = await load2(db, "membershipOfferCheckout", id11);
|
|
3649
3692
|
if (existing) return verifyMembershipCheckoutBinding(existing, input);
|
|
3650
3693
|
const row2 = await load2(db, "membershipCheckoutOffer", input.offerId);
|
|
3651
3694
|
const offer = await verifyIssuedMembershipOffer(row2, { ...input, now, lifetimeMs: 6e4 });
|
|
3652
3695
|
if (offer.digest !== input.offerDigest || offer.expiresAt <= now || offer.issuedAt > now) return reject2("membership_offer_expired");
|
|
3653
|
-
const restart = await load2(db, "membershipRestart",
|
|
3654
|
-
|
|
3696
|
+
const restart = await load2(db, "membershipRestart", id11.replace(/^checkout_/, "membership_"));
|
|
3697
|
+
let namedSeat;
|
|
3698
|
+
if (input.namedSeat) {
|
|
3699
|
+
if (!seatPriceId || !/^price_[A-Za-z0-9_]+$/.test(seatPriceId) || seatPriceId === input.priceId || restart) return reject2("named_seat_purchase_unavailable");
|
|
3700
|
+
const selection = checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail);
|
|
3701
|
+
namedSeat = {
|
|
3702
|
+
...selection,
|
|
3703
|
+
purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail),
|
|
3704
|
+
...await checkoutNamedSeatIdentity(input, selection.recipientEmail),
|
|
3705
|
+
priceId: seatPriceId,
|
|
3706
|
+
amountCents: offer.contract.offering.seatPriceCents
|
|
3707
|
+
};
|
|
3708
|
+
}
|
|
3709
|
+
const receipt = { id: id11, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: now, ...namedSeat ? { namedSeat } : {} };
|
|
3655
3710
|
try {
|
|
3656
|
-
await db.transact([
|
|
3657
|
-
|
|
3711
|
+
await db.transact([
|
|
3712
|
+
{ t: "update", ns: "membershipOfferCheckout", id: id11, attrs: receipt },
|
|
3713
|
+
...namedSeat ? [{ t: "update", ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, attrs: {
|
|
3714
|
+
id: namedSeat.recipientClaimId,
|
|
3715
|
+
appId: input.appId,
|
|
3716
|
+
chapterId: input.chapterId,
|
|
3717
|
+
environment: input.environment,
|
|
3718
|
+
runtime: input.runtime,
|
|
3719
|
+
seatId: namedSeat.seatId,
|
|
3720
|
+
recipientEmailNormalized: namedSeat.recipientEmail,
|
|
3721
|
+
createdAt: now
|
|
3722
|
+
} }] : []
|
|
3723
|
+
], {
|
|
3724
|
+
mutationId: `membership-checkout:${id11}${restart?.restartId ? `:${restart.restartId}` : ""}`,
|
|
3658
3725
|
guards: [
|
|
3659
|
-
{ ns: "membershipOfferCheckout", id:
|
|
3726
|
+
{ ns: "membershipOfferCheckout", id: id11, exists: false },
|
|
3727
|
+
...namedSeat ? [
|
|
3728
|
+
{ ns: "networkSeat", id: namedSeat.seatId, exists: false },
|
|
3729
|
+
{ ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, exists: false }
|
|
3730
|
+
] : [],
|
|
3660
3731
|
{ ns: "membershipCheckoutOffer", id: input.offerId, exists: true, equals: { digest: input.offerDigest } },
|
|
3661
3732
|
{ ns: "membershipAuthorityState", id: stateId, exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
3662
3733
|
]
|
|
@@ -3664,11 +3735,78 @@ async function bindMembershipCheckout(db, input, now) {
|
|
|
3664
3735
|
} catch (error) {
|
|
3665
3736
|
if (error.code !== "transact_guard_failed") throw error;
|
|
3666
3737
|
}
|
|
3667
|
-
const saved = await load2(db, "membershipOfferCheckout",
|
|
3738
|
+
const saved = await load2(db, "membershipOfferCheckout", id11);
|
|
3668
3739
|
if (!saved) return reject2("membership_binding_unconfirmed");
|
|
3669
3740
|
return verifyMembershipCheckoutBinding(saved, input);
|
|
3670
3741
|
}
|
|
3671
3742
|
|
|
3743
|
+
// src/membership-checkout-seat-payment.ts
|
|
3744
|
+
function checkoutSeatPaymentOperations(target, seat, input) {
|
|
3745
|
+
const { now, paidAt, endsAt } = input;
|
|
3746
|
+
const included = seat.amountCents === 0, entitlementId = `${seat.seatId}:entitlement`;
|
|
3747
|
+
const effectId = `${seat.seatId}:${included ? "invite" : "attach-renewal"}`;
|
|
3748
|
+
return [
|
|
3749
|
+
membershipUpdate("networkSeat", seat.seatId, {
|
|
3750
|
+
id: seat.seatId,
|
|
3751
|
+
...membershipScope(target),
|
|
3752
|
+
primaryMembershipId: seat.membershipId,
|
|
3753
|
+
purchaserEmailNormalized: seat.purchaserEmail,
|
|
3754
|
+
recipientName: seat.recipientName,
|
|
3755
|
+
recipientEmailNormalized: seat.recipientEmail,
|
|
3756
|
+
recipientClaimId: seat.recipientClaimId,
|
|
3757
|
+
priceId: seat.priceId,
|
|
3758
|
+
primaryPriceId: input.primaryPriceId,
|
|
3759
|
+
amountCents: seat.amountCents,
|
|
3760
|
+
currency: input.offering.currency,
|
|
3761
|
+
interval: input.offering.interval,
|
|
3762
|
+
providerCustomerId: input.customerId,
|
|
3763
|
+
providerSubscriptionId: input.subscriptionId,
|
|
3764
|
+
combinedInvoice: { invoiceId: input.invoiceId, totalCents: input.invoiceTotalCents },
|
|
3765
|
+
status: included ? "pending_acceptance" : "renewal_pending",
|
|
3766
|
+
preparedAt: input.boundAt,
|
|
3767
|
+
purchasedAt: paidAt,
|
|
3768
|
+
termStartedAt: paidAt,
|
|
3769
|
+
termEndsAt: endsAt,
|
|
3770
|
+
acceptanceEndsAt: endsAt,
|
|
3771
|
+
seatPurchaseEndsAt: seatPurchaseDeadline(input.offering, { initialTermStartedAt: paidAt, termEndsAt: endsAt }),
|
|
3772
|
+
offeringSnapshot: input.offering,
|
|
3773
|
+
paymentEventId: input.eventId,
|
|
3774
|
+
...included ? { entitlementId } : {},
|
|
3775
|
+
revision: 1,
|
|
3776
|
+
createdAt: now,
|
|
3777
|
+
updatedAt: now
|
|
3778
|
+
}),
|
|
3779
|
+
...included ? [membershipUpdate("networkEntitlement", entitlementId, {
|
|
3780
|
+
id: entitlementId,
|
|
3781
|
+
...membershipScope(target),
|
|
3782
|
+
membershipId: seat.membershipId,
|
|
3783
|
+
seatId: seat.seatId,
|
|
3784
|
+
kind: "named_seat",
|
|
3785
|
+
status: "pending_acceptance",
|
|
3786
|
+
termStartedAt: paidAt,
|
|
3787
|
+
termEndsAt: endsAt,
|
|
3788
|
+
offeringSnapshot: input.offering,
|
|
3789
|
+
sourceEventId: input.eventId,
|
|
3790
|
+
revision: 1,
|
|
3791
|
+
createdAt: now,
|
|
3792
|
+
updatedAt: now
|
|
3793
|
+
})] : [],
|
|
3794
|
+
membershipUpdate("membershipEffect", effectId, {
|
|
3795
|
+
id: effectId,
|
|
3796
|
+
chapterId: target.chapterId,
|
|
3797
|
+
environment: target.environment,
|
|
3798
|
+
runtime: target.runtime,
|
|
3799
|
+
membershipId: seat.membershipId,
|
|
3800
|
+
seatId: seat.seatId,
|
|
3801
|
+
kind: included ? "invite_named_recipient" : "attach_seat_renewal",
|
|
3802
|
+
status: "pending",
|
|
3803
|
+
attempts: 0,
|
|
3804
|
+
createdAt: now,
|
|
3805
|
+
updatedAt: now
|
|
3806
|
+
})
|
|
3807
|
+
];
|
|
3808
|
+
}
|
|
3809
|
+
|
|
3672
3810
|
// src/membership-payment-authority.ts
|
|
3673
3811
|
async function networkMembershipId(input) {
|
|
3674
3812
|
return (await membershipCheckoutBindingId(input)).replace(/^checkout_/, "membership_");
|
|
@@ -3684,11 +3822,14 @@ async function confirmMembershipPayment(db, input, now) {
|
|
|
3684
3822
|
{ ...input, offerId: String(binding.offerId), now, lifetimeMs: 6e4 }
|
|
3685
3823
|
);
|
|
3686
3824
|
if (binding.offerDigest !== offer.digest) membershipFail("membership_checkout_conflict");
|
|
3825
|
+
const { namedSeat } = await verifyMembershipCheckoutBinding(binding, binding);
|
|
3826
|
+
if (namedSeat && namedSeat.amountCents !== offer.contract.offering.seatPriceCents) membershipFail("membership_checkout_conflict");
|
|
3827
|
+
const totalCents = offer.contract.dueTodayCents + (namedSeat?.amountCents ?? 0);
|
|
3687
3828
|
const eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
|
|
3688
3829
|
if (!eventRow) membershipFail("membership_payment_evidence_pending");
|
|
3689
3830
|
const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow.digest);
|
|
3690
3831
|
const fact = eventRow.fact;
|
|
3691
|
-
if (!fact || !fact.identity || !/^[a-f0-9]{64}$/.test(String(eventRow.digest)) || fact.digest !== eventRow.digest || fact.eventId !== input.eventId || fact.eventType !== "invoice.payment_succeeded" || fact.objectKind !== "invoice" || fact.billingReason !== "subscription_create" || fact.livemode !== (input.environment === "prod") || fact.status !== "paid" || fact.applicationId !== input.applicationId || fact.subscriptionId !== input.providerSubscriptionId || fact.customerId !== input.providerCustomerId || !/^in_[A-Za-z0-9_]+$/.test(fact.invoiceId ?? "") || fact.currency !== offer.contract.offering.currency || fact.invoiceTotalCents !==
|
|
3832
|
+
if (!fact || !fact.identity || !/^[a-f0-9]{64}$/.test(String(eventRow.digest)) || fact.digest !== eventRow.digest || fact.eventId !== input.eventId || fact.eventType !== "invoice.payment_succeeded" || fact.objectKind !== "invoice" || fact.billingReason !== "subscription_create" || fact.livemode !== (input.environment === "prod") || fact.status !== "paid" || fact.applicationId !== input.applicationId || fact.subscriptionId !== input.providerSubscriptionId || fact.customerId !== input.providerCustomerId || !/^in_[A-Za-z0-9_]+$/.test(fact.invoiceId ?? "") || fact.currency !== offer.contract.offering.currency || fact.invoiceTotalCents !== totalCents || fact.amountPaidCents !== totalCents || Object.entries({
|
|
3692
3833
|
appId: input.appId,
|
|
3693
3834
|
chapterId: input.chapterId,
|
|
3694
3835
|
environment: input.environment,
|
|
@@ -3700,14 +3841,14 @@ async function confirmMembershipPayment(db, input, now) {
|
|
|
3700
3841
|
const paidAt = fact.invoicePaidAt;
|
|
3701
3842
|
const endsAt = offer.contract.initialTermEndsAt ?? fact.invoicePeriodEndsAt;
|
|
3702
3843
|
if (!Number.isSafeInteger(paidAt) || Number(paidAt) < Math.floor(Number(binding.boundAt) / 1e3) * 1e3 || Number(paidAt) > now + 3e4 || !Number.isSafeInteger(endsAt) || Number(endsAt) <= Number(paidAt)) membershipFail("membership_payment_period_mismatch");
|
|
3703
|
-
const
|
|
3704
|
-
let member = await membershipLoad(db, "networkMembership",
|
|
3844
|
+
const id11 = await networkMembershipId(input), entitlementId = `${id11}:primary`;
|
|
3845
|
+
let member = await membershipLoad(db, "networkMembership", id11);
|
|
3705
3846
|
if (!member || member.status === "restarting") {
|
|
3706
3847
|
const offering = offer.contract.offering;
|
|
3707
3848
|
const restart = member ? await membershipRestartPayment(db, input, member, now) : null;
|
|
3708
3849
|
const attrs = {
|
|
3709
|
-
id:
|
|
3710
|
-
membershipId:
|
|
3850
|
+
id: id11,
|
|
3851
|
+
membershipId: id11,
|
|
3711
3852
|
...membershipScope(input),
|
|
3712
3853
|
baseSkuId: input.skuId,
|
|
3713
3854
|
status: "paid_pending_approval",
|
|
@@ -3723,8 +3864,9 @@ async function confirmMembershipPayment(db, input, now) {
|
|
|
3723
3864
|
founding: offer.contract.founding,
|
|
3724
3865
|
cohort: offer.contract.founding ? offer.contract.discountPolicy.cohortLabel : "standard",
|
|
3725
3866
|
baseAmountCents: offering.basePriceCents,
|
|
3726
|
-
amountCents:
|
|
3867
|
+
amountCents: totalCents,
|
|
3727
3868
|
renewalAmountCents: offer.contract.renewalCents,
|
|
3869
|
+
...namedSeat ? { namedSeatId: namedSeat.seatId } : {},
|
|
3728
3870
|
currency: offering.currency,
|
|
3729
3871
|
interval: offering.interval,
|
|
3730
3872
|
initialTermStartedAt: paidAt,
|
|
@@ -3741,11 +3883,11 @@ async function confirmMembershipPayment(db, input, now) {
|
|
|
3741
3883
|
};
|
|
3742
3884
|
try {
|
|
3743
3885
|
await db.transact([
|
|
3744
|
-
membershipUpdate("networkMembership",
|
|
3886
|
+
membershipUpdate("networkMembership", id11, attrs),
|
|
3745
3887
|
membershipUpdate("networkEntitlement", entitlementId, {
|
|
3746
3888
|
id: entitlementId,
|
|
3747
3889
|
...membershipScope(input),
|
|
3748
|
-
membershipId:
|
|
3890
|
+
membershipId: id11,
|
|
3749
3891
|
kind: "primary",
|
|
3750
3892
|
status: restart?.attrs.status === "active" ? "active" : "pending_approval",
|
|
3751
3893
|
termStartedAt: paidAt,
|
|
@@ -3757,23 +3899,45 @@ async function confirmMembershipPayment(db, input, now) {
|
|
|
3757
3899
|
updatedAt: now,
|
|
3758
3900
|
...restart?.entitlementAttrs
|
|
3759
3901
|
}),
|
|
3760
|
-
...restart?.ops ?? []
|
|
3761
|
-
|
|
3762
|
-
|
|
3902
|
+
...restart?.ops ?? [],
|
|
3903
|
+
...namedSeat ? checkoutSeatPaymentOperations(input, namedSeat, {
|
|
3904
|
+
offering,
|
|
3905
|
+
primaryPriceId: String(binding.priceId),
|
|
3906
|
+
invoiceId: fact.invoiceId,
|
|
3907
|
+
invoiceTotalCents: totalCents,
|
|
3908
|
+
subscriptionId: input.providerSubscriptionId,
|
|
3909
|
+
customerId: input.providerCustomerId,
|
|
3910
|
+
eventId: input.eventId,
|
|
3911
|
+
paidAt: Number(paidAt),
|
|
3912
|
+
endsAt: Number(endsAt),
|
|
3913
|
+
boundAt: Number(binding.boundAt),
|
|
3914
|
+
now
|
|
3915
|
+
}) : []
|
|
3916
|
+
], { mutationId: `membership-paid:${id11}${restart ? `:${restart.restartId}` : ""}`, guards: [
|
|
3917
|
+
...restart?.guards ?? [{ ns: "networkMembership", id: id11, exists: false }],
|
|
3918
|
+
...namedSeat ? [
|
|
3919
|
+
{ ns: "networkSeat", id: namedSeat.seatId, exists: false },
|
|
3920
|
+
{
|
|
3921
|
+
ns: "networkSeatRecipient",
|
|
3922
|
+
id: namedSeat.recipientClaimId,
|
|
3923
|
+
exists: true,
|
|
3924
|
+
equals: { seatId: namedSeat.seatId, recipientEmailNormalized: namedSeat.recipientEmail }
|
|
3925
|
+
}
|
|
3926
|
+
] : [],
|
|
3763
3927
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
|
|
3764
|
-
{ ns: "membershipOfferCheckout", id: String(binding.id), exists: true, equals: { offerDigest: offer.digest } },
|
|
3928
|
+
{ ns: "membershipOfferCheckout", id: String(binding.id), exists: true, equals: { offerDigest: offer.digest, quoteDigest: binding.quoteDigest } },
|
|
3765
3929
|
{ ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
|
|
3766
3930
|
evidenceGuard
|
|
3767
3931
|
] });
|
|
3768
3932
|
} catch (error) {
|
|
3769
3933
|
if (error.code !== "transact_guard_failed") throw error;
|
|
3770
3934
|
}
|
|
3771
|
-
member = await membershipLoad(db, "networkMembership",
|
|
3935
|
+
member = await membershipLoad(db, "networkMembership", id11);
|
|
3772
3936
|
}
|
|
3773
3937
|
requireMembershipScope(member, input);
|
|
3774
3938
|
if (member.purchaseOfferDigest !== offer.digest || member.providerInitialInvoiceId !== fact.invoiceId || member.providerSubscriptionId !== fact.subscriptionId || member.providerCustomerId !== fact.customerId) membershipFail("membership_purchase_conflict");
|
|
3775
3939
|
return {
|
|
3776
|
-
membershipId:
|
|
3940
|
+
membershipId: id11,
|
|
3777
3941
|
status: String(member.status),
|
|
3778
3942
|
cohort: String(member.cohort),
|
|
3779
3943
|
initialTermStartedAt: Number(member.initialTermStartedAt),
|
|
@@ -3785,9 +3949,9 @@ async function confirmMembershipPayment(db, input, now) {
|
|
|
3785
3949
|
// src/membership-cancel-authority.ts
|
|
3786
3950
|
async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
|
|
3787
3951
|
membershipClock(now);
|
|
3788
|
-
const
|
|
3952
|
+
const id11 = await networkMembershipId(target);
|
|
3789
3953
|
const state = await loadMembershipAuthorityState(db, target);
|
|
3790
|
-
const member = await membershipLoad(db, "networkMembership",
|
|
3954
|
+
const member = await membershipLoad(db, "networkMembership", id11);
|
|
3791
3955
|
requireMembershipScope(member, target);
|
|
3792
3956
|
if (!["active", "cancel_at_term_end", "payment_grace", "paid_pending_approval", "lapsed"].includes(String(member.status))) {
|
|
3793
3957
|
membershipFail("membership_cancellation_unavailable");
|
|
@@ -3805,7 +3969,7 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
|
|
|
3805
3969
|
"POST",
|
|
3806
3970
|
path,
|
|
3807
3971
|
{ cancel_at_period_end: true, proration_behavior: "none" },
|
|
3808
|
-
{ idempotencyKey: `membership-cancel-at-term:${
|
|
3972
|
+
{ idempotencyKey: `membership-cancel-at-term:${id11}:${member.providerSubscriptionId}` }
|
|
3809
3973
|
);
|
|
3810
3974
|
if (!write.ok || write.body.id !== member.providerSubscriptionId || write.body.cancel_at_period_end !== true) {
|
|
3811
3975
|
membershipFail("membership_cancellation_pending");
|
|
@@ -3813,7 +3977,7 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
|
|
|
3813
3977
|
}
|
|
3814
3978
|
const status2 = Number(member.termEndsAt) <= now ? "lapsed" : member.journeyApprovedAt ? "cancel_at_term_end" : "paid_pending_approval";
|
|
3815
3979
|
if (member.status !== status2 || member.autoRenew !== false || Number(member.graceEndsAt ?? 0) !== 0) {
|
|
3816
|
-
await db.transact([membershipUpdate("networkMembership",
|
|
3980
|
+
await db.transact([membershipUpdate("networkMembership", id11, {
|
|
3817
3981
|
status: status2,
|
|
3818
3982
|
autoRenew: false,
|
|
3819
3983
|
graceEndsAt: 0,
|
|
@@ -3822,12 +3986,12 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
|
|
|
3822
3986
|
updatedAt: now
|
|
3823
3987
|
})], {
|
|
3824
3988
|
guards: [
|
|
3825
|
-
{ ns: "networkMembership", id:
|
|
3989
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
|
|
3826
3990
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
|
|
3827
3991
|
]
|
|
3828
3992
|
});
|
|
3829
3993
|
}
|
|
3830
|
-
return { membershipId:
|
|
3994
|
+
return { membershipId: id11, status: status2, termEndsAt: Number(member.termEndsAt), graceEndsAt: 0, autoRenew: false };
|
|
3831
3995
|
}
|
|
3832
3996
|
|
|
3833
3997
|
// src/membership-authority-handler.ts
|
|
@@ -3866,17 +4030,17 @@ async function namedSeatPrimaryActivation(db, member, now) {
|
|
|
3866
4030
|
// src/membership-approval-authority.ts
|
|
3867
4031
|
async function approveNetworkMembership(db, input, now) {
|
|
3868
4032
|
membershipClock(now);
|
|
3869
|
-
const
|
|
4033
|
+
const id11 = await networkMembershipId(input), entitlementId = `${id11}:primary`;
|
|
3870
4034
|
for (let attempt = 0; attempt < 8; attempt++) {
|
|
3871
4035
|
const state = await loadMembershipAuthorityState(db, input);
|
|
3872
|
-
const member = await membershipLoad(db, "networkMembership",
|
|
4036
|
+
const member = await membershipLoad(db, "networkMembership", id11);
|
|
3873
4037
|
requireMembershipScope(member, input);
|
|
3874
4038
|
const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
|
|
3875
4039
|
requireMembershipScope(entitlement, input);
|
|
3876
|
-
if (entitlement.membershipId !==
|
|
4040
|
+
if (entitlement.membershipId !== id11 || entitlement.kind !== "primary") membershipFail("membership_entitlement_conflict");
|
|
3877
4041
|
if (["active", "cancel_at_term_end"].includes(String(member.status)) && entitlement.status === "active" && Number(member.refundedCents) === 0 && Number.isSafeInteger(member.memberNumber) && Number(member.memberNumber) > 0 && Number(member.termEndsAt) > now) {
|
|
3878
4042
|
return {
|
|
3879
|
-
membershipId:
|
|
4043
|
+
membershipId: id11,
|
|
3880
4044
|
status: String(member.status),
|
|
3881
4045
|
cohort: String(member.cohort),
|
|
3882
4046
|
memberNumber: Number(member.memberNumber),
|
|
@@ -3896,7 +4060,7 @@ async function approveNetworkMembership(db, input, now) {
|
|
|
3896
4060
|
const seatActivation = await namedSeatPrimaryActivation(db, member, now);
|
|
3897
4061
|
try {
|
|
3898
4062
|
await db.transact([
|
|
3899
|
-
membershipUpdate("networkMembership",
|
|
4063
|
+
membershipUpdate("networkMembership", id11, {
|
|
3900
4064
|
status: member.autoRenew ? "active" : "cancel_at_term_end",
|
|
3901
4065
|
memberNumber,
|
|
3902
4066
|
journeyApprovedAt: now,
|
|
@@ -3911,8 +4075,8 @@ async function approveNetworkMembership(db, input, now) {
|
|
|
3911
4075
|
updatedAt: now
|
|
3912
4076
|
}),
|
|
3913
4077
|
...seatActivation.ops
|
|
3914
|
-
], { mutationId: `membership-approve:${
|
|
3915
|
-
{ ns: "networkMembership", id:
|
|
4078
|
+
], { mutationId: `membership-approve:${id11}`, guards: [
|
|
4079
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision, status: "paid_pending_approval" }, absent: ["memberNumber"] },
|
|
3916
4080
|
{ ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision, status: "pending_approval" } },
|
|
3917
4081
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
|
|
3918
4082
|
...seatActivation.guards
|
|
@@ -4011,41 +4175,41 @@ async function refundSubscriptionPayment(secretKey, input) {
|
|
|
4011
4175
|
// src/membership-refund-authority.ts
|
|
4012
4176
|
async function refundNetworkMembership(db, input, secretKey, now) {
|
|
4013
4177
|
membershipClock(now);
|
|
4014
|
-
const
|
|
4015
|
-
let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership",
|
|
4178
|
+
const id11 = await networkMembershipId(input), entitlementId = `${id11}:primary`;
|
|
4179
|
+
let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
|
|
4016
4180
|
requireMembershipScope(member, input);
|
|
4017
|
-
if (member.status === "refunded") return { membershipId:
|
|
4181
|
+
if (member.status === "refunded") return { membershipId: id11, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
|
|
4018
4182
|
if (!["paid_pending_approval", "active", "cancel_at_term_end", "payment_grace", "lapsed", "refund_pending"].includes(String(member.status))) membershipFail("membership_refund_not_eligible");
|
|
4019
4183
|
if (member.status !== "refund_pending") {
|
|
4020
4184
|
const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
|
|
4021
4185
|
requireMembershipScope(entitlement, input);
|
|
4022
4186
|
await db.transact([
|
|
4023
|
-
membershipUpdate("networkMembership",
|
|
4187
|
+
membershipUpdate("networkMembership", id11, {
|
|
4024
4188
|
status: "refund_pending",
|
|
4025
4189
|
refundRequestedAt: now,
|
|
4026
4190
|
revision: Number(member.revision) + 1,
|
|
4027
4191
|
updatedAt: now
|
|
4028
4192
|
}),
|
|
4029
4193
|
membershipUpdate("networkEntitlement", entitlementId, { status: "closed", revision: Number(entitlement.revision) + 1, updatedAt: now }),
|
|
4030
|
-
membershipUpdate("membershipEffect", `${
|
|
4031
|
-
id: `${
|
|
4194
|
+
membershipUpdate("membershipEffect", `${id11}:refund`, {
|
|
4195
|
+
id: `${id11}:refund`,
|
|
4032
4196
|
chapterId: input.chapterId,
|
|
4033
4197
|
environment: input.environment,
|
|
4034
4198
|
runtime: input.runtime,
|
|
4035
|
-
membershipId:
|
|
4199
|
+
membershipId: id11,
|
|
4036
4200
|
kind: "refund_membership",
|
|
4037
4201
|
status: "pending",
|
|
4038
4202
|
attempts: 0,
|
|
4039
4203
|
createdAt: now,
|
|
4040
4204
|
updatedAt: now
|
|
4041
4205
|
})
|
|
4042
|
-
], { mutationId: `membership-refund-request:${
|
|
4043
|
-
{ ns: "networkMembership", id:
|
|
4206
|
+
], { mutationId: `membership-refund-request:${id11}`, guards: [
|
|
4207
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
|
|
4044
4208
|
{ ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision } },
|
|
4045
4209
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
|
|
4046
4210
|
] });
|
|
4047
4211
|
}
|
|
4048
|
-
member = await membershipLoad(db, "networkMembership",
|
|
4212
|
+
member = await membershipLoad(db, "networkMembership", id11);
|
|
4049
4213
|
requireMembershipScope(member, input);
|
|
4050
4214
|
if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
|
|
4051
4215
|
const result = await refundSubscriptionPayment(secretKey, {
|
|
@@ -4059,23 +4223,23 @@ async function refundNetworkMembership(db, input, secretKey, now) {
|
|
|
4059
4223
|
if (!result.subscriptionCanceled || result.refundedCents !== member.amountCents) membershipFail("membership_refund_unconfirmed");
|
|
4060
4224
|
for (let attempt = 0; attempt < 8; attempt++) {
|
|
4061
4225
|
state = await loadMembershipAuthorityState(db, input);
|
|
4062
|
-
member = await membershipLoad(db, "networkMembership",
|
|
4226
|
+
member = await membershipLoad(db, "networkMembership", id11);
|
|
4063
4227
|
requireMembershipScope(member, input);
|
|
4064
|
-
if (member.status === "refunded") return { membershipId:
|
|
4228
|
+
if (member.status === "refunded") return { membershipId: id11, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
|
|
4065
4229
|
if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
|
|
4066
4230
|
const counted = member.founding === true && member.memberNumber !== void 0;
|
|
4067
4231
|
const counts = state.cohortCounts;
|
|
4068
4232
|
if (!counts || counted && (!Number.isSafeInteger(counts[String(member.cohort)]) || counts[String(member.cohort)] < 1)) membershipFail("invalid_cohort_count");
|
|
4069
4233
|
try {
|
|
4070
4234
|
await db.transact([
|
|
4071
|
-
membershipUpdate("networkMembership",
|
|
4235
|
+
membershipUpdate("networkMembership", id11, {
|
|
4072
4236
|
status: "refunded",
|
|
4073
4237
|
refundedCents: result.refundedCents,
|
|
4074
4238
|
autoRenew: false,
|
|
4075
4239
|
revision: Number(member.revision) + 1,
|
|
4076
4240
|
updatedAt: now
|
|
4077
4241
|
}),
|
|
4078
|
-
membershipUpdate("membershipEffect", `${
|
|
4242
|
+
membershipUpdate("membershipEffect", `${id11}:refund`, { status: "complete", updatedAt: now }),
|
|
4079
4243
|
...counted ? [membershipUpdate("membershipAuthorityState", String(state.id), {
|
|
4080
4244
|
cohortCounts: { ...counts, [String(member.cohort)]: counts[String(member.cohort)] - 1 },
|
|
4081
4245
|
revision: Number(state.revision) + 1,
|
|
@@ -4086,7 +4250,7 @@ async function refundNetworkMembership(db, input, secretKey, now) {
|
|
|
4086
4250
|
chapterId: input.chapterId,
|
|
4087
4251
|
environment: input.environment,
|
|
4088
4252
|
runtime: input.runtime,
|
|
4089
|
-
membershipId:
|
|
4253
|
+
membershipId: id11,
|
|
4090
4254
|
seatId: member.namedSeatId,
|
|
4091
4255
|
kind: "refund_seat_with_primary",
|
|
4092
4256
|
status: "pending",
|
|
@@ -4094,8 +4258,8 @@ async function refundNetworkMembership(db, input, secretKey, now) {
|
|
|
4094
4258
|
createdAt: now,
|
|
4095
4259
|
updatedAt: now
|
|
4096
4260
|
})] : []
|
|
4097
|
-
], { mutationId: `membership-refunded:${
|
|
4098
|
-
{ ns: "networkMembership", id:
|
|
4261
|
+
], { mutationId: `membership-refunded:${id11}`, guards: [
|
|
4262
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { status: "refund_pending", revision: member.revision } },
|
|
4099
4263
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
4100
4264
|
] });
|
|
4101
4265
|
} catch (error) {
|
|
@@ -4123,8 +4287,8 @@ async function reserveNamedMembershipSeat(db, input, now) {
|
|
|
4123
4287
|
const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
|
|
4124
4288
|
requireMembershipScope(member, input);
|
|
4125
4289
|
if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
|
|
4126
|
-
const
|
|
4127
|
-
let seat = await membershipLoad(db, "networkSeat",
|
|
4290
|
+
const id11 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
4291
|
+
let seat = await membershipLoad(db, "networkSeat", id11);
|
|
4128
4292
|
if (!seat) {
|
|
4129
4293
|
const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
|
|
4130
4294
|
const purchaseEndsAt = seatPurchaseDeadline(offering, member);
|
|
@@ -4132,7 +4296,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
|
|
|
4132
4296
|
membershipFail("named_seat_purchase_unavailable");
|
|
4133
4297
|
}
|
|
4134
4298
|
const attrs = {
|
|
4135
|
-
id:
|
|
4299
|
+
id: id11,
|
|
4136
4300
|
...membershipScope(input),
|
|
4137
4301
|
primaryMembershipId: memberId,
|
|
4138
4302
|
recipientName: name,
|
|
@@ -4157,20 +4321,20 @@ async function reserveNamedMembershipSeat(db, input, now) {
|
|
|
4157
4321
|
};
|
|
4158
4322
|
try {
|
|
4159
4323
|
await db.transact([
|
|
4160
|
-
membershipUpdate("networkSeat",
|
|
4324
|
+
membershipUpdate("networkSeat", id11, attrs),
|
|
4161
4325
|
membershipUpdate("networkSeatRecipient", claimId, {
|
|
4162
4326
|
id: claimId,
|
|
4163
4327
|
appId: input.appId,
|
|
4164
4328
|
chapterId: input.chapterId,
|
|
4165
4329
|
environment: input.environment,
|
|
4166
4330
|
runtime: input.runtime,
|
|
4167
|
-
seatId:
|
|
4331
|
+
seatId: id11,
|
|
4168
4332
|
recipientEmailNormalized: email,
|
|
4169
4333
|
createdAt: now
|
|
4170
4334
|
}),
|
|
4171
|
-
membershipUpdate("networkMembership", memberId, { namedSeatId:
|
|
4172
|
-
], { mutationId: `named-seat-reserve:${
|
|
4173
|
-
{ ns: "networkSeat", id:
|
|
4335
|
+
membershipUpdate("networkMembership", memberId, { namedSeatId: id11, revision: Number(member.revision) + 1, updatedAt: now })
|
|
4336
|
+
], { mutationId: `named-seat-reserve:${id11}`, guards: [
|
|
4337
|
+
{ ns: "networkSeat", id: id11, exists: false },
|
|
4174
4338
|
{ ns: "networkSeatRecipient", id: claimId, exists: false },
|
|
4175
4339
|
{ ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
|
|
4176
4340
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
@@ -4178,7 +4342,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
|
|
|
4178
4342
|
} catch (error) {
|
|
4179
4343
|
if (error.code !== "transact_guard_failed") throw error;
|
|
4180
4344
|
}
|
|
4181
|
-
seat = await membershipLoad(db, "networkSeat",
|
|
4345
|
+
seat = await membershipLoad(db, "networkSeat", id11);
|
|
4182
4346
|
if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
|
|
4183
4347
|
}
|
|
4184
4348
|
requireMembershipScope(seat, input);
|
|
@@ -4188,7 +4352,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
|
|
|
4188
4352
|
|
|
4189
4353
|
// src/membership-seat-included.ts
|
|
4190
4354
|
var namedSeatIncluded = (seat) => Number(seat.amountCents) === 0;
|
|
4191
|
-
var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean(seat.providerPaymentIntentId && seat.providerSubscriptionItemId);
|
|
4355
|
+
var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean((seat.providerPaymentIntentId || seat.combinedInvoice) && seat.providerSubscriptionItemId);
|
|
4192
4356
|
function namedSeatRecipientState(seat) {
|
|
4193
4357
|
switch (String(seat.status)) {
|
|
4194
4358
|
case "renewal_pending":
|
|
@@ -4286,7 +4450,7 @@ async function grantIncludedNamedSeat(db, input, now) {
|
|
|
4286
4450
|
// src/membership-renewal-authority.ts
|
|
4287
4451
|
async function reconcileMembershipRenewal(db, input, now) {
|
|
4288
4452
|
membershipClock(now);
|
|
4289
|
-
const
|
|
4453
|
+
const id11 = await networkMembershipId(input), eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
|
|
4290
4454
|
const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow?.digest, "membership_renewal_evidence_pending");
|
|
4291
4455
|
const fact = eventRow?.fact;
|
|
4292
4456
|
if (!fact || !/^evt_[A-Za-z0-9_]+$/.test(input.eventId) || !/^[a-f0-9]{64}$/.test(String(eventRow.digest)) || fact.digest !== eventRow.digest || fact.eventId !== input.eventId || fact.objectKind !== "invoice" || !["invoice.payment_succeeded", "invoice.payment_failed"].includes(fact.eventType) || fact.billingReason !== "subscription_cycle" || fact.livemode !== (input.environment === "prod") || fact.applicationId !== input.applicationId || Object.entries({
|
|
@@ -4299,10 +4463,10 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
4299
4463
|
return membershipFail("membership_renewal_evidence_pending");
|
|
4300
4464
|
}
|
|
4301
4465
|
for (let attempt = 0; attempt < 8; attempt++) {
|
|
4302
|
-
const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership",
|
|
4466
|
+
const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
|
|
4303
4467
|
requireMembershipScope(member, input);
|
|
4304
4468
|
const receipt = () => ({
|
|
4305
|
-
membershipId:
|
|
4469
|
+
membershipId: id11,
|
|
4306
4470
|
status: String(member.status),
|
|
4307
4471
|
termEndsAt: Number(member.termEndsAt),
|
|
4308
4472
|
graceEndsAt: Number(member.graceEndsAt ?? 0),
|
|
@@ -4327,13 +4491,13 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
4327
4491
|
const paid = fact.eventType === "invoice.payment_succeeded";
|
|
4328
4492
|
if (!paid && (fact.status !== "open" || !Number.isSafeInteger(fact.amountPaidCents) || Number(fact.amountPaidCents) < 0 || Number(fact.amountPaidCents) >= amount)) membershipFail("membership_renewal_failure_mismatch");
|
|
4329
4493
|
if (paid && (fact.status !== "paid" || fact.amountPaidCents !== amount || !Number.isSafeInteger(fact.invoicePaidAt) || Number(fact.invoicePaidAt) < start || Number(fact.invoicePaidAt) > now + 3e4)) membershipFail("membership_renewal_unpaid");
|
|
4330
|
-
const entitlement = await membershipLoad(db, "networkEntitlement", `${
|
|
4494
|
+
const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
|
|
4331
4495
|
requireMembershipScope(entitlement, input);
|
|
4332
4496
|
const graceEndsAt = paid ? 0 : start + 14 * 864e5;
|
|
4333
4497
|
const status2 = paid ? member.journeyApprovedAt ? member.autoRenew ? "active" : "cancel_at_term_end" : "paid_pending_approval" : now < graceEndsAt ? "payment_grace" : "lapsed";
|
|
4334
4498
|
const entitlementStatus = member.journeyApprovedAt && (paid || now < graceEndsAt) ? "active" : member.journeyApprovedAt ? "closed" : "pending_approval";
|
|
4335
4499
|
const ops = [
|
|
4336
|
-
membershipUpdate("networkMembership",
|
|
4500
|
+
membershipUpdate("networkMembership", id11, {
|
|
4337
4501
|
status: status2,
|
|
4338
4502
|
graceEndsAt,
|
|
4339
4503
|
...paid ? {
|
|
@@ -4344,7 +4508,7 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
4344
4508
|
revision: Number(member.revision) + 1,
|
|
4345
4509
|
updatedAt: now
|
|
4346
4510
|
}),
|
|
4347
|
-
membershipUpdate("networkEntitlement", `${
|
|
4511
|
+
membershipUpdate("networkEntitlement", `${id11}:primary`, {
|
|
4348
4512
|
status: entitlementStatus,
|
|
4349
4513
|
...paid ? { termStartedAt: start, termEndsAt: end } : {},
|
|
4350
4514
|
sourceEventId: input.eventId,
|
|
@@ -4353,8 +4517,8 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
4353
4517
|
})
|
|
4354
4518
|
];
|
|
4355
4519
|
const guards = [
|
|
4356
|
-
{ ns: "networkMembership", id:
|
|
4357
|
-
{ ns: "networkEntitlement", id: `${
|
|
4520
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
|
|
4521
|
+
{ ns: "networkEntitlement", id: `${id11}:primary`, exists: true, equals: { revision: entitlement.revision } },
|
|
4358
4522
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
|
|
4359
4523
|
{ ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
|
|
4360
4524
|
evidenceGuard
|
|
@@ -4379,15 +4543,15 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
4379
4543
|
);
|
|
4380
4544
|
}
|
|
4381
4545
|
try {
|
|
4382
|
-
await db.transact(ops, { mutationId: `membership-renewal:${
|
|
4546
|
+
await db.transact(ops, { mutationId: `membership-renewal:${id11}:${input.eventId}`, guards });
|
|
4383
4547
|
} catch (error) {
|
|
4384
4548
|
if (error.code !== "transact_guard_failed") throw error;
|
|
4385
4549
|
continue;
|
|
4386
4550
|
}
|
|
4387
|
-
const saved = await membershipLoad(db, "networkMembership",
|
|
4551
|
+
const saved = await membershipLoad(db, "networkMembership", id11);
|
|
4388
4552
|
requireMembershipScope(saved, input);
|
|
4389
4553
|
return {
|
|
4390
|
-
membershipId:
|
|
4554
|
+
membershipId: id11,
|
|
4391
4555
|
status: String(saved.status),
|
|
4392
4556
|
termEndsAt: Number(saved.termEndsAt),
|
|
4393
4557
|
graceEndsAt: Number(saved.graceEndsAt ?? 0),
|
|
@@ -4413,12 +4577,12 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
|
|
|
4413
4577
|
}).some(([key3, value]) => fact.identity[key3] !== value)) membershipFail("membership_billing_evidence_pending");
|
|
4414
4578
|
const sk = await getVaultSecret(db, stripeSecretName);
|
|
4415
4579
|
if (!sk) membershipFail("membership_billing_provider_unavailable");
|
|
4416
|
-
const
|
|
4580
|
+
const id11 = await networkMembershipId(input);
|
|
4417
4581
|
for (let attempt = 0; attempt < 8; attempt++) {
|
|
4418
|
-
const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership",
|
|
4582
|
+
const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
|
|
4419
4583
|
requireMembershipScope(member, input);
|
|
4420
4584
|
const receipt = (value = member) => ({
|
|
4421
|
-
membershipId:
|
|
4585
|
+
membershipId: id11,
|
|
4422
4586
|
status: String(value.status),
|
|
4423
4587
|
termEndsAt: Number(value.termEndsAt),
|
|
4424
4588
|
graceEndsAt: Number(value.graceEndsAt ?? 0),
|
|
@@ -4439,7 +4603,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
|
|
|
4439
4603
|
});
|
|
4440
4604
|
await commercialEvidenceGuard(db, input.eventId, row2?.digest, "membership_billing_evidence_pending");
|
|
4441
4605
|
await refundNetworkMembership(db, input, sk, now);
|
|
4442
|
-
const saved2 = await membershipLoad(db, "networkMembership",
|
|
4606
|
+
const saved2 = await membershipLoad(db, "networkMembership", id11);
|
|
4443
4607
|
requireMembershipScope(saved2, input);
|
|
4444
4608
|
return receipt(saved2);
|
|
4445
4609
|
}
|
|
@@ -4454,18 +4618,18 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
|
|
|
4454
4618
|
const status2 = autoRenew ? member.status === "cancel_at_term_end" ? "active" : String(member.status) : now >= Number(member.termEndsAt) ? "lapsed" : member.journeyApprovedAt ? "cancel_at_term_end" : "paid_pending_approval";
|
|
4455
4619
|
const graceEndsAt = autoRenew ? Number(member.graceEndsAt ?? 0) : 0;
|
|
4456
4620
|
if (member.autoRenew === autoRenew && member.status === status2 && Number(member.graceEndsAt ?? 0) === graceEndsAt) return receipt();
|
|
4457
|
-
const entitlement = await membershipLoad(db, "networkEntitlement", `${
|
|
4621
|
+
const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
|
|
4458
4622
|
requireMembershipScope(entitlement, input);
|
|
4459
4623
|
try {
|
|
4460
4624
|
await db.transact([
|
|
4461
|
-
membershipUpdate("networkMembership",
|
|
4625
|
+
membershipUpdate("networkMembership", id11, { status: status2, autoRenew, graceEndsAt, revision: Number(member.revision) + 1, updatedAt: now }),
|
|
4462
4626
|
membershipUpdate("networkEntitlement", String(entitlement.id), {
|
|
4463
4627
|
status: status2 === "lapsed" ? "closed" : entitlement.status,
|
|
4464
4628
|
revision: Number(entitlement.revision) + 1,
|
|
4465
4629
|
updatedAt: now
|
|
4466
4630
|
})
|
|
4467
4631
|
], { guards: [
|
|
4468
|
-
{ ns: "networkMembership", id:
|
|
4632
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
|
|
4469
4633
|
{ ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision } },
|
|
4470
4634
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
|
|
4471
4635
|
{ ns: "networkCommercialEvents", id: String(row2.id), exists: true, equals: { digest: fact.digest } },
|
|
@@ -4475,7 +4639,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
|
|
|
4475
4639
|
if (error.code !== "transact_guard_failed") throw error;
|
|
4476
4640
|
continue;
|
|
4477
4641
|
}
|
|
4478
|
-
const saved = await membershipLoad(db, "networkMembership",
|
|
4642
|
+
const saved = await membershipLoad(db, "networkMembership", id11);
|
|
4479
4643
|
requireMembershipScope(saved, input);
|
|
4480
4644
|
return receipt(saved);
|
|
4481
4645
|
}
|
|
@@ -4536,19 +4700,19 @@ async function stopPreviousSubscription(member, target, secret) {
|
|
|
4536
4700
|
async function prepareMembershipRestart(db, input, now, stripeSecret) {
|
|
4537
4701
|
membershipClock(now);
|
|
4538
4702
|
if (!/^sub_[A-Za-z0-9_]+$/.test(input.previousSubscriptionId)) membershipFail("membership_restart_conflict");
|
|
4539
|
-
const
|
|
4540
|
-
const restartId = `restart_${await stripePayloadDigest(JSON.stringify([
|
|
4703
|
+
const id11 = await networkMembershipId(input), bindingId = await membershipCheckoutBindingId(input);
|
|
4704
|
+
const restartId = `restart_${await stripePayloadDigest(JSON.stringify([id11, input.previousSubscriptionId]))}`;
|
|
4541
4705
|
const state = await loadMembershipAuthorityState(db, input);
|
|
4542
|
-
const previous = await membershipLoad(db, "networkMembership",
|
|
4706
|
+
const previous = await membershipLoad(db, "networkMembership", id11);
|
|
4543
4707
|
requireMembershipScope(previous, input);
|
|
4544
4708
|
const receipt = {
|
|
4545
4709
|
applicationId: input.applicationId,
|
|
4546
|
-
membershipId:
|
|
4710
|
+
membershipId: id11,
|
|
4547
4711
|
restartId,
|
|
4548
4712
|
memberNumber: Number(previous.memberNumber),
|
|
4549
4713
|
status: "ready"
|
|
4550
4714
|
};
|
|
4551
|
-
const existing = await membershipLoad(db, "membershipRestart",
|
|
4715
|
+
const existing = await membershipLoad(db, "membershipRestart", id11);
|
|
4552
4716
|
if (existing?.restartId === restartId && existing.status === "ready") {
|
|
4553
4717
|
requireMembershipScope(existing, input);
|
|
4554
4718
|
return receipt;
|
|
@@ -4564,33 +4728,33 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
|
|
|
4564
4728
|
membershipUpdate("membershipPurchaseHistory", restartId, {
|
|
4565
4729
|
id: restartId,
|
|
4566
4730
|
...membershipScope(input),
|
|
4567
|
-
membershipId:
|
|
4731
|
+
membershipId: id11,
|
|
4568
4732
|
providerSubscriptionId: input.previousSubscriptionId,
|
|
4569
4733
|
memberSnapshot: previous,
|
|
4570
4734
|
bindingSnapshot: binding2,
|
|
4571
4735
|
archivedAt: now
|
|
4572
4736
|
}),
|
|
4573
|
-
membershipUpdate("networkMembership",
|
|
4737
|
+
membershipUpdate("networkMembership", id11, { status: "restarting", restartId, revision: Number(previous.revision) + 1, updatedAt: now })
|
|
4574
4738
|
], { mutationId: `membership-restart-reserve:${restartId}`, guards: [
|
|
4575
4739
|
...seats.guards,
|
|
4576
|
-
{ ns: "networkMembership", id:
|
|
4740
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: previous.revision, status: previous.status } },
|
|
4577
4741
|
{ ns: "membershipPurchaseHistory", id: restartId, exists: false },
|
|
4578
4742
|
{ ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding2.offerDigest } },
|
|
4579
4743
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
4580
4744
|
] });
|
|
4581
4745
|
} else if (previous.restartId !== restartId) membershipFail("membership_restart_conflict");
|
|
4582
|
-
const reserved = await membershipLoad(db, "networkMembership",
|
|
4746
|
+
const reserved = await membershipLoad(db, "networkMembership", id11);
|
|
4583
4747
|
requireMembershipScope(reserved, input);
|
|
4584
4748
|
if (reserved.status !== "restarting" || reserved.restartId !== restartId) membershipFail("membership_restart_conflict");
|
|
4585
4749
|
await stopPreviousSubscription(reserved, input, stripeSecret);
|
|
4586
4750
|
const binding = await membershipLoad(db, "membershipOfferCheckout", bindingId);
|
|
4587
4751
|
requireMembershipScope(binding, input);
|
|
4588
4752
|
await db.transact([
|
|
4589
|
-
membershipUpdate("membershipRestart",
|
|
4753
|
+
membershipUpdate("membershipRestart", id11, { id: id11, ...membershipScope(input), ...receipt, previousSubscriptionId: input.previousSubscriptionId, createdAt: now }),
|
|
4590
4754
|
{ t: "delete", ns: "membershipOfferCheckout", id: bindingId }
|
|
4591
4755
|
], { mutationId: `membership-restart-ready:${restartId}`, guards: [
|
|
4592
|
-
{ ns: "membershipRestart", id:
|
|
4593
|
-
{ ns: "networkMembership", id:
|
|
4756
|
+
{ ns: "membershipRestart", id: id11, exists: Boolean(existing), ...existing ? { equals: { restartId: existing.restartId, status: existing.status } } : {} },
|
|
4757
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: reserved.revision, status: "restarting", restartId } },
|
|
4594
4758
|
{ ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding.offerDigest } }
|
|
4595
4759
|
] });
|
|
4596
4760
|
return receipt;
|
|
@@ -4600,15 +4764,15 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
|
|
|
4600
4764
|
async function readMembershipAccess(db, input, now) {
|
|
4601
4765
|
membershipClock(now);
|
|
4602
4766
|
await loadMembershipAuthorityState(db, input);
|
|
4603
|
-
const
|
|
4604
|
-
if (!member) return { membershipId:
|
|
4767
|
+
const id11 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id11);
|
|
4768
|
+
if (!member) return { membershipId: id11, allowed: false, status: "unpaid", accessEndsAt: 0, revision: 0 };
|
|
4605
4769
|
requireMembershipScope(member, input);
|
|
4606
|
-
const entitlement = await membershipLoad(db, "networkEntitlement", `${
|
|
4770
|
+
const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
|
|
4607
4771
|
requireMembershipScope(entitlement, input);
|
|
4608
4772
|
const endsAt = member.status === "payment_grace" ? Number(member.graceEndsAt) : Number(member.termEndsAt);
|
|
4609
|
-
const allowed2 = ["active", "cancel_at_term_end", "payment_grace"].includes(String(member.status)) && member.journeyApprovedAt !== void 0 && Number.isSafeInteger(member.memberNumber) && Number(member.memberNumber) > 0 && member.refundedCents === 0 && entitlement.status === "active" && entitlement.membershipId ===
|
|
4773
|
+
const allowed2 = ["active", "cancel_at_term_end", "payment_grace"].includes(String(member.status)) && member.journeyApprovedAt !== void 0 && Number.isSafeInteger(member.memberNumber) && Number(member.memberNumber) > 0 && member.refundedCents === 0 && entitlement.status === "active" && entitlement.membershipId === id11 && entitlement.kind === "primary" && Number.isSafeInteger(endsAt) && endsAt > now && Number(member.termStartedAt) <= now;
|
|
4610
4774
|
return {
|
|
4611
|
-
membershipId:
|
|
4775
|
+
membershipId: id11,
|
|
4612
4776
|
allowed: allowed2,
|
|
4613
4777
|
status: String(member.status),
|
|
4614
4778
|
accessEndsAt: Number.isSafeInteger(endsAt) ? endsAt : 0,
|
|
@@ -4663,7 +4827,7 @@ async function subscriptionForSeat(sk, seat) {
|
|
|
4663
4827
|
currency: seat.currency,
|
|
4664
4828
|
interval: seat.interval,
|
|
4665
4829
|
environment: seat.environment
|
|
4666
|
-
}, Boolean(seat.providerPaymentIntentId));
|
|
4830
|
+
}, Boolean(seat.providerPaymentIntentId || seat.combinedInvoice));
|
|
4667
4831
|
if (!productId) reject3();
|
|
4668
4832
|
const sub = await read(sk, `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}?expand[]=discounts`);
|
|
4669
4833
|
const items = sub.items?.data;
|
|
@@ -4707,10 +4871,14 @@ async function createNamedSeatStripePayment(sk, seat, now) {
|
|
|
4707
4871
|
return { paymentIntentId: pi.id, clientSecret: pi.client_secret, status: pi.status };
|
|
4708
4872
|
}
|
|
4709
4873
|
async function attachNamedSeatStripeRenewal(sk, seat) {
|
|
4710
|
-
if (!seat.providerPaymentIntentId || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject3();
|
|
4874
|
+
if (!seat.providerPaymentIntentId && !seat.combinedInvoice || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject3();
|
|
4711
4875
|
const sub = await subscriptionForSeat(sk, seat);
|
|
4712
4876
|
const existing = sub.items.data.find((item2) => item2.metadata?.seatId === seat.id);
|
|
4713
|
-
if (existing)
|
|
4877
|
+
if (existing) {
|
|
4878
|
+
if (!/^si_[A-Za-z0-9_]+$/.test(existing.id)) reject3();
|
|
4879
|
+
return String(existing.id);
|
|
4880
|
+
}
|
|
4881
|
+
if (seat.combinedInvoice) reject3();
|
|
4714
4882
|
const result = await stripeRequest7({ secretKey: sk }, "POST", "/v1/subscription_items", {
|
|
4715
4883
|
subscription: seat.providerSubscriptionId,
|
|
4716
4884
|
price: seat.priceId,
|
|
@@ -4928,7 +5096,7 @@ async function recordRecipient(db, input, now, approve) {
|
|
|
4928
5096
|
const state = await loadMembershipAuthorityState(db, input);
|
|
4929
5097
|
const seat = await membershipLoad(db, "networkSeat", input.seatId);
|
|
4930
5098
|
requireMembershipScope(seat, input);
|
|
4931
|
-
if (!seat.purchaserUserId || seat.purchaserUserId === input.recipientUserId) membershipFail("named_seat_recipient_must_differ_from_purchaser");
|
|
5099
|
+
if (!seat.purchaserUserId && !seat.purchaserEmailNormalized || seat.purchaserUserId === input.recipientUserId || seat.purchaserEmailNormalized === normalizeChapterEmail(input.verifiedRecipientEmail)) membershipFail("named_seat_recipient_must_differ_from_purchaser");
|
|
4932
5100
|
const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
|
|
4933
5101
|
requireMembershipScope(member, input);
|
|
4934
5102
|
const entitlement = await membershipLoad(db, "networkEntitlement", String(seat.entitlementId));
|
|
@@ -4986,43 +5154,121 @@ async function recordRecipient(db, input, now, approve) {
|
|
|
4986
5154
|
}
|
|
4987
5155
|
|
|
4988
5156
|
// src/membership-seat-refund-stripe.ts
|
|
5157
|
+
import { stripeRequest as stripeRequest9 } from "@odla-ai/stripe";
|
|
5158
|
+
|
|
5159
|
+
// src/membership-checkout-seat-refund.ts
|
|
4989
5160
|
import { stripeRequest as stripeRequest8 } from "@odla-ai/stripe";
|
|
4990
5161
|
var fail4 = (code = "named_seat_refund_mismatch") => {
|
|
4991
5162
|
throw Object.assign(new Error(code), { code });
|
|
4992
5163
|
};
|
|
5164
|
+
var id8 = (value) => typeof value === "string" ? value : value?.id ?? "";
|
|
5165
|
+
async function refundCheckoutNamedSeat(secretKey, seat) {
|
|
5166
|
+
const invoiceRef = seat.combinedInvoice;
|
|
5167
|
+
if (!invoiceRef || !/^in_[A-Za-z0-9_]+$/.test(invoiceRef.invoiceId) || !Number.isSafeInteger(invoiceRef.totalCents) || invoiceRef.totalCents <= seat.amountCents || seat.amountCents <= 0 || seat.status !== "refund_pending") fail4();
|
|
5168
|
+
const call = async (method, path, params, key3) => {
|
|
5169
|
+
const result = await stripeRequest8({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
|
|
5170
|
+
if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
|
|
5171
|
+
return result.body;
|
|
5172
|
+
};
|
|
5173
|
+
const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId };
|
|
5174
|
+
const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
|
|
5175
|
+
if (sub.id !== seat.providerSubscriptionId || id8(sub.customer) !== seat.providerCustomerId || sub.livemode !== (seat.environment === "prod") || !Object.entries(metadata).every(([key3, value]) => sub.metadata?.[key3] === value) || !Array.isArray(sub.items?.data) || sub.items.has_more !== false) fail4();
|
|
5176
|
+
const invoice = await call("GET", `/v1/invoices/${encodeURIComponent(invoiceRef.invoiceId)}`);
|
|
5177
|
+
if (invoice.id !== invoiceRef.invoiceId || id8(invoice.subscription ?? invoice.parent?.subscription_details?.subscription) !== sub.id || id8(invoice.customer) !== seat.providerCustomerId || invoice.status !== "paid" || invoice.billing_reason !== "subscription_create" || invoice.total !== invoiceRef.totalCents || invoice.amount_paid !== invoiceRef.totalCents || invoice.currency !== seat.currency || invoice.livemode !== (seat.environment === "prod")) fail4();
|
|
5178
|
+
let chargeId = id8(invoice.charge), paymentId = id8(invoice.payment_intent);
|
|
5179
|
+
if (!chargeId && !paymentId) {
|
|
5180
|
+
const payments = await call("GET", "/v1/invoice_payments", { invoice: invoice.id, status: "paid", limit: 100 });
|
|
5181
|
+
if (!Array.isArray(payments.data) || payments.data.length !== 1 || payments.has_more !== false) fail4();
|
|
5182
|
+
const payment = payments.data[0];
|
|
5183
|
+
if (id8(payment.invoice) !== invoice.id || payment.status !== "paid" || payment.payment?.type !== "payment_intent") fail4();
|
|
5184
|
+
paymentId = id8(payment.payment.payment_intent);
|
|
5185
|
+
}
|
|
5186
|
+
if (!chargeId) {
|
|
5187
|
+
if (!/^pi_[A-Za-z0-9_]+$/.test(paymentId)) fail4();
|
|
5188
|
+
const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(paymentId)}`);
|
|
5189
|
+
if (payment.id !== paymentId || id8(payment.customer) !== seat.providerCustomerId || payment.status !== "succeeded" || payment.amount_received !== invoiceRef.totalCents || payment.currency !== seat.currency || payment.livemode !== (seat.environment === "prod")) fail4();
|
|
5190
|
+
chargeId = id8(payment.latest_charge);
|
|
5191
|
+
}
|
|
5192
|
+
if (!/^ch_[A-Za-z0-9_]+$/.test(chargeId)) fail4();
|
|
5193
|
+
const charge = await call("GET", `/v1/charges/${encodeURIComponent(chargeId)}`);
|
|
5194
|
+
if (charge.id !== chargeId || id8(charge.customer) !== seat.providerCustomerId || charge.status !== "succeeded" || charge.livemode !== (seat.environment === "prod") || charge.amount !== invoiceRef.totalCents || charge.currency !== seat.currency || charge.invoice != null && id8(charge.invoice) !== invoice.id || paymentId && id8(charge.payment_intent) !== paymentId || !Number.isSafeInteger(charge.amount_refunded) || charge.amount_refunded < 0 || charge.amount_refunded > charge.amount) fail4();
|
|
5195
|
+
const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
|
|
5196
|
+
if (items.length > 1) fail4();
|
|
5197
|
+
if (items.length && sub.status !== "canceled") {
|
|
5198
|
+
const item = items[0];
|
|
5199
|
+
if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.quantity !== 1 || item.price?.id !== seat.priceId) fail4();
|
|
5200
|
+
const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
|
|
5201
|
+
if (removed.id !== item.id || removed.deleted !== true) fail4("named_seat_renewal_removal_unconfirmed");
|
|
5202
|
+
}
|
|
5203
|
+
if (charge.amount_refunded === charge.amount && charge.refunded === true) return { refundedCents: seat.amountCents, refundIds: [] };
|
|
5204
|
+
const list = await call("GET", "/v1/refunds", { charge: chargeId, limit: 100 });
|
|
5205
|
+
if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail4("named_seat_refund_history_incomplete");
|
|
5206
|
+
let refundedCents = 0;
|
|
5207
|
+
const refundIds = [];
|
|
5208
|
+
for (const refund of list.data) {
|
|
5209
|
+
if (id8(refund.charge) !== chargeId || refund.currency !== seat.currency || !Number.isSafeInteger(refund.amount) || refund.amount <= 0 || !/^re_[A-Za-z0-9_]+$/.test(refund.id) || !["succeeded", "pending", "requires_action", "failed", "canceled"].includes(refund.status)) fail4();
|
|
5210
|
+
if (["pending", "requires_action"].includes(refund.status)) fail4("named_seat_refund_pending");
|
|
5211
|
+
if (refund.status === "succeeded") {
|
|
5212
|
+
if (refund.metadata?.seatId !== seat.id || refund.metadata?.membershipId !== seat.primaryMembershipId) fail4("named_seat_refund_allocation_ambiguous");
|
|
5213
|
+
refundedCents += refund.amount;
|
|
5214
|
+
refundIds.push(refund.id);
|
|
5215
|
+
}
|
|
5216
|
+
}
|
|
5217
|
+
if (refundedCents !== charge.amount_refunded || refundedCents > seat.amountCents) fail4();
|
|
5218
|
+
if (refundedCents < seat.amountCents) {
|
|
5219
|
+
const remaining = seat.amountCents - refundedCents;
|
|
5220
|
+
const refund = await call("POST", "/v1/refunds", {
|
|
5221
|
+
charge: chargeId,
|
|
5222
|
+
amount: remaining,
|
|
5223
|
+
reason: "requested_by_customer",
|
|
5224
|
+
metadata: { ...metadata, membershipId: seat.primaryMembershipId, seatId: seat.id }
|
|
5225
|
+
}, `combined-seat-refund:${seat.id}:${remaining}`);
|
|
5226
|
+
if (id8(refund.charge) !== chargeId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail4();
|
|
5227
|
+
if (refund.status !== "succeeded") fail4("named_seat_refund_pending");
|
|
5228
|
+
refundedCents += remaining;
|
|
5229
|
+
refundIds.push(refund.id);
|
|
5230
|
+
}
|
|
5231
|
+
return { refundedCents, refundIds };
|
|
5232
|
+
}
|
|
5233
|
+
|
|
5234
|
+
// src/membership-seat-refund-stripe.ts
|
|
5235
|
+
var fail5 = (code = "named_seat_refund_mismatch") => {
|
|
5236
|
+
throw Object.assign(new Error(code), { code });
|
|
5237
|
+
};
|
|
4993
5238
|
async function refundNamedSeatStripePayment(secretKey, seat) {
|
|
4994
|
-
if (
|
|
5239
|
+
if (seat.combinedInvoice) return refundCheckoutNamedSeat(secretKey, seat);
|
|
5240
|
+
if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail5();
|
|
4995
5241
|
const call = async (method, path, params, idempotencyKey) => {
|
|
4996
|
-
const result = await
|
|
4997
|
-
if (!result.ok || !result.body) return
|
|
5242
|
+
const result = await stripeRequest9({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
|
|
5243
|
+
if (!result.ok || !result.body) return fail5("named_seat_refund_provider_unavailable");
|
|
4998
5244
|
return result.body;
|
|
4999
5245
|
};
|
|
5000
5246
|
const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
|
|
5001
5247
|
const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`);
|
|
5002
|
-
if (payment.id !== seat.providerPaymentIntentId || payment.customer !== seat.providerCustomerId || payment.amount !== seat.amountCents || payment.amount_received !== seat.amountCents || payment.currency !== seat.currency || payment.livemode !== (seat.environment === "prod") || payment.status !== "succeeded" || !Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === value))
|
|
5248
|
+
if (payment.id !== seat.providerPaymentIntentId || payment.customer !== seat.providerCustomerId || payment.amount !== seat.amountCents || payment.amount_received !== seat.amountCents || payment.currency !== seat.currency || payment.livemode !== (seat.environment === "prod") || payment.status !== "succeeded" || !Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === value)) fail5();
|
|
5003
5249
|
const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
|
|
5004
|
-
if (sub.id !== seat.providerSubscriptionId || sub.customer !== seat.providerCustomerId || sub.livemode !== (seat.environment === "prod") || !Object.entries({ ...stripePartitionMetadata(seat), applicationId: seat.applicationId }).every(([key3, value]) => sub.metadata?.[key3] === value) || sub.items?.has_more !== false || !Array.isArray(sub.items?.data))
|
|
5250
|
+
if (sub.id !== seat.providerSubscriptionId || sub.customer !== seat.providerCustomerId || sub.livemode !== (seat.environment === "prod") || !Object.entries({ ...stripePartitionMetadata(seat), applicationId: seat.applicationId }).every(([key3, value]) => sub.metadata?.[key3] === value) || sub.items?.has_more !== false || !Array.isArray(sub.items?.data)) fail5();
|
|
5005
5251
|
const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
|
|
5006
|
-
if (items.length > 1)
|
|
5252
|
+
if (items.length > 1) fail5();
|
|
5007
5253
|
if (items.length && sub.status !== "canceled") {
|
|
5008
5254
|
const item = items[0];
|
|
5009
|
-
if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1)
|
|
5255
|
+
if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail5();
|
|
5010
5256
|
const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
|
|
5011
|
-
if (removed.id !== item.id || removed.deleted !== true)
|
|
5257
|
+
if (removed.id !== item.id || removed.deleted !== true) fail5("named_seat_renewal_removal_unconfirmed");
|
|
5012
5258
|
}
|
|
5013
5259
|
const list = await call("GET", "/v1/refunds", { payment_intent: seat.providerPaymentIntentId, limit: 100 });
|
|
5014
|
-
if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100)
|
|
5260
|
+
if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail5("named_seat_refund_history_incomplete");
|
|
5015
5261
|
let refundedCents = 0, pendingCents = 0;
|
|
5016
5262
|
const refundIds = [];
|
|
5017
5263
|
for (const refund of list.data) {
|
|
5018
|
-
if (!/^re_[A-Za-z0-9_]+$/.test(refund.id) || refund.payment_intent !== seat.providerPaymentIntentId || refund.currency !== seat.currency || !Number.isSafeInteger(refund.amount) || refund.amount <= 0 || !["succeeded", "pending", "requires_action", "failed", "canceled"].includes(refund.status))
|
|
5264
|
+
if (!/^re_[A-Za-z0-9_]+$/.test(refund.id) || refund.payment_intent !== seat.providerPaymentIntentId || refund.currency !== seat.currency || !Number.isSafeInteger(refund.amount) || refund.amount <= 0 || !["succeeded", "pending", "requires_action", "failed", "canceled"].includes(refund.status)) fail5();
|
|
5019
5265
|
if (refund.status === "succeeded") {
|
|
5020
5266
|
refundedCents += refund.amount;
|
|
5021
5267
|
refundIds.push(refund.id);
|
|
5022
5268
|
} else if (refund.status === "pending" || refund.status === "requires_action") pendingCents += refund.amount;
|
|
5023
5269
|
}
|
|
5024
|
-
if (refundedCents + pendingCents > seat.amountCents)
|
|
5025
|
-
if (pendingCents)
|
|
5270
|
+
if (refundedCents + pendingCents > seat.amountCents) fail5();
|
|
5271
|
+
if (pendingCents) fail5("named_seat_refund_pending");
|
|
5026
5272
|
if (refundedCents < seat.amountCents) {
|
|
5027
5273
|
const remaining = seat.amountCents - refundedCents;
|
|
5028
5274
|
const refund = await call("POST", "/v1/refunds", {
|
|
@@ -5031,8 +5277,8 @@ async function refundNamedSeatStripePayment(secretKey, seat) {
|
|
|
5031
5277
|
reason: "requested_by_customer",
|
|
5032
5278
|
metadata
|
|
5033
5279
|
}, `named-seat-refund:${seat.id}:${remaining}`);
|
|
5034
|
-
if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id))
|
|
5035
|
-
if (refund.status !== "succeeded")
|
|
5280
|
+
if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail5();
|
|
5281
|
+
if (refund.status !== "succeeded") fail5("named_seat_refund_pending");
|
|
5036
5282
|
refundedCents += remaining;
|
|
5037
5283
|
refundIds.push(refund.id);
|
|
5038
5284
|
}
|
|
@@ -5048,7 +5294,7 @@ async function requestNamedSeatRefund(db, input, now) {
|
|
|
5048
5294
|
const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
|
|
5049
5295
|
requireMembershipScope(member, input);
|
|
5050
5296
|
if (seat.status === "refunded" || seat.status === "refund_pending") return;
|
|
5051
|
-
if (!seat.purchasedAt || !seat.providerPaymentIntentId && !namedSeatIncluded(seat) || input.reason === "declined" && seat.journeyApprovedAt !== void 0 || input.reason === "primary_closed" && !["refunded", "declined"].includes(String(member.status))) membershipFail("named_seat_refund_not_eligible");
|
|
5297
|
+
if (!seat.purchasedAt || !seat.providerPaymentIntentId && !seat.combinedInvoice && !namedSeatIncluded(seat) || input.reason === "declined" && seat.journeyApprovedAt !== void 0 || input.reason === "primary_closed" && !["refunded", "declined"].includes(String(member.status))) membershipFail("named_seat_refund_not_eligible");
|
|
5052
5298
|
const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
|
|
5053
5299
|
await db.transact([
|
|
5054
5300
|
membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
|
|
@@ -5153,14 +5399,14 @@ var at = (value) => Number.isSafeInteger(value) ? Number(value) : void 0;
|
|
|
5153
5399
|
async function readNamedSeatOffer(db, input, now) {
|
|
5154
5400
|
membershipClock(now);
|
|
5155
5401
|
await loadMembershipAuthorityState(db, input);
|
|
5156
|
-
const
|
|
5402
|
+
const id11 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id11);
|
|
5157
5403
|
requireMembershipScope(member, input);
|
|
5158
5404
|
const offering = membershipOfferingVersion(member.offeringSnapshot);
|
|
5159
5405
|
const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
|
|
5160
5406
|
if (seat) requireMembershipScope(seat, input);
|
|
5161
5407
|
const purchaseEndsAt = seatPurchaseDeadline(offering, member);
|
|
5162
5408
|
const offer = {
|
|
5163
|
-
membershipId:
|
|
5409
|
+
membershipId: id11,
|
|
5164
5410
|
amountCents: offering.seatPriceCents,
|
|
5165
5411
|
currency: offering.currency,
|
|
5166
5412
|
interval: offering.interval,
|
|
@@ -5438,9 +5684,14 @@ function createMembershipAuthorityHandler(options) {
|
|
|
5438
5684
|
try {
|
|
5439
5685
|
input = JSON.parse(verified.body);
|
|
5440
5686
|
const fields = action === "seat-invitation" ? MEMBERSHIP_SEAT_ACTION_FIELDS[action] : action === "offer-preview" ? ["skuId"] : ["applicationId", "skuId", ...action === "offers" ? ["requestId"] : action === "checkouts" ? ["offerId", "offerDigest", "quoteDigest", "priceId"] : action === "payments" ? ["eventId", "providerSubscriptionId", "providerCustomerId"] : action === "renewals" ? ["eventId"] : action === "restarts" ? ["previousSubscriptionId"] : MEMBERSHIP_SEAT_ACTION_FIELDS[action] ?? []];
|
|
5441
|
-
if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key3) => !fields.includes(key3)) || !fields.every((key3) => typeof input[key3] === "string" && (["recipientName", "recipientEmail", "purchaserEmail", "verifiedRecipientEmail"].includes(key3) ? input[key3].length > 0 && input[key3].length <= 254 && !/[\u0000-\u001f\u007f]/.test(input[key3]) : SAFE.test(input[key3])))) {
|
|
5687
|
+
if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key3) => !fields.includes(key3) && !(action === "checkouts" && key3 === "namedSeat")) || !fields.every((key3) => typeof input[key3] === "string" && (["recipientName", "recipientEmail", "purchaserEmail", "verifiedRecipientEmail"].includes(key3) ? input[key3].length > 0 && input[key3].length <= 254 && !/[\u0000-\u001f\u007f]/.test(input[key3]) : SAFE.test(input[key3])))) {
|
|
5442
5688
|
throw new TypeError("invalid request");
|
|
5443
5689
|
}
|
|
5690
|
+
if (action === "checkouts" && input.namedSeat !== void 0) {
|
|
5691
|
+
const seat = input.namedSeat;
|
|
5692
|
+
if (!seat || Object.keys(seat).some((key3) => !["recipientName", "recipientEmail", "purchaserEmail"].includes(key3)) || typeof seat.purchaserEmail !== "string" || seat.purchaserEmail.length > 254) throw new TypeError("invalid seat");
|
|
5693
|
+
checkoutNamedSeatSelection(seat, seat.purchaserEmail);
|
|
5694
|
+
}
|
|
5444
5695
|
} catch {
|
|
5445
5696
|
return respond({ error: "invalid_offer_request" }, 400);
|
|
5446
5697
|
}
|
|
@@ -5513,8 +5764,9 @@ function createMembershipAuthorityHandler(options) {
|
|
|
5513
5764
|
offerId: String(input.offerId),
|
|
5514
5765
|
offerDigest: String(input.offerDigest),
|
|
5515
5766
|
quoteDigest: String(input.quoteDigest),
|
|
5516
|
-
priceId: String(input.priceId)
|
|
5517
|
-
|
|
5767
|
+
priceId: String(input.priceId),
|
|
5768
|
+
...input.namedSeat ? { namedSeat: input.namedSeat } : {}
|
|
5769
|
+
}, options.now?.() ?? Date.now(), peer.seatPriceId);
|
|
5518
5770
|
return respond({ ok: true, binding });
|
|
5519
5771
|
}
|
|
5520
5772
|
const offerId = await membershipOfferIdForRequest({ ...peer, applicationId: String(input.applicationId), requestId: String(input.requestId) });
|
|
@@ -5607,7 +5859,7 @@ async function requestMembershipCommercialOffer(options) {
|
|
|
5607
5859
|
async function requestMembershipOfferPreview(options) {
|
|
5608
5860
|
const result = await requestAuthority(options, "offer-preview", { skuId: options.skuId });
|
|
5609
5861
|
const preview = result.preview;
|
|
5610
|
-
if (!preview || preview.skuId !== options.skuId || typeof preview.founding !== "boolean" || !Number.isSafeInteger(preview.basePriceCents) || !Number.isSafeInteger(preview.dueTodayCents) || preview.dueTodayCents < 0 || preview.dueTodayCents > preview.basePriceCents || !Number.isSafeInteger(preview.renewalCents) || !/^[a-z]{3}$/.test(preview.currency) || !["month", "year"].includes(preview.interval) || preview.cohortLabel !== void 0 && typeof preview.cohortLabel !== "string" || preview.discountDuration !== void 0 && !["first_term", "while_active"].includes(preview.discountDuration)) throw new Error("invalid offer preview");
|
|
5862
|
+
if (!preview || preview.skuId !== options.skuId || typeof preview.founding !== "boolean" || !Number.isSafeInteger(preview.basePriceCents) || !Number.isSafeInteger(preview.dueTodayCents) || preview.dueTodayCents < 0 || preview.dueTodayCents > preview.basePriceCents || !Number.isSafeInteger(preview.renewalCents) || !/^[a-z]{3}$/.test(preview.currency) || !["month", "year"].includes(preview.interval) || preview.cohortLabel !== void 0 && typeof preview.cohortLabel !== "string" || preview.seatPriceCents !== void 0 && (!Number.isSafeInteger(preview.seatPriceCents) || preview.seatPriceCents < 0) || preview.discountDuration !== void 0 && !["first_term", "while_active"].includes(preview.discountDuration)) throw new Error("invalid offer preview");
|
|
5611
5863
|
return preview;
|
|
5612
5864
|
}
|
|
5613
5865
|
async function requestMembershipCheckoutBinding(options) {
|
|
@@ -5617,7 +5869,8 @@ async function requestMembershipCheckoutBinding(options) {
|
|
|
5617
5869
|
offerId: options.offerId,
|
|
5618
5870
|
offerDigest: options.offerDigest,
|
|
5619
5871
|
quoteDigest: options.quoteDigest,
|
|
5620
|
-
priceId: options.priceId
|
|
5872
|
+
priceId: options.priceId,
|
|
5873
|
+
...options.namedSeat ? { namedSeat: options.namedSeat } : {}
|
|
5621
5874
|
});
|
|
5622
5875
|
return verifyMembershipCheckoutBinding(result.binding, options);
|
|
5623
5876
|
}
|
|
@@ -5827,7 +6080,7 @@ async function requestMembershipCancellation(options) {
|
|
|
5827
6080
|
}
|
|
5828
6081
|
|
|
5829
6082
|
// src/membership-seat-cancel.ts
|
|
5830
|
-
import { initStripe, stripeRequest as
|
|
6083
|
+
import { initStripe, stripeRequest as stripeRequest10 } from "@odla-ai/stripe";
|
|
5831
6084
|
async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
|
|
5832
6085
|
membershipClock(now);
|
|
5833
6086
|
const state = await loadMembershipAuthorityState(db, target);
|
|
@@ -5855,7 +6108,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
|
|
|
5855
6108
|
}
|
|
5856
6109
|
const metadata = { ...stripePartitionMetadata(target), applicationId: target.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
|
|
5857
6110
|
const request = async (method, path, params) => {
|
|
5858
|
-
const result = await
|
|
6111
|
+
const result = await stripeRequest10(
|
|
5859
6112
|
{ secretKey },
|
|
5860
6113
|
method,
|
|
5861
6114
|
path,
|
|
@@ -5881,9 +6134,9 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
|
|
|
5881
6134
|
if (payment.status === "succeeded" || payment.amount_received > 0) throw new Error("paid seat requires payment evidence and full refund");
|
|
5882
6135
|
if (payment.status !== "canceled") {
|
|
5883
6136
|
if (!["requires_payment_method", "requires_confirmation", "requires_action", "processing"].includes(payment.status)) throw new Error("seat payment outcome unresolved");
|
|
5884
|
-
const
|
|
5885
|
-
payment = await request("POST", `/v1/payment_intents/${encodeURIComponent(
|
|
5886
|
-
if (payment.id !==
|
|
6137
|
+
const id11 = payment.id;
|
|
6138
|
+
payment = await request("POST", `/v1/payment_intents/${encodeURIComponent(id11)}/cancel`, { cancellation_reason: "abandoned" });
|
|
6139
|
+
if (payment.id !== id11) throw new Error("seat cancellation receipt mismatch");
|
|
5887
6140
|
}
|
|
5888
6141
|
if (!valid(payment) || payment.status !== "canceled" || payment.amount_received !== 0) throw new Error("seat cancellation unconfirmed");
|
|
5889
6142
|
await db.transact([membershipUpdate("networkSeat", target.seatId, { status: "canceled", revision: Number(seat.revision) + 1, updatedAt: now })], {
|
|
@@ -5983,11 +6236,11 @@ async function drainMembershipEffects(db, options, now) {
|
|
|
5983
6236
|
|
|
5984
6237
|
// src/schema-membership-offers.ts
|
|
5985
6238
|
var attr8 = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
|
|
5986
|
-
var
|
|
6239
|
+
var id9 = () => attr8("string", false, true, true);
|
|
5987
6240
|
var key = () => attr8("string", false, false, true);
|
|
5988
6241
|
var membershipOfferEntities = {
|
|
5989
6242
|
membershipAuthorityState: { attrs: {
|
|
5990
|
-
id:
|
|
6243
|
+
id: id9(),
|
|
5991
6244
|
chapterId: key(),
|
|
5992
6245
|
environment: key(),
|
|
5993
6246
|
mode: key(),
|
|
@@ -5997,7 +6250,7 @@ var membershipOfferEntities = {
|
|
|
5997
6250
|
updatedAt: attr8("number")
|
|
5998
6251
|
} },
|
|
5999
6252
|
membershipCheckoutOffer: { attrs: {
|
|
6000
|
-
id:
|
|
6253
|
+
id: id9(),
|
|
6001
6254
|
applicationId: key(),
|
|
6002
6255
|
appId: key(),
|
|
6003
6256
|
chapterId: key(),
|
|
@@ -6012,7 +6265,7 @@ var membershipOfferEntities = {
|
|
|
6012
6265
|
authorityRevision: attr8("number")
|
|
6013
6266
|
} },
|
|
6014
6267
|
membershipOfferCheckout: { attrs: {
|
|
6015
|
-
id:
|
|
6268
|
+
id: id9(),
|
|
6016
6269
|
appId: key(),
|
|
6017
6270
|
chapterId: key(),
|
|
6018
6271
|
environment: key(),
|
|
@@ -6023,20 +6276,21 @@ var membershipOfferEntities = {
|
|
|
6023
6276
|
offerDigest: attr8("string"),
|
|
6024
6277
|
quoteDigest: attr8("string"),
|
|
6025
6278
|
priceId: attr8("string"),
|
|
6026
|
-
boundAt: attr8("number")
|
|
6279
|
+
boundAt: attr8("number"),
|
|
6280
|
+
namedSeat: attr8("json", true)
|
|
6027
6281
|
} }
|
|
6028
6282
|
};
|
|
6029
6283
|
var membershipOfferRules = Object.fromEntries(Object.keys(membershipOfferEntities).map((ns) => [ns, { view: "false", create: "false", update: "false", delete: "false" }]));
|
|
6030
6284
|
|
|
6031
6285
|
// src/schema-membership-authority.ts
|
|
6032
6286
|
var attr9 = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
|
|
6033
|
-
var
|
|
6287
|
+
var id10 = () => attr9("string", false, true, true);
|
|
6034
6288
|
var key2 = (optional = false) => attr9("string", optional, false, true);
|
|
6035
6289
|
var scope = () => ({ appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), applicationId: key2(), skuId: key2() });
|
|
6036
6290
|
var times = () => ({ revision: attr9("number"), createdAt: attr9("number"), updatedAt: attr9("number") });
|
|
6037
6291
|
var membershipAuthorityEntities = {
|
|
6038
6292
|
networkMembership: { attrs: {
|
|
6039
|
-
id:
|
|
6293
|
+
id: id10(),
|
|
6040
6294
|
membershipId: key2(),
|
|
6041
6295
|
...scope(),
|
|
6042
6296
|
personId: key2(true),
|
|
@@ -6076,7 +6330,7 @@ var membershipAuthorityEntities = {
|
|
|
6076
6330
|
restartId: key2(true)
|
|
6077
6331
|
} },
|
|
6078
6332
|
membershipRestart: { attrs: {
|
|
6079
|
-
id:
|
|
6333
|
+
id: id10(),
|
|
6080
6334
|
...scope(),
|
|
6081
6335
|
membershipId: key2(),
|
|
6082
6336
|
restartId: key2(),
|
|
@@ -6086,7 +6340,7 @@ var membershipAuthorityEntities = {
|
|
|
6086
6340
|
createdAt: attr9("number")
|
|
6087
6341
|
} },
|
|
6088
6342
|
membershipPurchaseHistory: { attrs: {
|
|
6089
|
-
id:
|
|
6343
|
+
id: id10(),
|
|
6090
6344
|
...scope(),
|
|
6091
6345
|
membershipId: key2(),
|
|
6092
6346
|
providerSubscriptionId: key2(),
|
|
@@ -6095,7 +6349,7 @@ var membershipAuthorityEntities = {
|
|
|
6095
6349
|
archivedAt: attr9("number")
|
|
6096
6350
|
} },
|
|
6097
6351
|
networkEntitlement: { attrs: {
|
|
6098
|
-
id:
|
|
6352
|
+
id: id10(),
|
|
6099
6353
|
...scope(),
|
|
6100
6354
|
membershipId: key2(),
|
|
6101
6355
|
seatId: key2(true),
|
|
@@ -6112,10 +6366,12 @@ var membershipAuthorityEntities = {
|
|
|
6112
6366
|
journeyApprovedAt: attr9("number", true)
|
|
6113
6367
|
} },
|
|
6114
6368
|
networkSeat: { attrs: {
|
|
6115
|
-
id:
|
|
6369
|
+
id: id10(),
|
|
6116
6370
|
...scope(),
|
|
6117
6371
|
primaryMembershipId: key2(),
|
|
6118
|
-
purchaserUserId: key2(),
|
|
6372
|
+
purchaserUserId: key2(true),
|
|
6373
|
+
purchaserEmailNormalized: key2(true),
|
|
6374
|
+
combinedInvoice: attr9("json", true),
|
|
6119
6375
|
recipientName: attr9("string"),
|
|
6120
6376
|
recipientEmailNormalized: key2(),
|
|
6121
6377
|
recipientClaimId: key2(),
|
|
@@ -6147,9 +6403,9 @@ var membershipAuthorityEntities = {
|
|
|
6147
6403
|
refundedCents: attr9("number", true),
|
|
6148
6404
|
providerRefundIds: attr9("json", true)
|
|
6149
6405
|
} },
|
|
6150
|
-
networkSeatRecipient: { attrs: { id:
|
|
6406
|
+
networkSeatRecipient: { attrs: { id: id10(), appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), seatId: key2(), recipientEmailNormalized: key2(), createdAt: attr9("number") } },
|
|
6151
6407
|
membershipEffect: { attrs: {
|
|
6152
|
-
id:
|
|
6408
|
+
id: id10(),
|
|
6153
6409
|
chapterId: key2(),
|
|
6154
6410
|
environment: key2(),
|
|
6155
6411
|
runtime: key2(),
|
|
@@ -6274,14 +6530,14 @@ function validateMemberContentDocument(input, options = {}) {
|
|
|
6274
6530
|
continue;
|
|
6275
6531
|
}
|
|
6276
6532
|
unknownKeys(raw2, PAGE_KEYS, path, errors);
|
|
6277
|
-
const
|
|
6533
|
+
const id11 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
|
|
6278
6534
|
const slug = textValue(raw2.slug, `${path}.slug`, errors, 120, true) ?? "";
|
|
6279
6535
|
const title = textValue(raw2.title, `${path}.title`, errors, MAX_TEXT, true) ?? "";
|
|
6280
|
-
if (
|
|
6536
|
+
if (id11 && !ID.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
6281
6537
|
if (slug && !SLUG8.test(slug)) errors.push(`${path}.slug must be a lowercase URL slug`);
|
|
6282
|
-
if (ids2.has(
|
|
6538
|
+
if (ids2.has(id11) || knownParentIds.has(id11)) errors.push(`${path}.id duplicates page "${id11}"`);
|
|
6283
6539
|
if (slugs.has(slug) || (options.parentPages ?? []).some((page) => page.slug === slug)) errors.push(`${path}.slug duplicates "${slug}"`);
|
|
6284
|
-
ids2.add(
|
|
6540
|
+
ids2.add(id11);
|
|
6285
6541
|
slugs.add(slug);
|
|
6286
6542
|
const navRaw = record(raw2.navigation);
|
|
6287
6543
|
let navigation = { section: "General", order: index2 };
|
|
@@ -6337,7 +6593,7 @@ function validateMemberContentDocument(input, options = {}) {
|
|
|
6337
6593
|
else if (owner === "chapter") upsell = { owner, ...offer ? { offer } : {} };
|
|
6338
6594
|
}
|
|
6339
6595
|
}
|
|
6340
|
-
pages.push({ id:
|
|
6596
|
+
pages.push({ id: id11, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
|
|
6341
6597
|
}
|
|
6342
6598
|
const allIds = /* @__PURE__ */ new Set([...knownParentIds, ...ids2]);
|
|
6343
6599
|
for (const [index2, page] of pages.entries()) {
|
|
@@ -6543,8 +6799,8 @@ function resourceValue(value, path, errors) {
|
|
|
6543
6799
|
return null;
|
|
6544
6800
|
}
|
|
6545
6801
|
unknownKeys2(raw2, RESOURCE_KEYS, path, errors);
|
|
6546
|
-
const
|
|
6547
|
-
if (
|
|
6802
|
+
const id11 = text3(raw2.id, `${path}.id`, errors, 80) ?? "";
|
|
6803
|
+
if (id11 && !ID2.test(id11)) errors.push(`${path}.id must be lowercase kebab-case`);
|
|
6548
6804
|
const kind = text3(raw2.kind, `${path}.kind`, errors) ?? "";
|
|
6549
6805
|
if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
|
|
6550
6806
|
const title = text3(raw2.title, `${path}.title`, errors, 240) ?? "";
|
|
@@ -6601,7 +6857,7 @@ function resourceValue(value, path, errors) {
|
|
|
6601
6857
|
updatedAt: integer3(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
|
|
6602
6858
|
...promotedFrom ? { promotedFrom } : {}
|
|
6603
6859
|
};
|
|
6604
|
-
return { id:
|
|
6860
|
+
return { id: id11, kind, title, ...summary ? { summary } : {}, tags, scope: scope2, status: status2, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance };
|
|
6605
6861
|
}
|
|
6606
6862
|
function validateBuilderLibraryDocument(value, options = {}) {
|
|
6607
6863
|
const errors = [];
|
|
@@ -6632,7 +6888,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
|
|
|
6632
6888
|
const inputs = Array.isArray(raw2.resources) ? raw2.resources : [];
|
|
6633
6889
|
if (inputs.length > MAX_RESOURCES) errors.push(`document.resources may contain at most ${MAX_RESOURCES} items`);
|
|
6634
6890
|
const resources = inputs.map((item, index2) => resourceValue(item, `document.resources[${index2}]`, errors)).filter(Boolean);
|
|
6635
|
-
const ids2 = resources.map(({ id:
|
|
6891
|
+
const ids2 = resources.map(({ id: id11 }) => id11);
|
|
6636
6892
|
if (new Set(ids2).size !== ids2.length) errors.push("document.resources ids must be unique");
|
|
6637
6893
|
for (const resource of resources) {
|
|
6638
6894
|
if (sourceType === "chapter" && (resource.scope.kind !== "chapter" || resource.scope.chapterId !== sourceAppId)) {
|
|
@@ -6886,7 +7142,7 @@ async function frozenCheckoutVariables(db, app) {
|
|
|
6886
7142
|
const intent = (await db.query({ subscriptionCheckoutIntents: { $: { where: { id: String(app.id) }, limit: 1 } } })).subscriptionCheckoutIntents?.[0];
|
|
6887
7143
|
const offer = intent?.membershipOffer, contract = offer?.contract;
|
|
6888
7144
|
const offering = contract?.offering, quote = intent?.paymentQuote;
|
|
6889
|
-
const amount = contract?.dueTodayCents ?? intent?.priceCents;
|
|
7145
|
+
const amount = quote?.lineItems?.dueTodayCents ?? contract?.dueTodayCents ?? intent?.priceCents;
|
|
6890
7146
|
const currency = offering?.currency ?? intent?.currency;
|
|
6891
7147
|
let formatted = "";
|
|
6892
7148
|
if (Number.isSafeInteger(amount) && Number(amount) > 0 && typeof currency === "string" && /^[a-z]{3}$/.test(currency)) {
|
|
@@ -6962,11 +7218,11 @@ async function sendTemplated(deps, input) {
|
|
|
6962
7218
|
const previous = prior[0];
|
|
6963
7219
|
if (previous && (previous.groupId !== input.group.id || previous.template !== input.template || previous.to !== decision.to || previous.transport !== decision.transport || previous.applicationId !== input.applicationId)) return { sent: false, reason: "delivery-identity-conflict" };
|
|
6964
7220
|
const hash2 = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input.dedupeKey));
|
|
6965
|
-
const
|
|
7221
|
+
const id11 = previous ? String(previous.id) : `email_${Array.from(new Uint8Array(hash2), (b) => b.toString(16).padStart(2, "0")).join("")}`;
|
|
6966
7222
|
const attempt = Number(previous?.deliveryAttempt ?? 0) + 1;
|
|
6967
7223
|
if (!Number.isSafeInteger(attempt) || attempt < 1) return { sent: false, reason: "delivery-state-invalid" };
|
|
6968
7224
|
const row2 = {
|
|
6969
|
-
id:
|
|
7225
|
+
id: id11,
|
|
6970
7226
|
groupId: input.group.id,
|
|
6971
7227
|
to: decision.to,
|
|
6972
7228
|
template: input.template,
|
|
@@ -6984,15 +7240,15 @@ async function sendTemplated(deps, input) {
|
|
|
6984
7240
|
if (isRetiredEmailCopy(String(row2.subject), String(row2.body))) return { sent: false, reason: "template-retired" };
|
|
6985
7241
|
if (validateEmailCopy3({ subject: row2.subject, text: row2.body }, { allowedVariables: [] }).length) return { sent: false, reason: "template-invalid" };
|
|
6986
7242
|
try {
|
|
6987
|
-
const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id:
|
|
6988
|
-
mutationId: `email-claim:${
|
|
7243
|
+
const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id: id11, attrs: row2 }], {
|
|
7244
|
+
mutationId: `email-claim:${id11}:${attempt}`,
|
|
6989
7245
|
guards: [previous ? {
|
|
6990
7246
|
ns: "emailLog",
|
|
6991
|
-
id:
|
|
7247
|
+
id: id11,
|
|
6992
7248
|
exists: true,
|
|
6993
7249
|
equals: { deliveryState: "retry_ready", ...previous.deliveryAttempt !== void 0 ? { deliveryAttempt: previous.deliveryAttempt } : {} },
|
|
6994
7250
|
...previous.deliveryAttempt === void 0 ? { absent: ["deliveryAttempt"] } : {}
|
|
6995
|
-
} : { ns: "emailLog", id:
|
|
7251
|
+
} : { ns: "emailLog", id: id11, exists: false }]
|
|
6996
7252
|
});
|
|
6997
7253
|
if (claim.duplicate) return { sent: false, reason: "delivery-uncertain" };
|
|
6998
7254
|
} catch (error) {
|
|
@@ -7012,16 +7268,16 @@ async function sendTemplated(deps, input) {
|
|
|
7012
7268
|
if (!response || typeof response.messageId !== "string" || !response.messageId) throw new Error("missing delivery receipt");
|
|
7013
7269
|
messageId = response.messageId;
|
|
7014
7270
|
} catch {
|
|
7015
|
-
await deps.db.transact([{ t: "update", ns: "emailLog", id:
|
|
7016
|
-
guards: [{ ns: "emailLog", id:
|
|
7271
|
+
await deps.db.transact([{ t: "update", ns: "emailLog", id: id11, attrs: { deliveryState: "uncertain", error: "delivery-uncertain" } }], {
|
|
7272
|
+
guards: [{ ns: "emailLog", id: id11, exists: true, equals: { deliveryState: "sending", deliveryAttempt: attempt } }]
|
|
7017
7273
|
});
|
|
7018
7274
|
return { sent: false, reason: "delivery-uncertain" };
|
|
7019
7275
|
}
|
|
7020
|
-
await deps.db.transact([{ t: "update", ns: "emailLog", id:
|
|
7021
|
-
mutationId: `email-confirmed:${
|
|
7276
|
+
await deps.db.transact([{ t: "update", ns: "emailLog", id: id11, attrs: { deliveryState: "sent", error: "", messageId, sentAt: deps.now() } }], {
|
|
7277
|
+
mutationId: `email-confirmed:${id11}:${attempt}`,
|
|
7022
7278
|
guards: [{
|
|
7023
7279
|
ns: "emailLog",
|
|
7024
|
-
id:
|
|
7280
|
+
id: id11,
|
|
7025
7281
|
exists: true,
|
|
7026
7282
|
equals: { deliveryState: "sending", deliveryAttempt: attempt }
|
|
7027
7283
|
}]
|
|
@@ -7279,14 +7535,14 @@ function validateAdmissionGrantDocument(input) {
|
|
|
7279
7535
|
const seenEmails = /* @__PURE__ */ new Set();
|
|
7280
7536
|
for (const [index2, raw2] of rawGrants.slice(0, MAX_GRANTS).entries()) {
|
|
7281
7537
|
const grant = raw2 && typeof raw2 === "object" ? raw2 : {};
|
|
7282
|
-
const
|
|
7538
|
+
const id11 = typeof grant.id === "string" ? grant.id : "";
|
|
7283
7539
|
const email = typeof grant.email === "string" ? grant.email.trim() : "";
|
|
7284
7540
|
const normalized = normalizeAdmissionEmail(email);
|
|
7285
7541
|
const decision = grant.decision;
|
|
7286
7542
|
const note = typeof grant.note === "string" ? grant.note.trim() : void 0;
|
|
7287
7543
|
const expiresAt = grant.expiresAt === void 0 ? void 0 : Number(grant.expiresAt);
|
|
7288
|
-
if (!ID3.test(
|
|
7289
|
-
if (seenIds.has(
|
|
7544
|
+
if (!ID3.test(id11)) errors.push(`document.grants[${index2}].id must be an identifier`);
|
|
7545
|
+
if (seenIds.has(id11)) errors.push(`document.grants repeats id "${id11}"`);
|
|
7290
7546
|
if (!isAdmissionEmail(normalized)) errors.push(`document.grants[${index2}].email must be valid`);
|
|
7291
7547
|
if (seenEmails.has(normalized)) errors.push(`document.grants repeats email "${normalized}"`);
|
|
7292
7548
|
if (decision !== "waive_interview" && decision !== "require_interview") {
|
|
@@ -7296,10 +7552,10 @@ function validateAdmissionGrantDocument(input) {
|
|
|
7296
7552
|
if (expiresAt !== void 0 && (!Number.isFinite(expiresAt) || expiresAt <= 0)) {
|
|
7297
7553
|
errors.push(`document.grants[${index2}].expiresAt must be epoch milliseconds`);
|
|
7298
7554
|
}
|
|
7299
|
-
seenIds.add(
|
|
7555
|
+
seenIds.add(id11);
|
|
7300
7556
|
seenEmails.add(normalized);
|
|
7301
7557
|
grants.push({
|
|
7302
|
-
id:
|
|
7558
|
+
id: id11,
|
|
7303
7559
|
email,
|
|
7304
7560
|
decision,
|
|
7305
7561
|
...note ? { note } : {},
|
|
@@ -7371,8 +7627,8 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
|
|
|
7371
7627
|
const incomingIds = new Set(document.grants.map((grant) => `admission:parent:${document.source.appId}:${grant.id}`));
|
|
7372
7628
|
const ops = [];
|
|
7373
7629
|
for (const row2 of current) {
|
|
7374
|
-
const
|
|
7375
|
-
if (
|
|
7630
|
+
const id11 = String(row2.id ?? "");
|
|
7631
|
+
if (id11 && !incomingIds.has(id11)) ops.push({ t: "update", ns: "admissionGrants", id: id11, attrs: {
|
|
7376
7632
|
active: false,
|
|
7377
7633
|
revokedAt: receivedAt,
|
|
7378
7634
|
revokedBy: document.source.actor,
|
|
@@ -7381,10 +7637,10 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
|
|
|
7381
7637
|
} });
|
|
7382
7638
|
}
|
|
7383
7639
|
for (const grant of document.grants) {
|
|
7384
|
-
const
|
|
7385
|
-
const existing = current.find((row2) => row2.id ===
|
|
7386
|
-
ops.push({ t: "update", ns: "admissionGrants", id:
|
|
7387
|
-
id:
|
|
7640
|
+
const id11 = `admission:parent:${document.source.appId}:${grant.id}`;
|
|
7641
|
+
const existing = current.find((row2) => row2.id === id11);
|
|
7642
|
+
ops.push({ t: "update", ns: "admissionGrants", id: id11, attrs: {
|
|
7643
|
+
id: id11,
|
|
7388
7644
|
sourceGrantId: grant.id,
|
|
7389
7645
|
sourceScope: "parent",
|
|
7390
7646
|
sourceId: document.source.appId,
|
|
@@ -7405,7 +7661,7 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
|
|
|
7405
7661
|
...!grant.note && existing?.note !== void 0 ? ["note"] : [],
|
|
7406
7662
|
...grant.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
|
|
7407
7663
|
];
|
|
7408
|
-
if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id:
|
|
7664
|
+
if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id11, attrs: retract });
|
|
7409
7665
|
}
|
|
7410
7666
|
if (previous?.active) ops.push({ t: "update", ns: "admissionGrantRevisions", id: previous.id, attrs: { active: false } });
|
|
7411
7667
|
const revisionId = `admission-revision:${document.source.appId}:${document.target.environment}:${document.revision}`;
|
|
@@ -7476,10 +7732,10 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
7476
7732
|
}
|
|
7477
7733
|
const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
|
|
7478
7734
|
for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
7479
|
-
const
|
|
7480
|
-
if (
|
|
7481
|
-
if (ids2.has(
|
|
7482
|
-
ids2.add(
|
|
7735
|
+
const id11 = text4(item.id, `${path}.id`, errors, 80);
|
|
7736
|
+
if (id11 && !ID4.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
7737
|
+
if (ids2.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
|
|
7738
|
+
ids2.add(id11);
|
|
7483
7739
|
const targetRaw = record3(item.target);
|
|
7484
7740
|
let target = { type: "entitlement", entitlementKey: "invalid" };
|
|
7485
7741
|
if (targetRaw?.type === "tier") {
|
|
@@ -7505,7 +7761,7 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
7505
7761
|
if (activeUntil !== void 0 && (!Number.isSafeInteger(activeUntil) || activeUntil < 1)) errors.push(`${path}.activeUntil must be epoch milliseconds`);
|
|
7506
7762
|
if (activeFrom !== void 0 && activeUntil !== void 0 && activeUntil <= activeFrom) errors.push(`${path}.activeUntil must follow activeFrom`);
|
|
7507
7763
|
offers.push({
|
|
7508
|
-
id:
|
|
7764
|
+
id: id11,
|
|
7509
7765
|
target,
|
|
7510
7766
|
billingMode: item.billingMode,
|
|
7511
7767
|
stripePriceId,
|
|
@@ -7554,10 +7810,10 @@ async function ensureProjectedPerson(deps, input, options = {}) {
|
|
|
7554
7810
|
const existing = await find();
|
|
7555
7811
|
if (existing) return { record: existing, created: false };
|
|
7556
7812
|
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`chapter:person:v1:${email}`)));
|
|
7557
|
-
const
|
|
7813
|
+
const id11 = `person-${Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
7558
7814
|
try {
|
|
7559
7815
|
const created = await createRecord({ ...deps, db: deps.db }, {
|
|
7560
|
-
id:
|
|
7816
|
+
id: id11,
|
|
7561
7817
|
type: "person",
|
|
7562
7818
|
input: { ...input, email },
|
|
7563
7819
|
...options
|
|
@@ -7980,9 +8236,9 @@ function subAnnualCents(sub) {
|
|
|
7980
8236
|
}
|
|
7981
8237
|
|
|
7982
8238
|
// src/payments-stripe.ts
|
|
7983
|
-
import { stripeRequest as
|
|
8239
|
+
import { stripeRequest as stripeRequest11, verifyStripeSignature as verifyStripeSignature2 } from "@odla-ai/stripe";
|
|
7984
8240
|
async function stripeCall(sk, method, path, params, idempotencyKey) {
|
|
7985
|
-
return
|
|
8241
|
+
return stripeRequest11(
|
|
7986
8242
|
{ secretKey: sk },
|
|
7987
8243
|
method,
|
|
7988
8244
|
path,
|
|
@@ -8060,8 +8316,8 @@ async function updateClerkUserMetadata(secretKey, userId, input, fetchImpl = fet
|
|
|
8060
8316
|
return res.ok;
|
|
8061
8317
|
}
|
|
8062
8318
|
async function updateClerkUserMetadataByEmail(secretKey, email, input, fetchImpl = fetch) {
|
|
8063
|
-
const
|
|
8064
|
-
return
|
|
8319
|
+
const id11 = await existingClerkUserId(secretKey, email, fetchImpl);
|
|
8320
|
+
return id11 ? updateClerkUserMetadata(secretKey, id11, input, fetchImpl) : false;
|
|
8065
8321
|
}
|
|
8066
8322
|
async function existingClerkUserId(secretKey, email, fetchImpl) {
|
|
8067
8323
|
const auth = { authorization: `Bearer ${secretKey}` };
|
|
@@ -8134,10 +8390,10 @@ async function clerkGetUserByEmail(secretKey, email, fetchImpl = fetch, source =
|
|
|
8134
8390
|
const user = Array.isArray(data) ? data[0] : void 0;
|
|
8135
8391
|
return user ? toRecord(user, source) : null;
|
|
8136
8392
|
}
|
|
8137
|
-
async function clerkGetUser(secretKey,
|
|
8138
|
-
const data = await clerkGet(`/v1/users/${encodeURIComponent(
|
|
8393
|
+
async function clerkGetUser(secretKey, id11, fetchImpl = fetch, source = "public") {
|
|
8394
|
+
const data = await clerkGet(`/v1/users/${encodeURIComponent(id11)}`, secretKey, fetchImpl).catch(() => null);
|
|
8139
8395
|
const record4 = data ? toRecord(data, source) : null;
|
|
8140
|
-
return record4?.id ===
|
|
8396
|
+
return record4?.id === id11 ? record4 : null;
|
|
8141
8397
|
}
|
|
8142
8398
|
async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
|
|
8143
8399
|
const out = [];
|
|
@@ -8153,8 +8409,8 @@ async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
|
|
|
8153
8409
|
}
|
|
8154
8410
|
return out;
|
|
8155
8411
|
}
|
|
8156
|
-
async function clerkSetRole(secretKey,
|
|
8157
|
-
const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(
|
|
8412
|
+
async function clerkSetRole(secretKey, id11, role, fetchImpl = fetch, source = "public") {
|
|
8413
|
+
const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id11)}/metadata`, {
|
|
8158
8414
|
method: "PATCH",
|
|
8159
8415
|
headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
|
|
8160
8416
|
redirect: "manual",
|
|
@@ -8171,7 +8427,7 @@ function planPrivateRoleMigration(input) {
|
|
|
8171
8427
|
if (input.environment !== "dev") blockers.push("dev_only");
|
|
8172
8428
|
if (!input.ladder.length || new Set(input.ladder).size !== input.ladder.length || input.ladder.some((role) => !role || role === "super_admin")) blockers.push("invalid_role_ladder");
|
|
8173
8429
|
const expected = new Set(input.expectedRuntimes);
|
|
8174
|
-
if (!expected.size || expected.size !== input.expectedRuntimes.length || input.expectedRuntimes.some((
|
|
8430
|
+
if (!expected.size || expected.size !== input.expectedRuntimes.length || input.expectedRuntimes.some((id11) => !id11)) blockers.push("runtime_inventory_required");
|
|
8175
8431
|
if (input.runtimes.length !== expected.size || new Set(input.runtimes.map((r) => r.runtime)).size !== expected.size || input.runtimes.some((r) => !expected.has(r.runtime))) blockers.push("runtime_inventory_mismatch");
|
|
8176
8432
|
for (const runtime of input.expectedRuntimes) {
|
|
8177
8433
|
const proof = input.runtimes.find((r) => r.runtime === runtime);
|
|
@@ -8201,7 +8457,7 @@ function planPrivateRoleMigration(input) {
|
|
|
8201
8457
|
if (privateRole === role && !Object.hasOwn(user.publicMetadata, "role")) alreadyCurrent++;
|
|
8202
8458
|
else changes.push({ userId: user.id, role, publicRole, privateRole });
|
|
8203
8459
|
}
|
|
8204
|
-
if (!input.protectedSuperAdminUserIds.some((
|
|
8460
|
+
if (!input.protectedSuperAdminUserIds.some((id11) => ids2.has(id11))) blockers.push("protected_operator_grant_required");
|
|
8205
8461
|
return { ready: blockers.length === 0, blockers, changes, alreadyCurrent, totalUsers: input.users.length };
|
|
8206
8462
|
}
|
|
8207
8463
|
|
|
@@ -8779,10 +9035,10 @@ function tiersOf(value, errors) {
|
|
|
8779
9035
|
return [];
|
|
8780
9036
|
}
|
|
8781
9037
|
unknownKeys3(item, TIER_KEYS2, path, errors);
|
|
8782
|
-
const
|
|
8783
|
-
if (
|
|
8784
|
-
if (seen.has(
|
|
8785
|
-
seen.add(
|
|
9038
|
+
const id11 = text5(item.id, `${path}.id`, errors, 80);
|
|
9039
|
+
if (id11 && !TIER.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
9040
|
+
if (seen.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
|
|
9041
|
+
seen.add(id11);
|
|
8786
9042
|
const name = text5(item.name, `${path}.name`, errors, 160);
|
|
8787
9043
|
if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
|
|
8788
9044
|
if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
|
|
@@ -8803,7 +9059,7 @@ function tiersOf(value, errors) {
|
|
|
8803
9059
|
}
|
|
8804
9060
|
const blurb = item.blurb === void 0 ? void 0 : text5(item.blurb, `${path}.blurb`, errors, 4e3);
|
|
8805
9061
|
return [{
|
|
8806
|
-
id:
|
|
9062
|
+
id: id11,
|
|
8807
9063
|
name,
|
|
8808
9064
|
available: item.available === true,
|
|
8809
9065
|
sortOrder: Number(item.sortOrder) || 0,
|
|
@@ -8865,15 +9121,15 @@ function policyOf(value, errors, tiers2) {
|
|
|
8865
9121
|
if (input.tierPolicies !== void 0) {
|
|
8866
9122
|
const overrides = recordOf(input.tierPolicies);
|
|
8867
9123
|
if (!overrides || Object.keys(overrides).length > 20) errors.push("document.policy.tierPolicies must be a bounded tier map");
|
|
8868
|
-
else for (const [
|
|
8869
|
-
const path = `document.policy.tierPolicies.${
|
|
9124
|
+
else for (const [id11, raw2] of Object.entries(overrides)) {
|
|
9125
|
+
const path = `document.policy.tierPolicies.${id11}`;
|
|
8870
9126
|
const policy2 = recordOf(raw2);
|
|
8871
|
-
if (!tiers2.some((tier) => tier.id ===
|
|
9127
|
+
if (!tiers2.some((tier) => tier.id === id11) || !policy2) {
|
|
8872
9128
|
errors.push(`${path} must name a declared tier and policy`);
|
|
8873
9129
|
continue;
|
|
8874
9130
|
}
|
|
8875
9131
|
unknownKeys3(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
|
|
8876
|
-
tierPolicies[
|
|
9132
|
+
tierPolicies[id11] = {
|
|
8877
9133
|
refundPolicyText: text5(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
|
|
8878
9134
|
...policy2.trustCopy !== void 0 ? { trustCopy: text5(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
|
|
8879
9135
|
};
|
|
@@ -9198,7 +9454,7 @@ var exclusions = (chapterId, paidTierIds) => [
|
|
|
9198
9454
|
id: chapterId,
|
|
9199
9455
|
reason: "provider_identity",
|
|
9200
9456
|
paths: [
|
|
9201
|
-
...paidTierIds.map((
|
|
9457
|
+
...paidTierIds.map((id11) => `tiers[${id11}].stripePriceId`),
|
|
9202
9458
|
"groups.stripePublishableKey",
|
|
9203
9459
|
"applications.stripeCustomerId",
|
|
9204
9460
|
"applications.stripeSubscriptionId",
|
|
@@ -9240,9 +9496,9 @@ async function exportChapterPromotion(input) {
|
|
|
9240
9496
|
sourceIds: [`signup-control:${document.revision}`, document.source.appId, document.target.chapterId].sort(),
|
|
9241
9497
|
receiptDigests: [input.digest]
|
|
9242
9498
|
};
|
|
9243
|
-
const artifact = (kind,
|
|
9499
|
+
const artifact = (kind, id11, content) => ({
|
|
9244
9500
|
kind,
|
|
9245
|
-
id:
|
|
9501
|
+
id: id11,
|
|
9246
9502
|
sourceRevision: revision,
|
|
9247
9503
|
provenance: promotionProvenance,
|
|
9248
9504
|
content: canonicalPromotionValue(content)
|
|
@@ -9287,7 +9543,7 @@ var MEMBER_SYSTEM_RECORD_OWNERS = {
|
|
|
9287
9543
|
provider: "originating-provider",
|
|
9288
9544
|
protectedRoles: "odla-platform"
|
|
9289
9545
|
};
|
|
9290
|
-
var rows2 = (group) => group.fields.map((
|
|
9546
|
+
var rows2 = (group) => group.fields.map((id11) => ({ ...group, id: id11 }));
|
|
9291
9547
|
var MEMBER_SYSTEM_AUTHORITY_MATRIX = [
|
|
9292
9548
|
...rows2({
|
|
9293
9549
|
scope: "network",
|