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