@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.cjs
CHANGED
|
@@ -253,7 +253,7 @@ __export(src_exports, {
|
|
|
253
253
|
slotWindow: () => slotWindow,
|
|
254
254
|
stageIndex: () => stageIndex,
|
|
255
255
|
stripeCall: () => stripeCall,
|
|
256
|
-
stripeForm: () =>
|
|
256
|
+
stripeForm: () => import_stripe11.stripeForm,
|
|
257
257
|
subAnnualCents: () => subAnnualCents,
|
|
258
258
|
submitApplication: () => submitApplication,
|
|
259
259
|
subscriptionIdempotencyKey: () => subscriptionIdempotencyKey,
|
|
@@ -275,7 +275,7 @@ __export(src_exports, {
|
|
|
275
275
|
verifyMembershipAccessReceipt: () => verifyMembershipAccessReceipt,
|
|
276
276
|
verifyMembershipCheckoutBinding: () => verifyMembershipCheckoutBinding,
|
|
277
277
|
verifyMembershipPaymentReceipt: () => verifyMembershipPaymentReceipt,
|
|
278
|
-
verifyStripeSignature: () =>
|
|
278
|
+
verifyStripeSignature: () => import_stripe11.verifyStripeSignature,
|
|
279
279
|
webhookMutationId: () => webhookMutationId
|
|
280
280
|
});
|
|
281
281
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -404,8 +404,8 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
404
404
|
if (app.requireDisclaimerAck && !acked) {
|
|
405
405
|
return { ok: false, error: "disclaimerAck is required" };
|
|
406
406
|
}
|
|
407
|
-
const
|
|
408
|
-
const row2 = { id:
|
|
407
|
+
const id11 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
|
|
408
|
+
const row2 = { id: id11, status: chapter.pipeline.initial, createdAt: opts.now };
|
|
409
409
|
for (const f of [...app.required, ...app.optional]) {
|
|
410
410
|
if (fieldStates[f]?.visible === false) continue;
|
|
411
411
|
if (typeof fields[f] === "string") row2[f] = fields[f].trim();
|
|
@@ -439,12 +439,12 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
439
439
|
}
|
|
440
440
|
if (acked) row2.disclaimerAckAt = opts.now;
|
|
441
441
|
const ops = [
|
|
442
|
-
{ t: "update", ns: "applications", id:
|
|
442
|
+
{ t: "update", ns: "applications", id: id11, attrs: row2 }
|
|
443
443
|
];
|
|
444
444
|
if (opts.admissionGrant?.decision === "waive_interview") {
|
|
445
445
|
ops.push({ t: "update", ns: "admissionGrants", id: opts.admissionGrant.id, attrs: {
|
|
446
446
|
consumedAt: opts.now,
|
|
447
|
-
consumedApplicationId:
|
|
447
|
+
consumedApplicationId: id11
|
|
448
448
|
} });
|
|
449
449
|
}
|
|
450
450
|
try {
|
|
@@ -452,7 +452,7 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
452
452
|
ops,
|
|
453
453
|
opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
|
|
454
454
|
);
|
|
455
|
-
return { ok: true, id:
|
|
455
|
+
return { ok: true, id: id11, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
|
|
456
456
|
} catch (error) {
|
|
457
457
|
if (error.code === "unique_violation") return {
|
|
458
458
|
ok: false,
|
|
@@ -1987,9 +1987,9 @@ var APPLICATION_LABELS = {
|
|
|
1987
1987
|
phone: "Phone",
|
|
1988
1988
|
state: "State"
|
|
1989
1989
|
};
|
|
1990
|
-
function humanize(
|
|
1991
|
-
const text6 =
|
|
1992
|
-
return text6 ? `${text6[0].toUpperCase()}${text6.slice(1)}` :
|
|
1990
|
+
function humanize(id11) {
|
|
1991
|
+
const text6 = id11.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
1992
|
+
return text6 ? `${text6[0].toUpperCase()}${text6.slice(1)}` : id11;
|
|
1993
1993
|
}
|
|
1994
1994
|
function applicationDefs(chapter) {
|
|
1995
1995
|
try {
|
|
@@ -1998,23 +1998,23 @@ function applicationDefs(chapter) {
|
|
|
1998
1998
|
return {};
|
|
1999
1999
|
}
|
|
2000
2000
|
}
|
|
2001
|
-
function applicationType(
|
|
2001
|
+
function applicationType(id11, def) {
|
|
2002
2002
|
if (def && ["string", "email", "url", "enum"].includes(def.type)) return def.type;
|
|
2003
|
-
return
|
|
2003
|
+
return id11 === "email" ? "email" : id11 === "linkedin" ? "url" : "string";
|
|
2004
2004
|
}
|
|
2005
2005
|
function applicationFlowFields(chapter, application = chapter.application) {
|
|
2006
2006
|
const defs = applicationDefs(chapter);
|
|
2007
|
-
return [...application.required, ...application.optional].map((
|
|
2008
|
-
const def = defs[
|
|
2009
|
-
const conditions = application.conditions[
|
|
2010
|
-
const type = applicationType(
|
|
2007
|
+
return [...application.required, ...application.optional].map((id11, order2) => {
|
|
2008
|
+
const def = defs[id11];
|
|
2009
|
+
const conditions = application.conditions[id11];
|
|
2010
|
+
const type = applicationType(id11, def);
|
|
2011
2011
|
return {
|
|
2012
|
-
id:
|
|
2013
|
-
label: def?.label ?? APPLICATION_LABELS[
|
|
2012
|
+
id: id11,
|
|
2013
|
+
label: def?.label ?? APPLICATION_LABELS[id11] ?? humanize(id11),
|
|
2014
2014
|
type,
|
|
2015
|
-
required: application.required.includes(
|
|
2015
|
+
required: application.required.includes(id11),
|
|
2016
2016
|
order: order2,
|
|
2017
|
-
maxLength: application.maxLen[
|
|
2017
|
+
maxLength: application.maxLen[id11] ?? application.defaultMaxLen,
|
|
2018
2018
|
...type === "enum" && def?.options ? { options: [...def.options] } : {},
|
|
2019
2019
|
...conditions ? { conditions: { ...conditions } } : {},
|
|
2020
2020
|
...type === "email" ? { emailIdentity: "literal" } : {}
|
|
@@ -2024,17 +2024,17 @@ function applicationFlowFields(chapter, application = chapter.application) {
|
|
|
2024
2024
|
function resolveFormationFlowFields(crm, formation) {
|
|
2025
2025
|
if (!formation.enabled) return [];
|
|
2026
2026
|
const def = crm.type(formation.type);
|
|
2027
|
-
return [...formation.required, ...formation.optional].map((
|
|
2028
|
-
const field2 = def.fields[
|
|
2027
|
+
return [...formation.required, ...formation.optional].map((id11, order2) => {
|
|
2028
|
+
const field2 = def.fields[id11];
|
|
2029
2029
|
return {
|
|
2030
|
-
id:
|
|
2031
|
-
label: field2.label ?? humanize(
|
|
2030
|
+
id: id11,
|
|
2031
|
+
label: field2.label ?? humanize(id11),
|
|
2032
2032
|
type: field2.type,
|
|
2033
|
-
required: formation.required.includes(
|
|
2033
|
+
required: formation.required.includes(id11),
|
|
2034
2034
|
order: order2,
|
|
2035
|
-
maxLength: formation.maxLen[
|
|
2035
|
+
maxLength: formation.maxLen[id11] ?? formation.defaultMaxLen,
|
|
2036
2036
|
...field2.options ? { options: [...field2.options] } : {},
|
|
2037
|
-
...
|
|
2037
|
+
...id11 === def.emailField || field2.type === "email" ? { emailIdentity: "literal" } : {}
|
|
2038
2038
|
};
|
|
2039
2039
|
});
|
|
2040
2040
|
}
|
|
@@ -2338,12 +2338,12 @@ function resolveNetworkPersonRegistry(mode, input, targetIds) {
|
|
|
2338
2338
|
if (input.mode !== "shadow") throw new Error('defineChapter.networkPersonRegistry.mode: must be "shadow"');
|
|
2339
2339
|
const chapterIds = input.chapterIds ? [...input.chapterIds] : [...targetIds];
|
|
2340
2340
|
if (chapterIds.length === 0) throw new Error("defineChapter.networkPersonRegistry.chapterIds: at least one chapter is required");
|
|
2341
|
-
if (new Set(chapterIds).size !== chapterIds.length || chapterIds.some((
|
|
2341
|
+
if (new Set(chapterIds).size !== chapterIds.length || chapterIds.some((id11) => !SLUG2.test(id11))) {
|
|
2342
2342
|
throw new Error("defineChapter.networkPersonRegistry.chapterIds: expected distinct lowercase slugs");
|
|
2343
2343
|
}
|
|
2344
|
-
const unknown = chapterIds.filter((
|
|
2344
|
+
const unknown = chapterIds.filter((id11) => !targetIds.includes(id11));
|
|
2345
2345
|
if (unknown.length) {
|
|
2346
|
-
throw new Error(`defineChapter.networkPersonRegistry.chapterIds: ${unknown.map((
|
|
2346
|
+
throw new Error(`defineChapter.networkPersonRegistry.chapterIds: ${unknown.map((id11) => `"${id11}"`).join(", ")} are not configured network targets`);
|
|
2347
2347
|
}
|
|
2348
2348
|
return { mode: "shadow", chapterIds };
|
|
2349
2349
|
}
|
|
@@ -2466,9 +2466,9 @@ function isResolvedCrm(x) {
|
|
|
2466
2466
|
}
|
|
2467
2467
|
function defineChapter(config) {
|
|
2468
2468
|
if (!config || typeof config !== "object") throw new Error("defineChapter: a config object is required");
|
|
2469
|
-
const { id:
|
|
2470
|
-
if (typeof
|
|
2471
|
-
throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(
|
|
2469
|
+
const { id: id11, name } = config;
|
|
2470
|
+
if (typeof id11 !== "string" || !SLUG7.test(id11)) {
|
|
2471
|
+
throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id11)}`);
|
|
2472
2472
|
}
|
|
2473
2473
|
if (typeof name !== "string" || name.trim() === "") throw new Error("defineChapter.name: a non-empty name is required");
|
|
2474
2474
|
const mode = config.mode ?? "chapter";
|
|
@@ -2575,7 +2575,7 @@ function defineChapter(config) {
|
|
|
2575
2575
|
};
|
|
2576
2576
|
const chapter = {
|
|
2577
2577
|
config,
|
|
2578
|
-
id:
|
|
2578
|
+
id: id11,
|
|
2579
2579
|
name,
|
|
2580
2580
|
mode,
|
|
2581
2581
|
brand,
|
|
@@ -2990,14 +2990,14 @@ function facts(snapshots) {
|
|
|
2990
2990
|
["payment_intent.updated", snapshots.payments?.objects ?? []]
|
|
2991
2991
|
];
|
|
2992
2992
|
return groups2.flatMap(([eventType, objects]) => objects.map((object2) => {
|
|
2993
|
-
const
|
|
2993
|
+
const id11 = typeof object2.id === "string" ? object2.id : "invalid";
|
|
2994
2994
|
return normalizeNetworkCommercialStripeEvent({
|
|
2995
|
-
id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${
|
|
2995
|
+
id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id11}`,
|
|
2996
2996
|
type: eventType,
|
|
2997
2997
|
created: typeof object2.created === "number" ? object2.created : 0,
|
|
2998
2998
|
livemode: object2.livemode === true,
|
|
2999
2999
|
data: { object: object2 }
|
|
3000
|
-
}, `stripe-snapshot:${eventType}:${
|
|
3000
|
+
}, `stripe-snapshot:${eventType}:${id11}`);
|
|
3001
3001
|
}));
|
|
3002
3002
|
}
|
|
3003
3003
|
var selected = (row2) => ({
|
|
@@ -3706,8 +3706,8 @@ async function hash(value) {
|
|
|
3706
3706
|
async function membershipDiscountCouponId(input, policyId) {
|
|
3707
3707
|
return policyId ? `odla_m_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.skuId, policyId])}` : void 0;
|
|
3708
3708
|
}
|
|
3709
|
-
async function load(db, ns,
|
|
3710
|
-
return (await db.query({ [ns]: { $: { where: { id:
|
|
3709
|
+
async function load(db, ns, id11) {
|
|
3710
|
+
return (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
|
|
3711
3711
|
}
|
|
3712
3712
|
async function membershipOfferIdForRequest(input) {
|
|
3713
3713
|
return `offer_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.applicationId, input.requestId])}`;
|
|
@@ -3844,20 +3844,20 @@ var import_stripe2 = require("@odla-ai/stripe");
|
|
|
3844
3844
|
|
|
3845
3845
|
// src/commercial-evidence-guard.ts
|
|
3846
3846
|
async function commercialEvidenceGuard(db, eventId, digest, code = "membership_payment_evidence_quarantined") {
|
|
3847
|
-
const ns = "networkCommercialExceptions",
|
|
3848
|
-
const row2 = (await db.query({ [ns]: { $: { where: { id:
|
|
3849
|
-
if (!row2) return { ns, id:
|
|
3847
|
+
const ns = "networkCommercialExceptions", id11 = `network-commercial-exception:${eventId}:collision`;
|
|
3848
|
+
const row2 = (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
|
|
3849
|
+
if (!row2) return { ns, id: id11, exists: false };
|
|
3850
3850
|
if (row2.status !== "resolved" || row2.resolvedDigest !== digest || typeof digest !== "string") {
|
|
3851
3851
|
throw Object.assign(new Error(code), { code });
|
|
3852
3852
|
}
|
|
3853
|
-
return { ns, id:
|
|
3853
|
+
return { ns, id: id11, exists: true, equals: { status: "resolved", resolvedDigest: digest, updatedAt: row2.updatedAt } };
|
|
3854
3854
|
}
|
|
3855
3855
|
|
|
3856
3856
|
// src/membership-authority-state.ts
|
|
3857
3857
|
var membershipFail = (code) => {
|
|
3858
3858
|
throw Object.assign(new Error(code), { code });
|
|
3859
3859
|
};
|
|
3860
|
-
var membershipLoad = async (db, ns,
|
|
3860
|
+
var membershipLoad = async (db, ns, id11) => (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
|
|
3861
3861
|
var membershipScopeKeys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId"];
|
|
3862
3862
|
var membershipScope = (input) => Object.fromEntries(membershipScopeKeys.map((key3) => [key3, input[key3]]));
|
|
3863
3863
|
function requireMembershipScope(row2, input) {
|
|
@@ -3868,18 +3868,18 @@ async function loadMembershipAuthorityState(db, input) {
|
|
|
3868
3868
|
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");
|
|
3869
3869
|
return row2;
|
|
3870
3870
|
}
|
|
3871
|
-
var membershipUpdate = (ns,
|
|
3871
|
+
var membershipUpdate = (ns, id11, attrs) => ({ t: "update", ns, id: id11, attrs });
|
|
3872
3872
|
var membershipClock = (now) => {
|
|
3873
3873
|
if (!Number.isSafeInteger(now) || now < 0) membershipFail("invalid_membership_clock");
|
|
3874
3874
|
};
|
|
3875
3875
|
|
|
3876
3876
|
// src/membership-restart-payment.ts
|
|
3877
3877
|
async function membershipRestartPayment(db, target, member, now) {
|
|
3878
|
-
const
|
|
3878
|
+
const id11 = String(member.id), restart = await membershipLoad(db, "membershipRestart", id11);
|
|
3879
3879
|
requireMembershipScope(restart, target);
|
|
3880
|
-
const entitlement = await membershipLoad(db, "networkEntitlement", `${
|
|
3880
|
+
const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
|
|
3881
3881
|
requireMembershipScope(entitlement, target);
|
|
3882
|
-
if (entitlement.membershipId !==
|
|
3882
|
+
if (entitlement.membershipId !== id11 || entitlement.kind !== "primary") membershipFail("membership_restart_conflict");
|
|
3883
3883
|
if (restart.status !== "ready" || member.status !== "restarting" || member.restartId !== restart.restartId || member.memberNumber !== restart.memberNumber || !Number.isSafeInteger(member.journeyApprovedAt)) membershipFail("membership_restart_conflict");
|
|
3884
3884
|
return {
|
|
3885
3885
|
restartId: String(restart.restartId),
|
|
@@ -3896,49 +3896,117 @@ async function membershipRestartPayment(db, target, member, now) {
|
|
|
3896
3896
|
graceEndsAt: 0
|
|
3897
3897
|
},
|
|
3898
3898
|
ops: [
|
|
3899
|
-
membershipUpdate("membershipRestart",
|
|
3900
|
-
{ t: "retract", ns: "networkMembership", id:
|
|
3899
|
+
membershipUpdate("membershipRestart", id11, { status: "complete" }),
|
|
3900
|
+
{ t: "retract", ns: "networkMembership", id: id11, attrs: ["refundRequestedAt", "cancellationRequestedAt", "discountPolicyVersionId", "discountSnapshot"] }
|
|
3901
3901
|
],
|
|
3902
3902
|
guards: [
|
|
3903
|
-
{ ns: "networkMembership", id:
|
|
3904
|
-
{ ns: "membershipRestart", id:
|
|
3903
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { status: "restarting", revision: member.revision, restartId: restart.restartId } },
|
|
3904
|
+
{ ns: "membershipRestart", id: id11, exists: true, equals: { status: "ready", restartId: restart.restartId } },
|
|
3905
3905
|
{ ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision, status: entitlement.status } }
|
|
3906
3906
|
]
|
|
3907
3907
|
};
|
|
3908
3908
|
}
|
|
3909
3909
|
|
|
3910
|
+
// src/checkout-named-seat.ts
|
|
3911
|
+
function checkoutNamedSeatSelection(value, purchaserEmail) {
|
|
3912
|
+
const row2 = value;
|
|
3913
|
+
const name = typeof row2?.recipientName === "string" ? row2.recipientName.trim().replace(/\s+/g, " ") : "";
|
|
3914
|
+
const email = normalizeChapterEmail(typeof row2?.recipientEmail === "string" ? row2.recipientEmail : "");
|
|
3915
|
+
const purchaser = normalizeChapterEmail(purchaserEmail);
|
|
3916
|
+
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) {
|
|
3917
|
+
throw Object.assign(new Error("Enter a different recipient's name and email."), { code: "invalid_named_seat_recipient" });
|
|
3918
|
+
}
|
|
3919
|
+
return { recipientName: name, recipientEmail: email };
|
|
3920
|
+
}
|
|
3921
|
+
async function checkoutNamedSeatIdentity(target, email) {
|
|
3922
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([
|
|
3923
|
+
target.appId,
|
|
3924
|
+
target.chapterId,
|
|
3925
|
+
target.environment,
|
|
3926
|
+
target.runtime,
|
|
3927
|
+
email
|
|
3928
|
+
])));
|
|
3929
|
+
return {
|
|
3930
|
+
seatId: (await membershipOfferIdForRequest({ ...target, requestId: "named_seat" })).replace(/^offer_/, "seat_"),
|
|
3931
|
+
membershipId: (await membershipOfferIdForRequest({ ...target, requestId: "checkout_binding" })).replace(/^offer_/, "membership_"),
|
|
3932
|
+
recipientClaimId: `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`
|
|
3933
|
+
};
|
|
3934
|
+
}
|
|
3935
|
+
|
|
3910
3936
|
// src/membership-checkout-binding.ts
|
|
3911
3937
|
var keys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId", "offerId", "offerDigest", "quoteDigest", "priceId"];
|
|
3912
3938
|
var reject2 = (code) => {
|
|
3913
3939
|
throw Object.assign(new Error(code), { code });
|
|
3914
3940
|
};
|
|
3915
|
-
var load2 = async (db, ns,
|
|
3941
|
+
var load2 = async (db, ns, id11) => (await db.query({ [ns]: { $: { where: { id: id11 }, limit: 1 } } }))[ns]?.[0];
|
|
3916
3942
|
async function membershipCheckoutBindingId(input) {
|
|
3917
3943
|
return (await membershipOfferIdForRequest({ ...input, requestId: "checkout_binding" })).replace(/^offer_/, "checkout_");
|
|
3918
3944
|
}
|
|
3919
3945
|
async function verifyMembershipCheckoutBinding(value, input) {
|
|
3920
3946
|
const row2 = value;
|
|
3921
3947
|
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");
|
|
3922
|
-
|
|
3948
|
+
if (Boolean(row2.namedSeat) !== Boolean(input.namedSeat)) return reject2("membership_checkout_conflict");
|
|
3949
|
+
if (input.namedSeat) {
|
|
3950
|
+
const expected = {
|
|
3951
|
+
...checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail),
|
|
3952
|
+
purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail)
|
|
3953
|
+
};
|
|
3954
|
+
const identity2 = await checkoutNamedSeatIdentity(input, expected.recipientEmail);
|
|
3955
|
+
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");
|
|
3956
|
+
}
|
|
3957
|
+
return {
|
|
3958
|
+
id: row2.id,
|
|
3959
|
+
...Object.fromEntries(keys.map((key3) => [key3, input[key3]])),
|
|
3960
|
+
boundAt: row2.boundAt,
|
|
3961
|
+
...row2.namedSeat ? { namedSeat: row2.namedSeat } : {}
|
|
3962
|
+
};
|
|
3923
3963
|
}
|
|
3924
|
-
async function bindMembershipCheckout(db, input, now) {
|
|
3964
|
+
async function bindMembershipCheckout(db, input, now, seatPriceId) {
|
|
3925
3965
|
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");
|
|
3926
3966
|
const stateId = `${input.chapterId}:${input.environment}`;
|
|
3927
3967
|
const state = await load2(db, "membershipAuthorityState", stateId);
|
|
3928
3968
|
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");
|
|
3929
|
-
const
|
|
3930
|
-
const existing = await load2(db, "membershipOfferCheckout",
|
|
3969
|
+
const id11 = await membershipCheckoutBindingId(input);
|
|
3970
|
+
const existing = await load2(db, "membershipOfferCheckout", id11);
|
|
3931
3971
|
if (existing) return verifyMembershipCheckoutBinding(existing, input);
|
|
3932
3972
|
const row2 = await load2(db, "membershipCheckoutOffer", input.offerId);
|
|
3933
3973
|
const offer = await verifyIssuedMembershipOffer(row2, { ...input, now, lifetimeMs: 6e4 });
|
|
3934
3974
|
if (offer.digest !== input.offerDigest || offer.expiresAt <= now || offer.issuedAt > now) return reject2("membership_offer_expired");
|
|
3935
|
-
const restart = await load2(db, "membershipRestart",
|
|
3936
|
-
|
|
3975
|
+
const restart = await load2(db, "membershipRestart", id11.replace(/^checkout_/, "membership_"));
|
|
3976
|
+
let namedSeat;
|
|
3977
|
+
if (input.namedSeat) {
|
|
3978
|
+
if (!seatPriceId || !/^price_[A-Za-z0-9_]+$/.test(seatPriceId) || seatPriceId === input.priceId || restart) return reject2("named_seat_purchase_unavailable");
|
|
3979
|
+
const selection = checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail);
|
|
3980
|
+
namedSeat = {
|
|
3981
|
+
...selection,
|
|
3982
|
+
purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail),
|
|
3983
|
+
...await checkoutNamedSeatIdentity(input, selection.recipientEmail),
|
|
3984
|
+
priceId: seatPriceId,
|
|
3985
|
+
amountCents: offer.contract.offering.seatPriceCents
|
|
3986
|
+
};
|
|
3987
|
+
}
|
|
3988
|
+
const receipt = { id: id11, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: now, ...namedSeat ? { namedSeat } : {} };
|
|
3937
3989
|
try {
|
|
3938
|
-
await db.transact([
|
|
3939
|
-
|
|
3990
|
+
await db.transact([
|
|
3991
|
+
{ t: "update", ns: "membershipOfferCheckout", id: id11, attrs: receipt },
|
|
3992
|
+
...namedSeat ? [{ t: "update", ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, attrs: {
|
|
3993
|
+
id: namedSeat.recipientClaimId,
|
|
3994
|
+
appId: input.appId,
|
|
3995
|
+
chapterId: input.chapterId,
|
|
3996
|
+
environment: input.environment,
|
|
3997
|
+
runtime: input.runtime,
|
|
3998
|
+
seatId: namedSeat.seatId,
|
|
3999
|
+
recipientEmailNormalized: namedSeat.recipientEmail,
|
|
4000
|
+
createdAt: now
|
|
4001
|
+
} }] : []
|
|
4002
|
+
], {
|
|
4003
|
+
mutationId: `membership-checkout:${id11}${restart?.restartId ? `:${restart.restartId}` : ""}`,
|
|
3940
4004
|
guards: [
|
|
3941
|
-
{ ns: "membershipOfferCheckout", id:
|
|
4005
|
+
{ ns: "membershipOfferCheckout", id: id11, exists: false },
|
|
4006
|
+
...namedSeat ? [
|
|
4007
|
+
{ ns: "networkSeat", id: namedSeat.seatId, exists: false },
|
|
4008
|
+
{ ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, exists: false }
|
|
4009
|
+
] : [],
|
|
3942
4010
|
{ ns: "membershipCheckoutOffer", id: input.offerId, exists: true, equals: { digest: input.offerDigest } },
|
|
3943
4011
|
{ ns: "membershipAuthorityState", id: stateId, exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
3944
4012
|
]
|
|
@@ -3946,11 +4014,78 @@ async function bindMembershipCheckout(db, input, now) {
|
|
|
3946
4014
|
} catch (error) {
|
|
3947
4015
|
if (error.code !== "transact_guard_failed") throw error;
|
|
3948
4016
|
}
|
|
3949
|
-
const saved = await load2(db, "membershipOfferCheckout",
|
|
4017
|
+
const saved = await load2(db, "membershipOfferCheckout", id11);
|
|
3950
4018
|
if (!saved) return reject2("membership_binding_unconfirmed");
|
|
3951
4019
|
return verifyMembershipCheckoutBinding(saved, input);
|
|
3952
4020
|
}
|
|
3953
4021
|
|
|
4022
|
+
// src/membership-checkout-seat-payment.ts
|
|
4023
|
+
function checkoutSeatPaymentOperations(target, seat, input) {
|
|
4024
|
+
const { now, paidAt, endsAt } = input;
|
|
4025
|
+
const included = seat.amountCents === 0, entitlementId = `${seat.seatId}:entitlement`;
|
|
4026
|
+
const effectId = `${seat.seatId}:${included ? "invite" : "attach-renewal"}`;
|
|
4027
|
+
return [
|
|
4028
|
+
membershipUpdate("networkSeat", seat.seatId, {
|
|
4029
|
+
id: seat.seatId,
|
|
4030
|
+
...membershipScope(target),
|
|
4031
|
+
primaryMembershipId: seat.membershipId,
|
|
4032
|
+
purchaserEmailNormalized: seat.purchaserEmail,
|
|
4033
|
+
recipientName: seat.recipientName,
|
|
4034
|
+
recipientEmailNormalized: seat.recipientEmail,
|
|
4035
|
+
recipientClaimId: seat.recipientClaimId,
|
|
4036
|
+
priceId: seat.priceId,
|
|
4037
|
+
primaryPriceId: input.primaryPriceId,
|
|
4038
|
+
amountCents: seat.amountCents,
|
|
4039
|
+
currency: input.offering.currency,
|
|
4040
|
+
interval: input.offering.interval,
|
|
4041
|
+
providerCustomerId: input.customerId,
|
|
4042
|
+
providerSubscriptionId: input.subscriptionId,
|
|
4043
|
+
combinedInvoice: { invoiceId: input.invoiceId, totalCents: input.invoiceTotalCents },
|
|
4044
|
+
status: included ? "pending_acceptance" : "renewal_pending",
|
|
4045
|
+
preparedAt: input.boundAt,
|
|
4046
|
+
purchasedAt: paidAt,
|
|
4047
|
+
termStartedAt: paidAt,
|
|
4048
|
+
termEndsAt: endsAt,
|
|
4049
|
+
acceptanceEndsAt: endsAt,
|
|
4050
|
+
seatPurchaseEndsAt: seatPurchaseDeadline(input.offering, { initialTermStartedAt: paidAt, termEndsAt: endsAt }),
|
|
4051
|
+
offeringSnapshot: input.offering,
|
|
4052
|
+
paymentEventId: input.eventId,
|
|
4053
|
+
...included ? { entitlementId } : {},
|
|
4054
|
+
revision: 1,
|
|
4055
|
+
createdAt: now,
|
|
4056
|
+
updatedAt: now
|
|
4057
|
+
}),
|
|
4058
|
+
...included ? [membershipUpdate("networkEntitlement", entitlementId, {
|
|
4059
|
+
id: entitlementId,
|
|
4060
|
+
...membershipScope(target),
|
|
4061
|
+
membershipId: seat.membershipId,
|
|
4062
|
+
seatId: seat.seatId,
|
|
4063
|
+
kind: "named_seat",
|
|
4064
|
+
status: "pending_acceptance",
|
|
4065
|
+
termStartedAt: paidAt,
|
|
4066
|
+
termEndsAt: endsAt,
|
|
4067
|
+
offeringSnapshot: input.offering,
|
|
4068
|
+
sourceEventId: input.eventId,
|
|
4069
|
+
revision: 1,
|
|
4070
|
+
createdAt: now,
|
|
4071
|
+
updatedAt: now
|
|
4072
|
+
})] : [],
|
|
4073
|
+
membershipUpdate("membershipEffect", effectId, {
|
|
4074
|
+
id: effectId,
|
|
4075
|
+
chapterId: target.chapterId,
|
|
4076
|
+
environment: target.environment,
|
|
4077
|
+
runtime: target.runtime,
|
|
4078
|
+
membershipId: seat.membershipId,
|
|
4079
|
+
seatId: seat.seatId,
|
|
4080
|
+
kind: included ? "invite_named_recipient" : "attach_seat_renewal",
|
|
4081
|
+
status: "pending",
|
|
4082
|
+
attempts: 0,
|
|
4083
|
+
createdAt: now,
|
|
4084
|
+
updatedAt: now
|
|
4085
|
+
})
|
|
4086
|
+
];
|
|
4087
|
+
}
|
|
4088
|
+
|
|
3954
4089
|
// src/membership-payment-authority.ts
|
|
3955
4090
|
async function networkMembershipId(input) {
|
|
3956
4091
|
return (await membershipCheckoutBindingId(input)).replace(/^checkout_/, "membership_");
|
|
@@ -3966,11 +4101,14 @@ async function confirmMembershipPayment(db, input, now) {
|
|
|
3966
4101
|
{ ...input, offerId: String(binding.offerId), now, lifetimeMs: 6e4 }
|
|
3967
4102
|
);
|
|
3968
4103
|
if (binding.offerDigest !== offer.digest) membershipFail("membership_checkout_conflict");
|
|
4104
|
+
const { namedSeat } = await verifyMembershipCheckoutBinding(binding, binding);
|
|
4105
|
+
if (namedSeat && namedSeat.amountCents !== offer.contract.offering.seatPriceCents) membershipFail("membership_checkout_conflict");
|
|
4106
|
+
const totalCents = offer.contract.dueTodayCents + (namedSeat?.amountCents ?? 0);
|
|
3969
4107
|
const eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
|
|
3970
4108
|
if (!eventRow) membershipFail("membership_payment_evidence_pending");
|
|
3971
4109
|
const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow.digest);
|
|
3972
4110
|
const fact = eventRow.fact;
|
|
3973
|
-
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 !==
|
|
4111
|
+
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({
|
|
3974
4112
|
appId: input.appId,
|
|
3975
4113
|
chapterId: input.chapterId,
|
|
3976
4114
|
environment: input.environment,
|
|
@@ -3982,14 +4120,14 @@ async function confirmMembershipPayment(db, input, now) {
|
|
|
3982
4120
|
const paidAt = fact.invoicePaidAt;
|
|
3983
4121
|
const endsAt = offer.contract.initialTermEndsAt ?? fact.invoicePeriodEndsAt;
|
|
3984
4122
|
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");
|
|
3985
|
-
const
|
|
3986
|
-
let member = await membershipLoad(db, "networkMembership",
|
|
4123
|
+
const id11 = await networkMembershipId(input), entitlementId = `${id11}:primary`;
|
|
4124
|
+
let member = await membershipLoad(db, "networkMembership", id11);
|
|
3987
4125
|
if (!member || member.status === "restarting") {
|
|
3988
4126
|
const offering = offer.contract.offering;
|
|
3989
4127
|
const restart = member ? await membershipRestartPayment(db, input, member, now) : null;
|
|
3990
4128
|
const attrs = {
|
|
3991
|
-
id:
|
|
3992
|
-
membershipId:
|
|
4129
|
+
id: id11,
|
|
4130
|
+
membershipId: id11,
|
|
3993
4131
|
...membershipScope(input),
|
|
3994
4132
|
baseSkuId: input.skuId,
|
|
3995
4133
|
status: "paid_pending_approval",
|
|
@@ -4005,8 +4143,9 @@ async function confirmMembershipPayment(db, input, now) {
|
|
|
4005
4143
|
founding: offer.contract.founding,
|
|
4006
4144
|
cohort: offer.contract.founding ? offer.contract.discountPolicy.cohortLabel : "standard",
|
|
4007
4145
|
baseAmountCents: offering.basePriceCents,
|
|
4008
|
-
amountCents:
|
|
4146
|
+
amountCents: totalCents,
|
|
4009
4147
|
renewalAmountCents: offer.contract.renewalCents,
|
|
4148
|
+
...namedSeat ? { namedSeatId: namedSeat.seatId } : {},
|
|
4010
4149
|
currency: offering.currency,
|
|
4011
4150
|
interval: offering.interval,
|
|
4012
4151
|
initialTermStartedAt: paidAt,
|
|
@@ -4023,11 +4162,11 @@ async function confirmMembershipPayment(db, input, now) {
|
|
|
4023
4162
|
};
|
|
4024
4163
|
try {
|
|
4025
4164
|
await db.transact([
|
|
4026
|
-
membershipUpdate("networkMembership",
|
|
4165
|
+
membershipUpdate("networkMembership", id11, attrs),
|
|
4027
4166
|
membershipUpdate("networkEntitlement", entitlementId, {
|
|
4028
4167
|
id: entitlementId,
|
|
4029
4168
|
...membershipScope(input),
|
|
4030
|
-
membershipId:
|
|
4169
|
+
membershipId: id11,
|
|
4031
4170
|
kind: "primary",
|
|
4032
4171
|
status: restart?.attrs.status === "active" ? "active" : "pending_approval",
|
|
4033
4172
|
termStartedAt: paidAt,
|
|
@@ -4039,23 +4178,45 @@ async function confirmMembershipPayment(db, input, now) {
|
|
|
4039
4178
|
updatedAt: now,
|
|
4040
4179
|
...restart?.entitlementAttrs
|
|
4041
4180
|
}),
|
|
4042
|
-
...restart?.ops ?? []
|
|
4043
|
-
|
|
4044
|
-
|
|
4181
|
+
...restart?.ops ?? [],
|
|
4182
|
+
...namedSeat ? checkoutSeatPaymentOperations(input, namedSeat, {
|
|
4183
|
+
offering,
|
|
4184
|
+
primaryPriceId: String(binding.priceId),
|
|
4185
|
+
invoiceId: fact.invoiceId,
|
|
4186
|
+
invoiceTotalCents: totalCents,
|
|
4187
|
+
subscriptionId: input.providerSubscriptionId,
|
|
4188
|
+
customerId: input.providerCustomerId,
|
|
4189
|
+
eventId: input.eventId,
|
|
4190
|
+
paidAt: Number(paidAt),
|
|
4191
|
+
endsAt: Number(endsAt),
|
|
4192
|
+
boundAt: Number(binding.boundAt),
|
|
4193
|
+
now
|
|
4194
|
+
}) : []
|
|
4195
|
+
], { mutationId: `membership-paid:${id11}${restart ? `:${restart.restartId}` : ""}`, guards: [
|
|
4196
|
+
...restart?.guards ?? [{ ns: "networkMembership", id: id11, exists: false }],
|
|
4197
|
+
...namedSeat ? [
|
|
4198
|
+
{ ns: "networkSeat", id: namedSeat.seatId, exists: false },
|
|
4199
|
+
{
|
|
4200
|
+
ns: "networkSeatRecipient",
|
|
4201
|
+
id: namedSeat.recipientClaimId,
|
|
4202
|
+
exists: true,
|
|
4203
|
+
equals: { seatId: namedSeat.seatId, recipientEmailNormalized: namedSeat.recipientEmail }
|
|
4204
|
+
}
|
|
4205
|
+
] : [],
|
|
4045
4206
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
|
|
4046
|
-
{ ns: "membershipOfferCheckout", id: String(binding.id), exists: true, equals: { offerDigest: offer.digest } },
|
|
4207
|
+
{ ns: "membershipOfferCheckout", id: String(binding.id), exists: true, equals: { offerDigest: offer.digest, quoteDigest: binding.quoteDigest } },
|
|
4047
4208
|
{ ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
|
|
4048
4209
|
evidenceGuard
|
|
4049
4210
|
] });
|
|
4050
4211
|
} catch (error) {
|
|
4051
4212
|
if (error.code !== "transact_guard_failed") throw error;
|
|
4052
4213
|
}
|
|
4053
|
-
member = await membershipLoad(db, "networkMembership",
|
|
4214
|
+
member = await membershipLoad(db, "networkMembership", id11);
|
|
4054
4215
|
}
|
|
4055
4216
|
requireMembershipScope(member, input);
|
|
4056
4217
|
if (member.purchaseOfferDigest !== offer.digest || member.providerInitialInvoiceId !== fact.invoiceId || member.providerSubscriptionId !== fact.subscriptionId || member.providerCustomerId !== fact.customerId) membershipFail("membership_purchase_conflict");
|
|
4057
4218
|
return {
|
|
4058
|
-
membershipId:
|
|
4219
|
+
membershipId: id11,
|
|
4059
4220
|
status: String(member.status),
|
|
4060
4221
|
cohort: String(member.cohort),
|
|
4061
4222
|
initialTermStartedAt: Number(member.initialTermStartedAt),
|
|
@@ -4067,9 +4228,9 @@ async function confirmMembershipPayment(db, input, now) {
|
|
|
4067
4228
|
// src/membership-cancel-authority.ts
|
|
4068
4229
|
async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
|
|
4069
4230
|
membershipClock(now);
|
|
4070
|
-
const
|
|
4231
|
+
const id11 = await networkMembershipId(target);
|
|
4071
4232
|
const state = await loadMembershipAuthorityState(db, target);
|
|
4072
|
-
const member = await membershipLoad(db, "networkMembership",
|
|
4233
|
+
const member = await membershipLoad(db, "networkMembership", id11);
|
|
4073
4234
|
requireMembershipScope(member, target);
|
|
4074
4235
|
if (!["active", "cancel_at_term_end", "payment_grace", "paid_pending_approval", "lapsed"].includes(String(member.status))) {
|
|
4075
4236
|
membershipFail("membership_cancellation_unavailable");
|
|
@@ -4087,7 +4248,7 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
|
|
|
4087
4248
|
"POST",
|
|
4088
4249
|
path,
|
|
4089
4250
|
{ cancel_at_period_end: true, proration_behavior: "none" },
|
|
4090
|
-
{ idempotencyKey: `membership-cancel-at-term:${
|
|
4251
|
+
{ idempotencyKey: `membership-cancel-at-term:${id11}:${member.providerSubscriptionId}` }
|
|
4091
4252
|
);
|
|
4092
4253
|
if (!write.ok || write.body.id !== member.providerSubscriptionId || write.body.cancel_at_period_end !== true) {
|
|
4093
4254
|
membershipFail("membership_cancellation_pending");
|
|
@@ -4095,7 +4256,7 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
|
|
|
4095
4256
|
}
|
|
4096
4257
|
const status2 = Number(member.termEndsAt) <= now ? "lapsed" : member.journeyApprovedAt ? "cancel_at_term_end" : "paid_pending_approval";
|
|
4097
4258
|
if (member.status !== status2 || member.autoRenew !== false || Number(member.graceEndsAt ?? 0) !== 0) {
|
|
4098
|
-
await db.transact([membershipUpdate("networkMembership",
|
|
4259
|
+
await db.transact([membershipUpdate("networkMembership", id11, {
|
|
4099
4260
|
status: status2,
|
|
4100
4261
|
autoRenew: false,
|
|
4101
4262
|
graceEndsAt: 0,
|
|
@@ -4104,12 +4265,12 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
|
|
|
4104
4265
|
updatedAt: now
|
|
4105
4266
|
})], {
|
|
4106
4267
|
guards: [
|
|
4107
|
-
{ ns: "networkMembership", id:
|
|
4268
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
|
|
4108
4269
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
|
|
4109
4270
|
]
|
|
4110
4271
|
});
|
|
4111
4272
|
}
|
|
4112
|
-
return { membershipId:
|
|
4273
|
+
return { membershipId: id11, status: status2, termEndsAt: Number(member.termEndsAt), graceEndsAt: 0, autoRenew: false };
|
|
4113
4274
|
}
|
|
4114
4275
|
|
|
4115
4276
|
// src/membership-authority-handler.ts
|
|
@@ -4148,17 +4309,17 @@ async function namedSeatPrimaryActivation(db, member, now) {
|
|
|
4148
4309
|
// src/membership-approval-authority.ts
|
|
4149
4310
|
async function approveNetworkMembership(db, input, now) {
|
|
4150
4311
|
membershipClock(now);
|
|
4151
|
-
const
|
|
4312
|
+
const id11 = await networkMembershipId(input), entitlementId = `${id11}:primary`;
|
|
4152
4313
|
for (let attempt = 0; attempt < 8; attempt++) {
|
|
4153
4314
|
const state = await loadMembershipAuthorityState(db, input);
|
|
4154
|
-
const member = await membershipLoad(db, "networkMembership",
|
|
4315
|
+
const member = await membershipLoad(db, "networkMembership", id11);
|
|
4155
4316
|
requireMembershipScope(member, input);
|
|
4156
4317
|
const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
|
|
4157
4318
|
requireMembershipScope(entitlement, input);
|
|
4158
|
-
if (entitlement.membershipId !==
|
|
4319
|
+
if (entitlement.membershipId !== id11 || entitlement.kind !== "primary") membershipFail("membership_entitlement_conflict");
|
|
4159
4320
|
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) {
|
|
4160
4321
|
return {
|
|
4161
|
-
membershipId:
|
|
4322
|
+
membershipId: id11,
|
|
4162
4323
|
status: String(member.status),
|
|
4163
4324
|
cohort: String(member.cohort),
|
|
4164
4325
|
memberNumber: Number(member.memberNumber),
|
|
@@ -4178,7 +4339,7 @@ async function approveNetworkMembership(db, input, now) {
|
|
|
4178
4339
|
const seatActivation = await namedSeatPrimaryActivation(db, member, now);
|
|
4179
4340
|
try {
|
|
4180
4341
|
await db.transact([
|
|
4181
|
-
membershipUpdate("networkMembership",
|
|
4342
|
+
membershipUpdate("networkMembership", id11, {
|
|
4182
4343
|
status: member.autoRenew ? "active" : "cancel_at_term_end",
|
|
4183
4344
|
memberNumber,
|
|
4184
4345
|
journeyApprovedAt: now,
|
|
@@ -4193,8 +4354,8 @@ async function approveNetworkMembership(db, input, now) {
|
|
|
4193
4354
|
updatedAt: now
|
|
4194
4355
|
}),
|
|
4195
4356
|
...seatActivation.ops
|
|
4196
|
-
], { mutationId: `membership-approve:${
|
|
4197
|
-
{ ns: "networkMembership", id:
|
|
4357
|
+
], { mutationId: `membership-approve:${id11}`, guards: [
|
|
4358
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision, status: "paid_pending_approval" }, absent: ["memberNumber"] },
|
|
4198
4359
|
{ ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision, status: "pending_approval" } },
|
|
4199
4360
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
|
|
4200
4361
|
...seatActivation.guards
|
|
@@ -4293,41 +4454,41 @@ async function refundSubscriptionPayment(secretKey, input) {
|
|
|
4293
4454
|
// src/membership-refund-authority.ts
|
|
4294
4455
|
async function refundNetworkMembership(db, input, secretKey, now) {
|
|
4295
4456
|
membershipClock(now);
|
|
4296
|
-
const
|
|
4297
|
-
let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership",
|
|
4457
|
+
const id11 = await networkMembershipId(input), entitlementId = `${id11}:primary`;
|
|
4458
|
+
let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
|
|
4298
4459
|
requireMembershipScope(member, input);
|
|
4299
|
-
if (member.status === "refunded") return { membershipId:
|
|
4460
|
+
if (member.status === "refunded") return { membershipId: id11, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
|
|
4300
4461
|
if (!["paid_pending_approval", "active", "cancel_at_term_end", "payment_grace", "lapsed", "refund_pending"].includes(String(member.status))) membershipFail("membership_refund_not_eligible");
|
|
4301
4462
|
if (member.status !== "refund_pending") {
|
|
4302
4463
|
const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
|
|
4303
4464
|
requireMembershipScope(entitlement, input);
|
|
4304
4465
|
await db.transact([
|
|
4305
|
-
membershipUpdate("networkMembership",
|
|
4466
|
+
membershipUpdate("networkMembership", id11, {
|
|
4306
4467
|
status: "refund_pending",
|
|
4307
4468
|
refundRequestedAt: now,
|
|
4308
4469
|
revision: Number(member.revision) + 1,
|
|
4309
4470
|
updatedAt: now
|
|
4310
4471
|
}),
|
|
4311
4472
|
membershipUpdate("networkEntitlement", entitlementId, { status: "closed", revision: Number(entitlement.revision) + 1, updatedAt: now }),
|
|
4312
|
-
membershipUpdate("membershipEffect", `${
|
|
4313
|
-
id: `${
|
|
4473
|
+
membershipUpdate("membershipEffect", `${id11}:refund`, {
|
|
4474
|
+
id: `${id11}:refund`,
|
|
4314
4475
|
chapterId: input.chapterId,
|
|
4315
4476
|
environment: input.environment,
|
|
4316
4477
|
runtime: input.runtime,
|
|
4317
|
-
membershipId:
|
|
4478
|
+
membershipId: id11,
|
|
4318
4479
|
kind: "refund_membership",
|
|
4319
4480
|
status: "pending",
|
|
4320
4481
|
attempts: 0,
|
|
4321
4482
|
createdAt: now,
|
|
4322
4483
|
updatedAt: now
|
|
4323
4484
|
})
|
|
4324
|
-
], { mutationId: `membership-refund-request:${
|
|
4325
|
-
{ ns: "networkMembership", id:
|
|
4485
|
+
], { mutationId: `membership-refund-request:${id11}`, guards: [
|
|
4486
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
|
|
4326
4487
|
{ ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision } },
|
|
4327
4488
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
|
|
4328
4489
|
] });
|
|
4329
4490
|
}
|
|
4330
|
-
member = await membershipLoad(db, "networkMembership",
|
|
4491
|
+
member = await membershipLoad(db, "networkMembership", id11);
|
|
4331
4492
|
requireMembershipScope(member, input);
|
|
4332
4493
|
if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
|
|
4333
4494
|
const result = await refundSubscriptionPayment(secretKey, {
|
|
@@ -4341,23 +4502,23 @@ async function refundNetworkMembership(db, input, secretKey, now) {
|
|
|
4341
4502
|
if (!result.subscriptionCanceled || result.refundedCents !== member.amountCents) membershipFail("membership_refund_unconfirmed");
|
|
4342
4503
|
for (let attempt = 0; attempt < 8; attempt++) {
|
|
4343
4504
|
state = await loadMembershipAuthorityState(db, input);
|
|
4344
|
-
member = await membershipLoad(db, "networkMembership",
|
|
4505
|
+
member = await membershipLoad(db, "networkMembership", id11);
|
|
4345
4506
|
requireMembershipScope(member, input);
|
|
4346
|
-
if (member.status === "refunded") return { membershipId:
|
|
4507
|
+
if (member.status === "refunded") return { membershipId: id11, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
|
|
4347
4508
|
if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
|
|
4348
4509
|
const counted = member.founding === true && member.memberNumber !== void 0;
|
|
4349
4510
|
const counts = state.cohortCounts;
|
|
4350
4511
|
if (!counts || counted && (!Number.isSafeInteger(counts[String(member.cohort)]) || counts[String(member.cohort)] < 1)) membershipFail("invalid_cohort_count");
|
|
4351
4512
|
try {
|
|
4352
4513
|
await db.transact([
|
|
4353
|
-
membershipUpdate("networkMembership",
|
|
4514
|
+
membershipUpdate("networkMembership", id11, {
|
|
4354
4515
|
status: "refunded",
|
|
4355
4516
|
refundedCents: result.refundedCents,
|
|
4356
4517
|
autoRenew: false,
|
|
4357
4518
|
revision: Number(member.revision) + 1,
|
|
4358
4519
|
updatedAt: now
|
|
4359
4520
|
}),
|
|
4360
|
-
membershipUpdate("membershipEffect", `${
|
|
4521
|
+
membershipUpdate("membershipEffect", `${id11}:refund`, { status: "complete", updatedAt: now }),
|
|
4361
4522
|
...counted ? [membershipUpdate("membershipAuthorityState", String(state.id), {
|
|
4362
4523
|
cohortCounts: { ...counts, [String(member.cohort)]: counts[String(member.cohort)] - 1 },
|
|
4363
4524
|
revision: Number(state.revision) + 1,
|
|
@@ -4368,7 +4529,7 @@ async function refundNetworkMembership(db, input, secretKey, now) {
|
|
|
4368
4529
|
chapterId: input.chapterId,
|
|
4369
4530
|
environment: input.environment,
|
|
4370
4531
|
runtime: input.runtime,
|
|
4371
|
-
membershipId:
|
|
4532
|
+
membershipId: id11,
|
|
4372
4533
|
seatId: member.namedSeatId,
|
|
4373
4534
|
kind: "refund_seat_with_primary",
|
|
4374
4535
|
status: "pending",
|
|
@@ -4376,8 +4537,8 @@ async function refundNetworkMembership(db, input, secretKey, now) {
|
|
|
4376
4537
|
createdAt: now,
|
|
4377
4538
|
updatedAt: now
|
|
4378
4539
|
})] : []
|
|
4379
|
-
], { mutationId: `membership-refunded:${
|
|
4380
|
-
{ ns: "networkMembership", id:
|
|
4540
|
+
], { mutationId: `membership-refunded:${id11}`, guards: [
|
|
4541
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { status: "refund_pending", revision: member.revision } },
|
|
4381
4542
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
4382
4543
|
] });
|
|
4383
4544
|
} catch (error) {
|
|
@@ -4405,8 +4566,8 @@ async function reserveNamedMembershipSeat(db, input, now) {
|
|
|
4405
4566
|
const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
|
|
4406
4567
|
requireMembershipScope(member, input);
|
|
4407
4568
|
if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
|
|
4408
|
-
const
|
|
4409
|
-
let seat = await membershipLoad(db, "networkSeat",
|
|
4569
|
+
const id11 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
4570
|
+
let seat = await membershipLoad(db, "networkSeat", id11);
|
|
4410
4571
|
if (!seat) {
|
|
4411
4572
|
const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
|
|
4412
4573
|
const purchaseEndsAt = seatPurchaseDeadline(offering, member);
|
|
@@ -4414,7 +4575,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
|
|
|
4414
4575
|
membershipFail("named_seat_purchase_unavailable");
|
|
4415
4576
|
}
|
|
4416
4577
|
const attrs = {
|
|
4417
|
-
id:
|
|
4578
|
+
id: id11,
|
|
4418
4579
|
...membershipScope(input),
|
|
4419
4580
|
primaryMembershipId: memberId,
|
|
4420
4581
|
recipientName: name,
|
|
@@ -4439,20 +4600,20 @@ async function reserveNamedMembershipSeat(db, input, now) {
|
|
|
4439
4600
|
};
|
|
4440
4601
|
try {
|
|
4441
4602
|
await db.transact([
|
|
4442
|
-
membershipUpdate("networkSeat",
|
|
4603
|
+
membershipUpdate("networkSeat", id11, attrs),
|
|
4443
4604
|
membershipUpdate("networkSeatRecipient", claimId, {
|
|
4444
4605
|
id: claimId,
|
|
4445
4606
|
appId: input.appId,
|
|
4446
4607
|
chapterId: input.chapterId,
|
|
4447
4608
|
environment: input.environment,
|
|
4448
4609
|
runtime: input.runtime,
|
|
4449
|
-
seatId:
|
|
4610
|
+
seatId: id11,
|
|
4450
4611
|
recipientEmailNormalized: email,
|
|
4451
4612
|
createdAt: now
|
|
4452
4613
|
}),
|
|
4453
|
-
membershipUpdate("networkMembership", memberId, { namedSeatId:
|
|
4454
|
-
], { mutationId: `named-seat-reserve:${
|
|
4455
|
-
{ ns: "networkSeat", id:
|
|
4614
|
+
membershipUpdate("networkMembership", memberId, { namedSeatId: id11, revision: Number(member.revision) + 1, updatedAt: now })
|
|
4615
|
+
], { mutationId: `named-seat-reserve:${id11}`, guards: [
|
|
4616
|
+
{ ns: "networkSeat", id: id11, exists: false },
|
|
4456
4617
|
{ ns: "networkSeatRecipient", id: claimId, exists: false },
|
|
4457
4618
|
{ ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
|
|
4458
4619
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
@@ -4460,7 +4621,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
|
|
|
4460
4621
|
} catch (error) {
|
|
4461
4622
|
if (error.code !== "transact_guard_failed") throw error;
|
|
4462
4623
|
}
|
|
4463
|
-
seat = await membershipLoad(db, "networkSeat",
|
|
4624
|
+
seat = await membershipLoad(db, "networkSeat", id11);
|
|
4464
4625
|
if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
|
|
4465
4626
|
}
|
|
4466
4627
|
requireMembershipScope(seat, input);
|
|
@@ -4470,7 +4631,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
|
|
|
4470
4631
|
|
|
4471
4632
|
// src/membership-seat-included.ts
|
|
4472
4633
|
var namedSeatIncluded = (seat) => Number(seat.amountCents) === 0;
|
|
4473
|
-
var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean(seat.providerPaymentIntentId && seat.providerSubscriptionItemId);
|
|
4634
|
+
var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean((seat.providerPaymentIntentId || seat.combinedInvoice) && seat.providerSubscriptionItemId);
|
|
4474
4635
|
function namedSeatRecipientState(seat) {
|
|
4475
4636
|
switch (String(seat.status)) {
|
|
4476
4637
|
case "renewal_pending":
|
|
@@ -4568,7 +4729,7 @@ async function grantIncludedNamedSeat(db, input, now) {
|
|
|
4568
4729
|
// src/membership-renewal-authority.ts
|
|
4569
4730
|
async function reconcileMembershipRenewal(db, input, now) {
|
|
4570
4731
|
membershipClock(now);
|
|
4571
|
-
const
|
|
4732
|
+
const id11 = await networkMembershipId(input), eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
|
|
4572
4733
|
const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow?.digest, "membership_renewal_evidence_pending");
|
|
4573
4734
|
const fact = eventRow?.fact;
|
|
4574
4735
|
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({
|
|
@@ -4581,10 +4742,10 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
4581
4742
|
return membershipFail("membership_renewal_evidence_pending");
|
|
4582
4743
|
}
|
|
4583
4744
|
for (let attempt = 0; attempt < 8; attempt++) {
|
|
4584
|
-
const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership",
|
|
4745
|
+
const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
|
|
4585
4746
|
requireMembershipScope(member, input);
|
|
4586
4747
|
const receipt = () => ({
|
|
4587
|
-
membershipId:
|
|
4748
|
+
membershipId: id11,
|
|
4588
4749
|
status: String(member.status),
|
|
4589
4750
|
termEndsAt: Number(member.termEndsAt),
|
|
4590
4751
|
graceEndsAt: Number(member.graceEndsAt ?? 0),
|
|
@@ -4609,13 +4770,13 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
4609
4770
|
const paid = fact.eventType === "invoice.payment_succeeded";
|
|
4610
4771
|
if (!paid && (fact.status !== "open" || !Number.isSafeInteger(fact.amountPaidCents) || Number(fact.amountPaidCents) < 0 || Number(fact.amountPaidCents) >= amount)) membershipFail("membership_renewal_failure_mismatch");
|
|
4611
4772
|
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");
|
|
4612
|
-
const entitlement = await membershipLoad(db, "networkEntitlement", `${
|
|
4773
|
+
const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
|
|
4613
4774
|
requireMembershipScope(entitlement, input);
|
|
4614
4775
|
const graceEndsAt = paid ? 0 : start + 14 * 864e5;
|
|
4615
4776
|
const status2 = paid ? member.journeyApprovedAt ? member.autoRenew ? "active" : "cancel_at_term_end" : "paid_pending_approval" : now < graceEndsAt ? "payment_grace" : "lapsed";
|
|
4616
4777
|
const entitlementStatus = member.journeyApprovedAt && (paid || now < graceEndsAt) ? "active" : member.journeyApprovedAt ? "closed" : "pending_approval";
|
|
4617
4778
|
const ops = [
|
|
4618
|
-
membershipUpdate("networkMembership",
|
|
4779
|
+
membershipUpdate("networkMembership", id11, {
|
|
4619
4780
|
status: status2,
|
|
4620
4781
|
graceEndsAt,
|
|
4621
4782
|
...paid ? {
|
|
@@ -4626,7 +4787,7 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
4626
4787
|
revision: Number(member.revision) + 1,
|
|
4627
4788
|
updatedAt: now
|
|
4628
4789
|
}),
|
|
4629
|
-
membershipUpdate("networkEntitlement", `${
|
|
4790
|
+
membershipUpdate("networkEntitlement", `${id11}:primary`, {
|
|
4630
4791
|
status: entitlementStatus,
|
|
4631
4792
|
...paid ? { termStartedAt: start, termEndsAt: end } : {},
|
|
4632
4793
|
sourceEventId: input.eventId,
|
|
@@ -4635,8 +4796,8 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
4635
4796
|
})
|
|
4636
4797
|
];
|
|
4637
4798
|
const guards = [
|
|
4638
|
-
{ ns: "networkMembership", id:
|
|
4639
|
-
{ ns: "networkEntitlement", id: `${
|
|
4799
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
|
|
4800
|
+
{ ns: "networkEntitlement", id: `${id11}:primary`, exists: true, equals: { revision: entitlement.revision } },
|
|
4640
4801
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
|
|
4641
4802
|
{ ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
|
|
4642
4803
|
evidenceGuard
|
|
@@ -4661,15 +4822,15 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
4661
4822
|
);
|
|
4662
4823
|
}
|
|
4663
4824
|
try {
|
|
4664
|
-
await db.transact(ops, { mutationId: `membership-renewal:${
|
|
4825
|
+
await db.transact(ops, { mutationId: `membership-renewal:${id11}:${input.eventId}`, guards });
|
|
4665
4826
|
} catch (error) {
|
|
4666
4827
|
if (error.code !== "transact_guard_failed") throw error;
|
|
4667
4828
|
continue;
|
|
4668
4829
|
}
|
|
4669
|
-
const saved = await membershipLoad(db, "networkMembership",
|
|
4830
|
+
const saved = await membershipLoad(db, "networkMembership", id11);
|
|
4670
4831
|
requireMembershipScope(saved, input);
|
|
4671
4832
|
return {
|
|
4672
|
-
membershipId:
|
|
4833
|
+
membershipId: id11,
|
|
4673
4834
|
status: String(saved.status),
|
|
4674
4835
|
termEndsAt: Number(saved.termEndsAt),
|
|
4675
4836
|
graceEndsAt: Number(saved.graceEndsAt ?? 0),
|
|
@@ -4695,12 +4856,12 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
|
|
|
4695
4856
|
}).some(([key3, value]) => fact.identity[key3] !== value)) membershipFail("membership_billing_evidence_pending");
|
|
4696
4857
|
const sk = await getVaultSecret(db, stripeSecretName);
|
|
4697
4858
|
if (!sk) membershipFail("membership_billing_provider_unavailable");
|
|
4698
|
-
const
|
|
4859
|
+
const id11 = await networkMembershipId(input);
|
|
4699
4860
|
for (let attempt = 0; attempt < 8; attempt++) {
|
|
4700
|
-
const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership",
|
|
4861
|
+
const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
|
|
4701
4862
|
requireMembershipScope(member, input);
|
|
4702
4863
|
const receipt = (value = member) => ({
|
|
4703
|
-
membershipId:
|
|
4864
|
+
membershipId: id11,
|
|
4704
4865
|
status: String(value.status),
|
|
4705
4866
|
termEndsAt: Number(value.termEndsAt),
|
|
4706
4867
|
graceEndsAt: Number(value.graceEndsAt ?? 0),
|
|
@@ -4721,7 +4882,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
|
|
|
4721
4882
|
});
|
|
4722
4883
|
await commercialEvidenceGuard(db, input.eventId, row2?.digest, "membership_billing_evidence_pending");
|
|
4723
4884
|
await refundNetworkMembership(db, input, sk, now);
|
|
4724
|
-
const saved2 = await membershipLoad(db, "networkMembership",
|
|
4885
|
+
const saved2 = await membershipLoad(db, "networkMembership", id11);
|
|
4725
4886
|
requireMembershipScope(saved2, input);
|
|
4726
4887
|
return receipt(saved2);
|
|
4727
4888
|
}
|
|
@@ -4736,18 +4897,18 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
|
|
|
4736
4897
|
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";
|
|
4737
4898
|
const graceEndsAt = autoRenew ? Number(member.graceEndsAt ?? 0) : 0;
|
|
4738
4899
|
if (member.autoRenew === autoRenew && member.status === status2 && Number(member.graceEndsAt ?? 0) === graceEndsAt) return receipt();
|
|
4739
|
-
const entitlement = await membershipLoad(db, "networkEntitlement", `${
|
|
4900
|
+
const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
|
|
4740
4901
|
requireMembershipScope(entitlement, input);
|
|
4741
4902
|
try {
|
|
4742
4903
|
await db.transact([
|
|
4743
|
-
membershipUpdate("networkMembership",
|
|
4904
|
+
membershipUpdate("networkMembership", id11, { status: status2, autoRenew, graceEndsAt, revision: Number(member.revision) + 1, updatedAt: now }),
|
|
4744
4905
|
membershipUpdate("networkEntitlement", String(entitlement.id), {
|
|
4745
4906
|
status: status2 === "lapsed" ? "closed" : entitlement.status,
|
|
4746
4907
|
revision: Number(entitlement.revision) + 1,
|
|
4747
4908
|
updatedAt: now
|
|
4748
4909
|
})
|
|
4749
4910
|
], { guards: [
|
|
4750
|
-
{ ns: "networkMembership", id:
|
|
4911
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
|
|
4751
4912
|
{ ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision } },
|
|
4752
4913
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
|
|
4753
4914
|
{ ns: "networkCommercialEvents", id: String(row2.id), exists: true, equals: { digest: fact.digest } },
|
|
@@ -4757,7 +4918,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
|
|
|
4757
4918
|
if (error.code !== "transact_guard_failed") throw error;
|
|
4758
4919
|
continue;
|
|
4759
4920
|
}
|
|
4760
|
-
const saved = await membershipLoad(db, "networkMembership",
|
|
4921
|
+
const saved = await membershipLoad(db, "networkMembership", id11);
|
|
4761
4922
|
requireMembershipScope(saved, input);
|
|
4762
4923
|
return receipt(saved);
|
|
4763
4924
|
}
|
|
@@ -4818,19 +4979,19 @@ async function stopPreviousSubscription(member, target, secret) {
|
|
|
4818
4979
|
async function prepareMembershipRestart(db, input, now, stripeSecret) {
|
|
4819
4980
|
membershipClock(now);
|
|
4820
4981
|
if (!/^sub_[A-Za-z0-9_]+$/.test(input.previousSubscriptionId)) membershipFail("membership_restart_conflict");
|
|
4821
|
-
const
|
|
4822
|
-
const restartId = `restart_${await stripePayloadDigest(JSON.stringify([
|
|
4982
|
+
const id11 = await networkMembershipId(input), bindingId = await membershipCheckoutBindingId(input);
|
|
4983
|
+
const restartId = `restart_${await stripePayloadDigest(JSON.stringify([id11, input.previousSubscriptionId]))}`;
|
|
4823
4984
|
const state = await loadMembershipAuthorityState(db, input);
|
|
4824
|
-
const previous = await membershipLoad(db, "networkMembership",
|
|
4985
|
+
const previous = await membershipLoad(db, "networkMembership", id11);
|
|
4825
4986
|
requireMembershipScope(previous, input);
|
|
4826
4987
|
const receipt = {
|
|
4827
4988
|
applicationId: input.applicationId,
|
|
4828
|
-
membershipId:
|
|
4989
|
+
membershipId: id11,
|
|
4829
4990
|
restartId,
|
|
4830
4991
|
memberNumber: Number(previous.memberNumber),
|
|
4831
4992
|
status: "ready"
|
|
4832
4993
|
};
|
|
4833
|
-
const existing = await membershipLoad(db, "membershipRestart",
|
|
4994
|
+
const existing = await membershipLoad(db, "membershipRestart", id11);
|
|
4834
4995
|
if (existing?.restartId === restartId && existing.status === "ready") {
|
|
4835
4996
|
requireMembershipScope(existing, input);
|
|
4836
4997
|
return receipt;
|
|
@@ -4846,33 +5007,33 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
|
|
|
4846
5007
|
membershipUpdate("membershipPurchaseHistory", restartId, {
|
|
4847
5008
|
id: restartId,
|
|
4848
5009
|
...membershipScope(input),
|
|
4849
|
-
membershipId:
|
|
5010
|
+
membershipId: id11,
|
|
4850
5011
|
providerSubscriptionId: input.previousSubscriptionId,
|
|
4851
5012
|
memberSnapshot: previous,
|
|
4852
5013
|
bindingSnapshot: binding2,
|
|
4853
5014
|
archivedAt: now
|
|
4854
5015
|
}),
|
|
4855
|
-
membershipUpdate("networkMembership",
|
|
5016
|
+
membershipUpdate("networkMembership", id11, { status: "restarting", restartId, revision: Number(previous.revision) + 1, updatedAt: now })
|
|
4856
5017
|
], { mutationId: `membership-restart-reserve:${restartId}`, guards: [
|
|
4857
5018
|
...seats.guards,
|
|
4858
|
-
{ ns: "networkMembership", id:
|
|
5019
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: previous.revision, status: previous.status } },
|
|
4859
5020
|
{ ns: "membershipPurchaseHistory", id: restartId, exists: false },
|
|
4860
5021
|
{ ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding2.offerDigest } },
|
|
4861
5022
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
4862
5023
|
] });
|
|
4863
5024
|
} else if (previous.restartId !== restartId) membershipFail("membership_restart_conflict");
|
|
4864
|
-
const reserved = await membershipLoad(db, "networkMembership",
|
|
5025
|
+
const reserved = await membershipLoad(db, "networkMembership", id11);
|
|
4865
5026
|
requireMembershipScope(reserved, input);
|
|
4866
5027
|
if (reserved.status !== "restarting" || reserved.restartId !== restartId) membershipFail("membership_restart_conflict");
|
|
4867
5028
|
await stopPreviousSubscription(reserved, input, stripeSecret);
|
|
4868
5029
|
const binding = await membershipLoad(db, "membershipOfferCheckout", bindingId);
|
|
4869
5030
|
requireMembershipScope(binding, input);
|
|
4870
5031
|
await db.transact([
|
|
4871
|
-
membershipUpdate("membershipRestart",
|
|
5032
|
+
membershipUpdate("membershipRestart", id11, { id: id11, ...membershipScope(input), ...receipt, previousSubscriptionId: input.previousSubscriptionId, createdAt: now }),
|
|
4872
5033
|
{ t: "delete", ns: "membershipOfferCheckout", id: bindingId }
|
|
4873
5034
|
], { mutationId: `membership-restart-ready:${restartId}`, guards: [
|
|
4874
|
-
{ ns: "membershipRestart", id:
|
|
4875
|
-
{ ns: "networkMembership", id:
|
|
5035
|
+
{ ns: "membershipRestart", id: id11, exists: Boolean(existing), ...existing ? { equals: { restartId: existing.restartId, status: existing.status } } : {} },
|
|
5036
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: reserved.revision, status: "restarting", restartId } },
|
|
4876
5037
|
{ ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding.offerDigest } }
|
|
4877
5038
|
] });
|
|
4878
5039
|
return receipt;
|
|
@@ -4882,15 +5043,15 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
|
|
|
4882
5043
|
async function readMembershipAccess(db, input, now) {
|
|
4883
5044
|
membershipClock(now);
|
|
4884
5045
|
await loadMembershipAuthorityState(db, input);
|
|
4885
|
-
const
|
|
4886
|
-
if (!member) return { membershipId:
|
|
5046
|
+
const id11 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id11);
|
|
5047
|
+
if (!member) return { membershipId: id11, allowed: false, status: "unpaid", accessEndsAt: 0, revision: 0 };
|
|
4887
5048
|
requireMembershipScope(member, input);
|
|
4888
|
-
const entitlement = await membershipLoad(db, "networkEntitlement", `${
|
|
5049
|
+
const entitlement = await membershipLoad(db, "networkEntitlement", `${id11}:primary`);
|
|
4889
5050
|
requireMembershipScope(entitlement, input);
|
|
4890
5051
|
const endsAt = member.status === "payment_grace" ? Number(member.graceEndsAt) : Number(member.termEndsAt);
|
|
4891
|
-
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 ===
|
|
5052
|
+
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;
|
|
4892
5053
|
return {
|
|
4893
|
-
membershipId:
|
|
5054
|
+
membershipId: id11,
|
|
4894
5055
|
allowed: allowed2,
|
|
4895
5056
|
status: String(member.status),
|
|
4896
5057
|
accessEndsAt: Number.isSafeInteger(endsAt) ? endsAt : 0,
|
|
@@ -4945,7 +5106,7 @@ async function subscriptionForSeat(sk, seat) {
|
|
|
4945
5106
|
currency: seat.currency,
|
|
4946
5107
|
interval: seat.interval,
|
|
4947
5108
|
environment: seat.environment
|
|
4948
|
-
}, Boolean(seat.providerPaymentIntentId));
|
|
5109
|
+
}, Boolean(seat.providerPaymentIntentId || seat.combinedInvoice));
|
|
4949
5110
|
if (!productId) reject3();
|
|
4950
5111
|
const sub = await read(sk, `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}?expand[]=discounts`);
|
|
4951
5112
|
const items = sub.items?.data;
|
|
@@ -4989,10 +5150,14 @@ async function createNamedSeatStripePayment(sk, seat, now) {
|
|
|
4989
5150
|
return { paymentIntentId: pi.id, clientSecret: pi.client_secret, status: pi.status };
|
|
4990
5151
|
}
|
|
4991
5152
|
async function attachNamedSeatStripeRenewal(sk, seat) {
|
|
4992
|
-
if (!seat.providerPaymentIntentId || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject3();
|
|
5153
|
+
if (!seat.providerPaymentIntentId && !seat.combinedInvoice || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject3();
|
|
4993
5154
|
const sub = await subscriptionForSeat(sk, seat);
|
|
4994
5155
|
const existing = sub.items.data.find((item2) => item2.metadata?.seatId === seat.id);
|
|
4995
|
-
if (existing)
|
|
5156
|
+
if (existing) {
|
|
5157
|
+
if (!/^si_[A-Za-z0-9_]+$/.test(existing.id)) reject3();
|
|
5158
|
+
return String(existing.id);
|
|
5159
|
+
}
|
|
5160
|
+
if (seat.combinedInvoice) reject3();
|
|
4996
5161
|
const result = await (0, import_stripe7.stripeRequest)({ secretKey: sk }, "POST", "/v1/subscription_items", {
|
|
4997
5162
|
subscription: seat.providerSubscriptionId,
|
|
4998
5163
|
price: seat.priceId,
|
|
@@ -5210,7 +5375,7 @@ async function recordRecipient(db, input, now, approve) {
|
|
|
5210
5375
|
const state = await loadMembershipAuthorityState(db, input);
|
|
5211
5376
|
const seat = await membershipLoad(db, "networkSeat", input.seatId);
|
|
5212
5377
|
requireMembershipScope(seat, input);
|
|
5213
|
-
if (!seat.purchaserUserId || seat.purchaserUserId === input.recipientUserId) membershipFail("named_seat_recipient_must_differ_from_purchaser");
|
|
5378
|
+
if (!seat.purchaserUserId && !seat.purchaserEmailNormalized || seat.purchaserUserId === input.recipientUserId || seat.purchaserEmailNormalized === normalizeChapterEmail(input.verifiedRecipientEmail)) membershipFail("named_seat_recipient_must_differ_from_purchaser");
|
|
5214
5379
|
const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
|
|
5215
5380
|
requireMembershipScope(member, input);
|
|
5216
5381
|
const entitlement = await membershipLoad(db, "networkEntitlement", String(seat.entitlementId));
|
|
@@ -5268,43 +5433,121 @@ async function recordRecipient(db, input, now, approve) {
|
|
|
5268
5433
|
}
|
|
5269
5434
|
|
|
5270
5435
|
// src/membership-seat-refund-stripe.ts
|
|
5436
|
+
var import_stripe9 = require("@odla-ai/stripe");
|
|
5437
|
+
|
|
5438
|
+
// src/membership-checkout-seat-refund.ts
|
|
5271
5439
|
var import_stripe8 = require("@odla-ai/stripe");
|
|
5272
5440
|
var fail4 = (code = "named_seat_refund_mismatch") => {
|
|
5273
5441
|
throw Object.assign(new Error(code), { code });
|
|
5274
5442
|
};
|
|
5443
|
+
var id8 = (value) => typeof value === "string" ? value : value?.id ?? "";
|
|
5444
|
+
async function refundCheckoutNamedSeat(secretKey, seat) {
|
|
5445
|
+
const invoiceRef = seat.combinedInvoice;
|
|
5446
|
+
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();
|
|
5447
|
+
const call = async (method, path, params, key3) => {
|
|
5448
|
+
const result = await (0, import_stripe8.stripeRequest)({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
|
|
5449
|
+
if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
|
|
5450
|
+
return result.body;
|
|
5451
|
+
};
|
|
5452
|
+
const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId };
|
|
5453
|
+
const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
|
|
5454
|
+
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();
|
|
5455
|
+
const invoice = await call("GET", `/v1/invoices/${encodeURIComponent(invoiceRef.invoiceId)}`);
|
|
5456
|
+
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();
|
|
5457
|
+
let chargeId = id8(invoice.charge), paymentId = id8(invoice.payment_intent);
|
|
5458
|
+
if (!chargeId && !paymentId) {
|
|
5459
|
+
const payments = await call("GET", "/v1/invoice_payments", { invoice: invoice.id, status: "paid", limit: 100 });
|
|
5460
|
+
if (!Array.isArray(payments.data) || payments.data.length !== 1 || payments.has_more !== false) fail4();
|
|
5461
|
+
const payment = payments.data[0];
|
|
5462
|
+
if (id8(payment.invoice) !== invoice.id || payment.status !== "paid" || payment.payment?.type !== "payment_intent") fail4();
|
|
5463
|
+
paymentId = id8(payment.payment.payment_intent);
|
|
5464
|
+
}
|
|
5465
|
+
if (!chargeId) {
|
|
5466
|
+
if (!/^pi_[A-Za-z0-9_]+$/.test(paymentId)) fail4();
|
|
5467
|
+
const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(paymentId)}`);
|
|
5468
|
+
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();
|
|
5469
|
+
chargeId = id8(payment.latest_charge);
|
|
5470
|
+
}
|
|
5471
|
+
if (!/^ch_[A-Za-z0-9_]+$/.test(chargeId)) fail4();
|
|
5472
|
+
const charge = await call("GET", `/v1/charges/${encodeURIComponent(chargeId)}`);
|
|
5473
|
+
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();
|
|
5474
|
+
const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
|
|
5475
|
+
if (items.length > 1) fail4();
|
|
5476
|
+
if (items.length && sub.status !== "canceled") {
|
|
5477
|
+
const item = items[0];
|
|
5478
|
+
if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.quantity !== 1 || item.price?.id !== seat.priceId) fail4();
|
|
5479
|
+
const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
|
|
5480
|
+
if (removed.id !== item.id || removed.deleted !== true) fail4("named_seat_renewal_removal_unconfirmed");
|
|
5481
|
+
}
|
|
5482
|
+
if (charge.amount_refunded === charge.amount && charge.refunded === true) return { refundedCents: seat.amountCents, refundIds: [] };
|
|
5483
|
+
const list = await call("GET", "/v1/refunds", { charge: chargeId, limit: 100 });
|
|
5484
|
+
if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail4("named_seat_refund_history_incomplete");
|
|
5485
|
+
let refundedCents = 0;
|
|
5486
|
+
const refundIds = [];
|
|
5487
|
+
for (const refund of list.data) {
|
|
5488
|
+
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();
|
|
5489
|
+
if (["pending", "requires_action"].includes(refund.status)) fail4("named_seat_refund_pending");
|
|
5490
|
+
if (refund.status === "succeeded") {
|
|
5491
|
+
if (refund.metadata?.seatId !== seat.id || refund.metadata?.membershipId !== seat.primaryMembershipId) fail4("named_seat_refund_allocation_ambiguous");
|
|
5492
|
+
refundedCents += refund.amount;
|
|
5493
|
+
refundIds.push(refund.id);
|
|
5494
|
+
}
|
|
5495
|
+
}
|
|
5496
|
+
if (refundedCents !== charge.amount_refunded || refundedCents > seat.amountCents) fail4();
|
|
5497
|
+
if (refundedCents < seat.amountCents) {
|
|
5498
|
+
const remaining = seat.amountCents - refundedCents;
|
|
5499
|
+
const refund = await call("POST", "/v1/refunds", {
|
|
5500
|
+
charge: chargeId,
|
|
5501
|
+
amount: remaining,
|
|
5502
|
+
reason: "requested_by_customer",
|
|
5503
|
+
metadata: { ...metadata, membershipId: seat.primaryMembershipId, seatId: seat.id }
|
|
5504
|
+
}, `combined-seat-refund:${seat.id}:${remaining}`);
|
|
5505
|
+
if (id8(refund.charge) !== chargeId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail4();
|
|
5506
|
+
if (refund.status !== "succeeded") fail4("named_seat_refund_pending");
|
|
5507
|
+
refundedCents += remaining;
|
|
5508
|
+
refundIds.push(refund.id);
|
|
5509
|
+
}
|
|
5510
|
+
return { refundedCents, refundIds };
|
|
5511
|
+
}
|
|
5512
|
+
|
|
5513
|
+
// src/membership-seat-refund-stripe.ts
|
|
5514
|
+
var fail5 = (code = "named_seat_refund_mismatch") => {
|
|
5515
|
+
throw Object.assign(new Error(code), { code });
|
|
5516
|
+
};
|
|
5275
5517
|
async function refundNamedSeatStripePayment(secretKey, seat) {
|
|
5276
|
-
if (
|
|
5518
|
+
if (seat.combinedInvoice) return refundCheckoutNamedSeat(secretKey, seat);
|
|
5519
|
+
if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail5();
|
|
5277
5520
|
const call = async (method, path, params, idempotencyKey) => {
|
|
5278
|
-
const result = await (0,
|
|
5279
|
-
if (!result.ok || !result.body) return
|
|
5521
|
+
const result = await (0, import_stripe9.stripeRequest)({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
|
|
5522
|
+
if (!result.ok || !result.body) return fail5("named_seat_refund_provider_unavailable");
|
|
5280
5523
|
return result.body;
|
|
5281
5524
|
};
|
|
5282
5525
|
const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
|
|
5283
5526
|
const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`);
|
|
5284
|
-
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))
|
|
5527
|
+
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();
|
|
5285
5528
|
const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
|
|
5286
|
-
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))
|
|
5529
|
+
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();
|
|
5287
5530
|
const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
|
|
5288
|
-
if (items.length > 1)
|
|
5531
|
+
if (items.length > 1) fail5();
|
|
5289
5532
|
if (items.length && sub.status !== "canceled") {
|
|
5290
5533
|
const item = items[0];
|
|
5291
|
-
if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1)
|
|
5534
|
+
if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail5();
|
|
5292
5535
|
const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
|
|
5293
|
-
if (removed.id !== item.id || removed.deleted !== true)
|
|
5536
|
+
if (removed.id !== item.id || removed.deleted !== true) fail5("named_seat_renewal_removal_unconfirmed");
|
|
5294
5537
|
}
|
|
5295
5538
|
const list = await call("GET", "/v1/refunds", { payment_intent: seat.providerPaymentIntentId, limit: 100 });
|
|
5296
|
-
if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100)
|
|
5539
|
+
if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail5("named_seat_refund_history_incomplete");
|
|
5297
5540
|
let refundedCents = 0, pendingCents = 0;
|
|
5298
5541
|
const refundIds = [];
|
|
5299
5542
|
for (const refund of list.data) {
|
|
5300
|
-
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))
|
|
5543
|
+
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();
|
|
5301
5544
|
if (refund.status === "succeeded") {
|
|
5302
5545
|
refundedCents += refund.amount;
|
|
5303
5546
|
refundIds.push(refund.id);
|
|
5304
5547
|
} else if (refund.status === "pending" || refund.status === "requires_action") pendingCents += refund.amount;
|
|
5305
5548
|
}
|
|
5306
|
-
if (refundedCents + pendingCents > seat.amountCents)
|
|
5307
|
-
if (pendingCents)
|
|
5549
|
+
if (refundedCents + pendingCents > seat.amountCents) fail5();
|
|
5550
|
+
if (pendingCents) fail5("named_seat_refund_pending");
|
|
5308
5551
|
if (refundedCents < seat.amountCents) {
|
|
5309
5552
|
const remaining = seat.amountCents - refundedCents;
|
|
5310
5553
|
const refund = await call("POST", "/v1/refunds", {
|
|
@@ -5313,8 +5556,8 @@ async function refundNamedSeatStripePayment(secretKey, seat) {
|
|
|
5313
5556
|
reason: "requested_by_customer",
|
|
5314
5557
|
metadata
|
|
5315
5558
|
}, `named-seat-refund:${seat.id}:${remaining}`);
|
|
5316
|
-
if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id))
|
|
5317
|
-
if (refund.status !== "succeeded")
|
|
5559
|
+
if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail5();
|
|
5560
|
+
if (refund.status !== "succeeded") fail5("named_seat_refund_pending");
|
|
5318
5561
|
refundedCents += remaining;
|
|
5319
5562
|
refundIds.push(refund.id);
|
|
5320
5563
|
}
|
|
@@ -5330,7 +5573,7 @@ async function requestNamedSeatRefund(db, input, now) {
|
|
|
5330
5573
|
const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
|
|
5331
5574
|
requireMembershipScope(member, input);
|
|
5332
5575
|
if (seat.status === "refunded" || seat.status === "refund_pending") return;
|
|
5333
|
-
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");
|
|
5576
|
+
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");
|
|
5334
5577
|
const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
|
|
5335
5578
|
await db.transact([
|
|
5336
5579
|
membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
|
|
@@ -5435,14 +5678,14 @@ var at = (value) => Number.isSafeInteger(value) ? Number(value) : void 0;
|
|
|
5435
5678
|
async function readNamedSeatOffer(db, input, now) {
|
|
5436
5679
|
membershipClock(now);
|
|
5437
5680
|
await loadMembershipAuthorityState(db, input);
|
|
5438
|
-
const
|
|
5681
|
+
const id11 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id11);
|
|
5439
5682
|
requireMembershipScope(member, input);
|
|
5440
5683
|
const offering = membershipOfferingVersion(member.offeringSnapshot);
|
|
5441
5684
|
const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
|
|
5442
5685
|
if (seat) requireMembershipScope(seat, input);
|
|
5443
5686
|
const purchaseEndsAt = seatPurchaseDeadline(offering, member);
|
|
5444
5687
|
const offer = {
|
|
5445
|
-
membershipId:
|
|
5688
|
+
membershipId: id11,
|
|
5446
5689
|
amountCents: offering.seatPriceCents,
|
|
5447
5690
|
currency: offering.currency,
|
|
5448
5691
|
interval: offering.interval,
|
|
@@ -5720,9 +5963,14 @@ function createMembershipAuthorityHandler(options) {
|
|
|
5720
5963
|
try {
|
|
5721
5964
|
input = JSON.parse(verified.body);
|
|
5722
5965
|
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] ?? []];
|
|
5723
|
-
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])))) {
|
|
5966
|
+
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])))) {
|
|
5724
5967
|
throw new TypeError("invalid request");
|
|
5725
5968
|
}
|
|
5969
|
+
if (action === "checkouts" && input.namedSeat !== void 0) {
|
|
5970
|
+
const seat = input.namedSeat;
|
|
5971
|
+
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");
|
|
5972
|
+
checkoutNamedSeatSelection(seat, seat.purchaserEmail);
|
|
5973
|
+
}
|
|
5726
5974
|
} catch {
|
|
5727
5975
|
return respond({ error: "invalid_offer_request" }, 400);
|
|
5728
5976
|
}
|
|
@@ -5795,8 +6043,9 @@ function createMembershipAuthorityHandler(options) {
|
|
|
5795
6043
|
offerId: String(input.offerId),
|
|
5796
6044
|
offerDigest: String(input.offerDigest),
|
|
5797
6045
|
quoteDigest: String(input.quoteDigest),
|
|
5798
|
-
priceId: String(input.priceId)
|
|
5799
|
-
|
|
6046
|
+
priceId: String(input.priceId),
|
|
6047
|
+
...input.namedSeat ? { namedSeat: input.namedSeat } : {}
|
|
6048
|
+
}, options.now?.() ?? Date.now(), peer.seatPriceId);
|
|
5800
6049
|
return respond({ ok: true, binding });
|
|
5801
6050
|
}
|
|
5802
6051
|
const offerId = await membershipOfferIdForRequest({ ...peer, applicationId: String(input.applicationId), requestId: String(input.requestId) });
|
|
@@ -5899,7 +6148,8 @@ async function requestMembershipCheckoutBinding(options) {
|
|
|
5899
6148
|
offerId: options.offerId,
|
|
5900
6149
|
offerDigest: options.offerDigest,
|
|
5901
6150
|
quoteDigest: options.quoteDigest,
|
|
5902
|
-
priceId: options.priceId
|
|
6151
|
+
priceId: options.priceId,
|
|
6152
|
+
...options.namedSeat ? { namedSeat: options.namedSeat } : {}
|
|
5903
6153
|
});
|
|
5904
6154
|
return verifyMembershipCheckoutBinding(result.binding, options);
|
|
5905
6155
|
}
|
|
@@ -6109,7 +6359,7 @@ async function requestMembershipCancellation(options) {
|
|
|
6109
6359
|
}
|
|
6110
6360
|
|
|
6111
6361
|
// src/membership-seat-cancel.ts
|
|
6112
|
-
var
|
|
6362
|
+
var import_stripe10 = require("@odla-ai/stripe");
|
|
6113
6363
|
async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
|
|
6114
6364
|
membershipClock(now);
|
|
6115
6365
|
const state = await loadMembershipAuthorityState(db, target);
|
|
@@ -6137,7 +6387,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
|
|
|
6137
6387
|
}
|
|
6138
6388
|
const metadata = { ...stripePartitionMetadata(target), applicationId: target.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
|
|
6139
6389
|
const request = async (method, path, params) => {
|
|
6140
|
-
const result = await (0,
|
|
6390
|
+
const result = await (0, import_stripe10.stripeRequest)(
|
|
6141
6391
|
{ secretKey },
|
|
6142
6392
|
method,
|
|
6143
6393
|
path,
|
|
@@ -6150,7 +6400,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
|
|
|
6150
6400
|
let payment;
|
|
6151
6401
|
if (seat.providerPaymentIntentId) payment = await request("GET", `/v1/payment_intents/${encodeURIComponent(String(seat.providerPaymentIntentId))}`);
|
|
6152
6402
|
else {
|
|
6153
|
-
const snapshot = await listStripeObjects((0,
|
|
6403
|
+
const snapshot = await listStripeObjects((0, import_stripe10.initStripe)({ secretKey }), "/v1/payment_intents", { customer: String(seat.providerCustomerId) });
|
|
6154
6404
|
const matches2 = snapshot.objects.filter((row2) => row2.metadata?.seatId === target.seatId);
|
|
6155
6405
|
if (!snapshot.complete || matches2.length !== 1) throw new Error("seat payment creation outcome unresolved");
|
|
6156
6406
|
payment = matches2[0];
|
|
@@ -6163,9 +6413,9 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
|
|
|
6163
6413
|
if (payment.status === "succeeded" || payment.amount_received > 0) throw new Error("paid seat requires payment evidence and full refund");
|
|
6164
6414
|
if (payment.status !== "canceled") {
|
|
6165
6415
|
if (!["requires_payment_method", "requires_confirmation", "requires_action", "processing"].includes(payment.status)) throw new Error("seat payment outcome unresolved");
|
|
6166
|
-
const
|
|
6167
|
-
payment = await request("POST", `/v1/payment_intents/${encodeURIComponent(
|
|
6168
|
-
if (payment.id !==
|
|
6416
|
+
const id11 = payment.id;
|
|
6417
|
+
payment = await request("POST", `/v1/payment_intents/${encodeURIComponent(id11)}/cancel`, { cancellation_reason: "abandoned" });
|
|
6418
|
+
if (payment.id !== id11) throw new Error("seat cancellation receipt mismatch");
|
|
6169
6419
|
}
|
|
6170
6420
|
if (!valid(payment) || payment.status !== "canceled" || payment.amount_received !== 0) throw new Error("seat cancellation unconfirmed");
|
|
6171
6421
|
await db.transact([membershipUpdate("networkSeat", target.seatId, { status: "canceled", revision: Number(seat.revision) + 1, updatedAt: now })], {
|
|
@@ -6265,11 +6515,11 @@ async function drainMembershipEffects(db, options, now) {
|
|
|
6265
6515
|
|
|
6266
6516
|
// src/schema-membership-offers.ts
|
|
6267
6517
|
var attr8 = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
|
|
6268
|
-
var
|
|
6518
|
+
var id9 = () => attr8("string", false, true, true);
|
|
6269
6519
|
var key = () => attr8("string", false, false, true);
|
|
6270
6520
|
var membershipOfferEntities = {
|
|
6271
6521
|
membershipAuthorityState: { attrs: {
|
|
6272
|
-
id:
|
|
6522
|
+
id: id9(),
|
|
6273
6523
|
chapterId: key(),
|
|
6274
6524
|
environment: key(),
|
|
6275
6525
|
mode: key(),
|
|
@@ -6279,7 +6529,7 @@ var membershipOfferEntities = {
|
|
|
6279
6529
|
updatedAt: attr8("number")
|
|
6280
6530
|
} },
|
|
6281
6531
|
membershipCheckoutOffer: { attrs: {
|
|
6282
|
-
id:
|
|
6532
|
+
id: id9(),
|
|
6283
6533
|
applicationId: key(),
|
|
6284
6534
|
appId: key(),
|
|
6285
6535
|
chapterId: key(),
|
|
@@ -6294,7 +6544,7 @@ var membershipOfferEntities = {
|
|
|
6294
6544
|
authorityRevision: attr8("number")
|
|
6295
6545
|
} },
|
|
6296
6546
|
membershipOfferCheckout: { attrs: {
|
|
6297
|
-
id:
|
|
6547
|
+
id: id9(),
|
|
6298
6548
|
appId: key(),
|
|
6299
6549
|
chapterId: key(),
|
|
6300
6550
|
environment: key(),
|
|
@@ -6305,20 +6555,21 @@ var membershipOfferEntities = {
|
|
|
6305
6555
|
offerDigest: attr8("string"),
|
|
6306
6556
|
quoteDigest: attr8("string"),
|
|
6307
6557
|
priceId: attr8("string"),
|
|
6308
|
-
boundAt: attr8("number")
|
|
6558
|
+
boundAt: attr8("number"),
|
|
6559
|
+
namedSeat: attr8("json", true)
|
|
6309
6560
|
} }
|
|
6310
6561
|
};
|
|
6311
6562
|
var membershipOfferRules = Object.fromEntries(Object.keys(membershipOfferEntities).map((ns) => [ns, { view: "false", create: "false", update: "false", delete: "false" }]));
|
|
6312
6563
|
|
|
6313
6564
|
// src/schema-membership-authority.ts
|
|
6314
6565
|
var attr9 = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
|
|
6315
|
-
var
|
|
6566
|
+
var id10 = () => attr9("string", false, true, true);
|
|
6316
6567
|
var key2 = (optional = false) => attr9("string", optional, false, true);
|
|
6317
6568
|
var scope = () => ({ appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), applicationId: key2(), skuId: key2() });
|
|
6318
6569
|
var times = () => ({ revision: attr9("number"), createdAt: attr9("number"), updatedAt: attr9("number") });
|
|
6319
6570
|
var membershipAuthorityEntities = {
|
|
6320
6571
|
networkMembership: { attrs: {
|
|
6321
|
-
id:
|
|
6572
|
+
id: id10(),
|
|
6322
6573
|
membershipId: key2(),
|
|
6323
6574
|
...scope(),
|
|
6324
6575
|
personId: key2(true),
|
|
@@ -6358,7 +6609,7 @@ var membershipAuthorityEntities = {
|
|
|
6358
6609
|
restartId: key2(true)
|
|
6359
6610
|
} },
|
|
6360
6611
|
membershipRestart: { attrs: {
|
|
6361
|
-
id:
|
|
6612
|
+
id: id10(),
|
|
6362
6613
|
...scope(),
|
|
6363
6614
|
membershipId: key2(),
|
|
6364
6615
|
restartId: key2(),
|
|
@@ -6368,7 +6619,7 @@ var membershipAuthorityEntities = {
|
|
|
6368
6619
|
createdAt: attr9("number")
|
|
6369
6620
|
} },
|
|
6370
6621
|
membershipPurchaseHistory: { attrs: {
|
|
6371
|
-
id:
|
|
6622
|
+
id: id10(),
|
|
6372
6623
|
...scope(),
|
|
6373
6624
|
membershipId: key2(),
|
|
6374
6625
|
providerSubscriptionId: key2(),
|
|
@@ -6377,7 +6628,7 @@ var membershipAuthorityEntities = {
|
|
|
6377
6628
|
archivedAt: attr9("number")
|
|
6378
6629
|
} },
|
|
6379
6630
|
networkEntitlement: { attrs: {
|
|
6380
|
-
id:
|
|
6631
|
+
id: id10(),
|
|
6381
6632
|
...scope(),
|
|
6382
6633
|
membershipId: key2(),
|
|
6383
6634
|
seatId: key2(true),
|
|
@@ -6394,10 +6645,12 @@ var membershipAuthorityEntities = {
|
|
|
6394
6645
|
journeyApprovedAt: attr9("number", true)
|
|
6395
6646
|
} },
|
|
6396
6647
|
networkSeat: { attrs: {
|
|
6397
|
-
id:
|
|
6648
|
+
id: id10(),
|
|
6398
6649
|
...scope(),
|
|
6399
6650
|
primaryMembershipId: key2(),
|
|
6400
|
-
purchaserUserId: key2(),
|
|
6651
|
+
purchaserUserId: key2(true),
|
|
6652
|
+
purchaserEmailNormalized: key2(true),
|
|
6653
|
+
combinedInvoice: attr9("json", true),
|
|
6401
6654
|
recipientName: attr9("string"),
|
|
6402
6655
|
recipientEmailNormalized: key2(),
|
|
6403
6656
|
recipientClaimId: key2(),
|
|
@@ -6429,9 +6682,9 @@ var membershipAuthorityEntities = {
|
|
|
6429
6682
|
refundedCents: attr9("number", true),
|
|
6430
6683
|
providerRefundIds: attr9("json", true)
|
|
6431
6684
|
} },
|
|
6432
|
-
networkSeatRecipient: { attrs: { id:
|
|
6685
|
+
networkSeatRecipient: { attrs: { id: id10(), appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), seatId: key2(), recipientEmailNormalized: key2(), createdAt: attr9("number") } },
|
|
6433
6686
|
membershipEffect: { attrs: {
|
|
6434
|
-
id:
|
|
6687
|
+
id: id10(),
|
|
6435
6688
|
chapterId: key2(),
|
|
6436
6689
|
environment: key2(),
|
|
6437
6690
|
runtime: key2(),
|
|
@@ -6556,14 +6809,14 @@ function validateMemberContentDocument(input, options = {}) {
|
|
|
6556
6809
|
continue;
|
|
6557
6810
|
}
|
|
6558
6811
|
unknownKeys(raw2, PAGE_KEYS, path, errors);
|
|
6559
|
-
const
|
|
6812
|
+
const id11 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
|
|
6560
6813
|
const slug = textValue(raw2.slug, `${path}.slug`, errors, 120, true) ?? "";
|
|
6561
6814
|
const title = textValue(raw2.title, `${path}.title`, errors, MAX_TEXT, true) ?? "";
|
|
6562
|
-
if (
|
|
6815
|
+
if (id11 && !ID.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
6563
6816
|
if (slug && !SLUG8.test(slug)) errors.push(`${path}.slug must be a lowercase URL slug`);
|
|
6564
|
-
if (ids2.has(
|
|
6817
|
+
if (ids2.has(id11) || knownParentIds.has(id11)) errors.push(`${path}.id duplicates page "${id11}"`);
|
|
6565
6818
|
if (slugs.has(slug) || (options.parentPages ?? []).some((page) => page.slug === slug)) errors.push(`${path}.slug duplicates "${slug}"`);
|
|
6566
|
-
ids2.add(
|
|
6819
|
+
ids2.add(id11);
|
|
6567
6820
|
slugs.add(slug);
|
|
6568
6821
|
const navRaw = record(raw2.navigation);
|
|
6569
6822
|
let navigation = { section: "General", order: index2 };
|
|
@@ -6619,7 +6872,7 @@ function validateMemberContentDocument(input, options = {}) {
|
|
|
6619
6872
|
else if (owner === "chapter") upsell = { owner, ...offer ? { offer } : {} };
|
|
6620
6873
|
}
|
|
6621
6874
|
}
|
|
6622
|
-
pages.push({ id:
|
|
6875
|
+
pages.push({ id: id11, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
|
|
6623
6876
|
}
|
|
6624
6877
|
const allIds = /* @__PURE__ */ new Set([...knownParentIds, ...ids2]);
|
|
6625
6878
|
for (const [index2, page] of pages.entries()) {
|
|
@@ -6825,8 +7078,8 @@ function resourceValue(value, path, errors) {
|
|
|
6825
7078
|
return null;
|
|
6826
7079
|
}
|
|
6827
7080
|
unknownKeys2(raw2, RESOURCE_KEYS, path, errors);
|
|
6828
|
-
const
|
|
6829
|
-
if (
|
|
7081
|
+
const id11 = text3(raw2.id, `${path}.id`, errors, 80) ?? "";
|
|
7082
|
+
if (id11 && !ID2.test(id11)) errors.push(`${path}.id must be lowercase kebab-case`);
|
|
6830
7083
|
const kind = text3(raw2.kind, `${path}.kind`, errors) ?? "";
|
|
6831
7084
|
if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
|
|
6832
7085
|
const title = text3(raw2.title, `${path}.title`, errors, 240) ?? "";
|
|
@@ -6883,7 +7136,7 @@ function resourceValue(value, path, errors) {
|
|
|
6883
7136
|
updatedAt: integer3(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
|
|
6884
7137
|
...promotedFrom ? { promotedFrom } : {}
|
|
6885
7138
|
};
|
|
6886
|
-
return { id:
|
|
7139
|
+
return { id: id11, kind, title, ...summary ? { summary } : {}, tags, scope: scope2, status: status2, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance };
|
|
6887
7140
|
}
|
|
6888
7141
|
function validateBuilderLibraryDocument(value, options = {}) {
|
|
6889
7142
|
const errors = [];
|
|
@@ -6914,7 +7167,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
|
|
|
6914
7167
|
const inputs = Array.isArray(raw2.resources) ? raw2.resources : [];
|
|
6915
7168
|
if (inputs.length > MAX_RESOURCES) errors.push(`document.resources may contain at most ${MAX_RESOURCES} items`);
|
|
6916
7169
|
const resources = inputs.map((item, index2) => resourceValue(item, `document.resources[${index2}]`, errors)).filter(Boolean);
|
|
6917
|
-
const ids2 = resources.map(({ id:
|
|
7170
|
+
const ids2 = resources.map(({ id: id11 }) => id11);
|
|
6918
7171
|
if (new Set(ids2).size !== ids2.length) errors.push("document.resources ids must be unique");
|
|
6919
7172
|
for (const resource of resources) {
|
|
6920
7173
|
if (sourceType === "chapter" && (resource.scope.kind !== "chapter" || resource.scope.chapterId !== sourceAppId)) {
|
|
@@ -7244,11 +7497,11 @@ async function sendTemplated(deps, input) {
|
|
|
7244
7497
|
const previous = prior[0];
|
|
7245
7498
|
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" };
|
|
7246
7499
|
const hash2 = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input.dedupeKey));
|
|
7247
|
-
const
|
|
7500
|
+
const id11 = previous ? String(previous.id) : `email_${Array.from(new Uint8Array(hash2), (b) => b.toString(16).padStart(2, "0")).join("")}`;
|
|
7248
7501
|
const attempt = Number(previous?.deliveryAttempt ?? 0) + 1;
|
|
7249
7502
|
if (!Number.isSafeInteger(attempt) || attempt < 1) return { sent: false, reason: "delivery-state-invalid" };
|
|
7250
7503
|
const row2 = {
|
|
7251
|
-
id:
|
|
7504
|
+
id: id11,
|
|
7252
7505
|
groupId: input.group.id,
|
|
7253
7506
|
to: decision.to,
|
|
7254
7507
|
template: input.template,
|
|
@@ -7266,15 +7519,15 @@ async function sendTemplated(deps, input) {
|
|
|
7266
7519
|
if (isRetiredEmailCopy(String(row2.subject), String(row2.body))) return { sent: false, reason: "template-retired" };
|
|
7267
7520
|
if ((0, import_crm8.validateEmailCopy)({ subject: row2.subject, text: row2.body }, { allowedVariables: [] }).length) return { sent: false, reason: "template-invalid" };
|
|
7268
7521
|
try {
|
|
7269
|
-
const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id:
|
|
7270
|
-
mutationId: `email-claim:${
|
|
7522
|
+
const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id: id11, attrs: row2 }], {
|
|
7523
|
+
mutationId: `email-claim:${id11}:${attempt}`,
|
|
7271
7524
|
guards: [previous ? {
|
|
7272
7525
|
ns: "emailLog",
|
|
7273
|
-
id:
|
|
7526
|
+
id: id11,
|
|
7274
7527
|
exists: true,
|
|
7275
7528
|
equals: { deliveryState: "retry_ready", ...previous.deliveryAttempt !== void 0 ? { deliveryAttempt: previous.deliveryAttempt } : {} },
|
|
7276
7529
|
...previous.deliveryAttempt === void 0 ? { absent: ["deliveryAttempt"] } : {}
|
|
7277
|
-
} : { ns: "emailLog", id:
|
|
7530
|
+
} : { ns: "emailLog", id: id11, exists: false }]
|
|
7278
7531
|
});
|
|
7279
7532
|
if (claim.duplicate) return { sent: false, reason: "delivery-uncertain" };
|
|
7280
7533
|
} catch (error) {
|
|
@@ -7294,16 +7547,16 @@ async function sendTemplated(deps, input) {
|
|
|
7294
7547
|
if (!response || typeof response.messageId !== "string" || !response.messageId) throw new Error("missing delivery receipt");
|
|
7295
7548
|
messageId = response.messageId;
|
|
7296
7549
|
} catch {
|
|
7297
|
-
await deps.db.transact([{ t: "update", ns: "emailLog", id:
|
|
7298
|
-
guards: [{ ns: "emailLog", id:
|
|
7550
|
+
await deps.db.transact([{ t: "update", ns: "emailLog", id: id11, attrs: { deliveryState: "uncertain", error: "delivery-uncertain" } }], {
|
|
7551
|
+
guards: [{ ns: "emailLog", id: id11, exists: true, equals: { deliveryState: "sending", deliveryAttempt: attempt } }]
|
|
7299
7552
|
});
|
|
7300
7553
|
return { sent: false, reason: "delivery-uncertain" };
|
|
7301
7554
|
}
|
|
7302
|
-
await deps.db.transact([{ t: "update", ns: "emailLog", id:
|
|
7303
|
-
mutationId: `email-confirmed:${
|
|
7555
|
+
await deps.db.transact([{ t: "update", ns: "emailLog", id: id11, attrs: { deliveryState: "sent", error: "", messageId, sentAt: deps.now() } }], {
|
|
7556
|
+
mutationId: `email-confirmed:${id11}:${attempt}`,
|
|
7304
7557
|
guards: [{
|
|
7305
7558
|
ns: "emailLog",
|
|
7306
|
-
id:
|
|
7559
|
+
id: id11,
|
|
7307
7560
|
exists: true,
|
|
7308
7561
|
equals: { deliveryState: "sending", deliveryAttempt: attempt }
|
|
7309
7562
|
}]
|
|
@@ -7359,7 +7612,7 @@ async function reconcileLifecycleDelivery(db, input, now) {
|
|
|
7359
7612
|
}
|
|
7360
7613
|
|
|
7361
7614
|
// src/payments.ts
|
|
7362
|
-
var
|
|
7615
|
+
var import_stripe11 = require("@odla-ai/stripe");
|
|
7363
7616
|
function paymentsReady(group, hasSecretKey) {
|
|
7364
7617
|
return Boolean(group.stripePublishableKey && group.stripePriceId && hasSecretKey);
|
|
7365
7618
|
}
|
|
@@ -7561,14 +7814,14 @@ function validateAdmissionGrantDocument(input) {
|
|
|
7561
7814
|
const seenEmails = /* @__PURE__ */ new Set();
|
|
7562
7815
|
for (const [index2, raw2] of rawGrants.slice(0, MAX_GRANTS).entries()) {
|
|
7563
7816
|
const grant = raw2 && typeof raw2 === "object" ? raw2 : {};
|
|
7564
|
-
const
|
|
7817
|
+
const id11 = typeof grant.id === "string" ? grant.id : "";
|
|
7565
7818
|
const email = typeof grant.email === "string" ? grant.email.trim() : "";
|
|
7566
7819
|
const normalized = normalizeAdmissionEmail(email);
|
|
7567
7820
|
const decision = grant.decision;
|
|
7568
7821
|
const note = typeof grant.note === "string" ? grant.note.trim() : void 0;
|
|
7569
7822
|
const expiresAt = grant.expiresAt === void 0 ? void 0 : Number(grant.expiresAt);
|
|
7570
|
-
if (!ID3.test(
|
|
7571
|
-
if (seenIds.has(
|
|
7823
|
+
if (!ID3.test(id11)) errors.push(`document.grants[${index2}].id must be an identifier`);
|
|
7824
|
+
if (seenIds.has(id11)) errors.push(`document.grants repeats id "${id11}"`);
|
|
7572
7825
|
if (!isAdmissionEmail(normalized)) errors.push(`document.grants[${index2}].email must be valid`);
|
|
7573
7826
|
if (seenEmails.has(normalized)) errors.push(`document.grants repeats email "${normalized}"`);
|
|
7574
7827
|
if (decision !== "waive_interview" && decision !== "require_interview") {
|
|
@@ -7578,10 +7831,10 @@ function validateAdmissionGrantDocument(input) {
|
|
|
7578
7831
|
if (expiresAt !== void 0 && (!Number.isFinite(expiresAt) || expiresAt <= 0)) {
|
|
7579
7832
|
errors.push(`document.grants[${index2}].expiresAt must be epoch milliseconds`);
|
|
7580
7833
|
}
|
|
7581
|
-
seenIds.add(
|
|
7834
|
+
seenIds.add(id11);
|
|
7582
7835
|
seenEmails.add(normalized);
|
|
7583
7836
|
grants.push({
|
|
7584
|
-
id:
|
|
7837
|
+
id: id11,
|
|
7585
7838
|
email,
|
|
7586
7839
|
decision,
|
|
7587
7840
|
...note ? { note } : {},
|
|
@@ -7653,8 +7906,8 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
|
|
|
7653
7906
|
const incomingIds = new Set(document.grants.map((grant) => `admission:parent:${document.source.appId}:${grant.id}`));
|
|
7654
7907
|
const ops = [];
|
|
7655
7908
|
for (const row2 of current) {
|
|
7656
|
-
const
|
|
7657
|
-
if (
|
|
7909
|
+
const id11 = String(row2.id ?? "");
|
|
7910
|
+
if (id11 && !incomingIds.has(id11)) ops.push({ t: "update", ns: "admissionGrants", id: id11, attrs: {
|
|
7658
7911
|
active: false,
|
|
7659
7912
|
revokedAt: receivedAt,
|
|
7660
7913
|
revokedBy: document.source.actor,
|
|
@@ -7663,10 +7916,10 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
|
|
|
7663
7916
|
} });
|
|
7664
7917
|
}
|
|
7665
7918
|
for (const grant of document.grants) {
|
|
7666
|
-
const
|
|
7667
|
-
const existing = current.find((row2) => row2.id ===
|
|
7668
|
-
ops.push({ t: "update", ns: "admissionGrants", id:
|
|
7669
|
-
id:
|
|
7919
|
+
const id11 = `admission:parent:${document.source.appId}:${grant.id}`;
|
|
7920
|
+
const existing = current.find((row2) => row2.id === id11);
|
|
7921
|
+
ops.push({ t: "update", ns: "admissionGrants", id: id11, attrs: {
|
|
7922
|
+
id: id11,
|
|
7670
7923
|
sourceGrantId: grant.id,
|
|
7671
7924
|
sourceScope: "parent",
|
|
7672
7925
|
sourceId: document.source.appId,
|
|
@@ -7687,7 +7940,7 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
|
|
|
7687
7940
|
...!grant.note && existing?.note !== void 0 ? ["note"] : [],
|
|
7688
7941
|
...grant.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
|
|
7689
7942
|
];
|
|
7690
|
-
if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id:
|
|
7943
|
+
if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id11, attrs: retract });
|
|
7691
7944
|
}
|
|
7692
7945
|
if (previous?.active) ops.push({ t: "update", ns: "admissionGrantRevisions", id: previous.id, attrs: { active: false } });
|
|
7693
7946
|
const revisionId = `admission-revision:${document.source.appId}:${document.target.environment}:${document.revision}`;
|
|
@@ -7758,10 +8011,10 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
7758
8011
|
}
|
|
7759
8012
|
const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
|
|
7760
8013
|
for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
7761
|
-
const
|
|
7762
|
-
if (
|
|
7763
|
-
if (ids2.has(
|
|
7764
|
-
ids2.add(
|
|
8014
|
+
const id11 = text4(item.id, `${path}.id`, errors, 80);
|
|
8015
|
+
if (id11 && !ID4.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
8016
|
+
if (ids2.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
|
|
8017
|
+
ids2.add(id11);
|
|
7765
8018
|
const targetRaw = record3(item.target);
|
|
7766
8019
|
let target = { type: "entitlement", entitlementKey: "invalid" };
|
|
7767
8020
|
if (targetRaw?.type === "tier") {
|
|
@@ -7787,7 +8040,7 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
7787
8040
|
if (activeUntil !== void 0 && (!Number.isSafeInteger(activeUntil) || activeUntil < 1)) errors.push(`${path}.activeUntil must be epoch milliseconds`);
|
|
7788
8041
|
if (activeFrom !== void 0 && activeUntil !== void 0 && activeUntil <= activeFrom) errors.push(`${path}.activeUntil must follow activeFrom`);
|
|
7789
8042
|
offers.push({
|
|
7790
|
-
id:
|
|
8043
|
+
id: id11,
|
|
7791
8044
|
target,
|
|
7792
8045
|
billingMode: item.billingMode,
|
|
7793
8046
|
stripePriceId,
|
|
@@ -7831,10 +8084,10 @@ async function ensureProjectedPerson(deps, input, options = {}) {
|
|
|
7831
8084
|
const existing = await find();
|
|
7832
8085
|
if (existing) return { record: existing, created: false };
|
|
7833
8086
|
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`chapter:person:v1:${email}`)));
|
|
7834
|
-
const
|
|
8087
|
+
const id11 = `person-${Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
7835
8088
|
try {
|
|
7836
8089
|
const created = await (0, import_crm9.createRecord)({ ...deps, db: deps.db }, {
|
|
7837
|
-
id:
|
|
8090
|
+
id: id11,
|
|
7838
8091
|
type: "person",
|
|
7839
8092
|
input: { ...input, email },
|
|
7840
8093
|
...options
|
|
@@ -8257,9 +8510,9 @@ function subAnnualCents(sub) {
|
|
|
8257
8510
|
}
|
|
8258
8511
|
|
|
8259
8512
|
// src/payments-stripe.ts
|
|
8260
|
-
var
|
|
8513
|
+
var import_stripe12 = require("@odla-ai/stripe");
|
|
8261
8514
|
async function stripeCall(sk, method, path, params, idempotencyKey) {
|
|
8262
|
-
return (0,
|
|
8515
|
+
return (0, import_stripe12.stripeRequest)(
|
|
8263
8516
|
{ secretKey: sk },
|
|
8264
8517
|
method,
|
|
8265
8518
|
path,
|
|
@@ -8337,8 +8590,8 @@ async function updateClerkUserMetadata(secretKey, userId, input, fetchImpl = fet
|
|
|
8337
8590
|
return res.ok;
|
|
8338
8591
|
}
|
|
8339
8592
|
async function updateClerkUserMetadataByEmail(secretKey, email, input, fetchImpl = fetch) {
|
|
8340
|
-
const
|
|
8341
|
-
return
|
|
8593
|
+
const id11 = await existingClerkUserId(secretKey, email, fetchImpl);
|
|
8594
|
+
return id11 ? updateClerkUserMetadata(secretKey, id11, input, fetchImpl) : false;
|
|
8342
8595
|
}
|
|
8343
8596
|
async function existingClerkUserId(secretKey, email, fetchImpl) {
|
|
8344
8597
|
const auth = { authorization: `Bearer ${secretKey}` };
|
|
@@ -8411,10 +8664,10 @@ async function clerkGetUserByEmail(secretKey, email, fetchImpl = fetch, source =
|
|
|
8411
8664
|
const user = Array.isArray(data) ? data[0] : void 0;
|
|
8412
8665
|
return user ? toRecord(user, source) : null;
|
|
8413
8666
|
}
|
|
8414
|
-
async function clerkGetUser(secretKey,
|
|
8415
|
-
const data = await clerkGet(`/v1/users/${encodeURIComponent(
|
|
8667
|
+
async function clerkGetUser(secretKey, id11, fetchImpl = fetch, source = "public") {
|
|
8668
|
+
const data = await clerkGet(`/v1/users/${encodeURIComponent(id11)}`, secretKey, fetchImpl).catch(() => null);
|
|
8416
8669
|
const record4 = data ? toRecord(data, source) : null;
|
|
8417
|
-
return record4?.id ===
|
|
8670
|
+
return record4?.id === id11 ? record4 : null;
|
|
8418
8671
|
}
|
|
8419
8672
|
async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
|
|
8420
8673
|
const out = [];
|
|
@@ -8430,8 +8683,8 @@ async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
|
|
|
8430
8683
|
}
|
|
8431
8684
|
return out;
|
|
8432
8685
|
}
|
|
8433
|
-
async function clerkSetRole(secretKey,
|
|
8434
|
-
const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(
|
|
8686
|
+
async function clerkSetRole(secretKey, id11, role, fetchImpl = fetch, source = "public") {
|
|
8687
|
+
const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id11)}/metadata`, {
|
|
8435
8688
|
method: "PATCH",
|
|
8436
8689
|
headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
|
|
8437
8690
|
redirect: "manual",
|
|
@@ -8448,7 +8701,7 @@ function planPrivateRoleMigration(input) {
|
|
|
8448
8701
|
if (input.environment !== "dev") blockers.push("dev_only");
|
|
8449
8702
|
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");
|
|
8450
8703
|
const expected = new Set(input.expectedRuntimes);
|
|
8451
|
-
if (!expected.size || expected.size !== input.expectedRuntimes.length || input.expectedRuntimes.some((
|
|
8704
|
+
if (!expected.size || expected.size !== input.expectedRuntimes.length || input.expectedRuntimes.some((id11) => !id11)) blockers.push("runtime_inventory_required");
|
|
8452
8705
|
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");
|
|
8453
8706
|
for (const runtime of input.expectedRuntimes) {
|
|
8454
8707
|
const proof = input.runtimes.find((r) => r.runtime === runtime);
|
|
@@ -8478,7 +8731,7 @@ function planPrivateRoleMigration(input) {
|
|
|
8478
8731
|
if (privateRole === role && !Object.hasOwn(user.publicMetadata, "role")) alreadyCurrent++;
|
|
8479
8732
|
else changes.push({ userId: user.id, role, publicRole, privateRole });
|
|
8480
8733
|
}
|
|
8481
|
-
if (!input.protectedSuperAdminUserIds.some((
|
|
8734
|
+
if (!input.protectedSuperAdminUserIds.some((id11) => ids2.has(id11))) blockers.push("protected_operator_grant_required");
|
|
8482
8735
|
return { ready: blockers.length === 0, blockers, changes, alreadyCurrent, totalUsers: input.users.length };
|
|
8483
8736
|
}
|
|
8484
8737
|
|
|
@@ -9056,10 +9309,10 @@ function tiersOf(value, errors) {
|
|
|
9056
9309
|
return [];
|
|
9057
9310
|
}
|
|
9058
9311
|
unknownKeys3(item, TIER_KEYS2, path, errors);
|
|
9059
|
-
const
|
|
9060
|
-
if (
|
|
9061
|
-
if (seen.has(
|
|
9062
|
-
seen.add(
|
|
9312
|
+
const id11 = text5(item.id, `${path}.id`, errors, 80);
|
|
9313
|
+
if (id11 && !TIER.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
9314
|
+
if (seen.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
|
|
9315
|
+
seen.add(id11);
|
|
9063
9316
|
const name = text5(item.name, `${path}.name`, errors, 160);
|
|
9064
9317
|
if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
|
|
9065
9318
|
if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
|
|
@@ -9080,7 +9333,7 @@ function tiersOf(value, errors) {
|
|
|
9080
9333
|
}
|
|
9081
9334
|
const blurb = item.blurb === void 0 ? void 0 : text5(item.blurb, `${path}.blurb`, errors, 4e3);
|
|
9082
9335
|
return [{
|
|
9083
|
-
id:
|
|
9336
|
+
id: id11,
|
|
9084
9337
|
name,
|
|
9085
9338
|
available: item.available === true,
|
|
9086
9339
|
sortOrder: Number(item.sortOrder) || 0,
|
|
@@ -9142,15 +9395,15 @@ function policyOf(value, errors, tiers2) {
|
|
|
9142
9395
|
if (input.tierPolicies !== void 0) {
|
|
9143
9396
|
const overrides = recordOf(input.tierPolicies);
|
|
9144
9397
|
if (!overrides || Object.keys(overrides).length > 20) errors.push("document.policy.tierPolicies must be a bounded tier map");
|
|
9145
|
-
else for (const [
|
|
9146
|
-
const path = `document.policy.tierPolicies.${
|
|
9398
|
+
else for (const [id11, raw2] of Object.entries(overrides)) {
|
|
9399
|
+
const path = `document.policy.tierPolicies.${id11}`;
|
|
9147
9400
|
const policy2 = recordOf(raw2);
|
|
9148
|
-
if (!tiers2.some((tier) => tier.id ===
|
|
9401
|
+
if (!tiers2.some((tier) => tier.id === id11) || !policy2) {
|
|
9149
9402
|
errors.push(`${path} must name a declared tier and policy`);
|
|
9150
9403
|
continue;
|
|
9151
9404
|
}
|
|
9152
9405
|
unknownKeys3(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
|
|
9153
|
-
tierPolicies[
|
|
9406
|
+
tierPolicies[id11] = {
|
|
9154
9407
|
refundPolicyText: text5(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
|
|
9155
9408
|
...policy2.trustCopy !== void 0 ? { trustCopy: text5(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
|
|
9156
9409
|
};
|
|
@@ -9473,7 +9726,7 @@ var exclusions = (chapterId, paidTierIds) => [
|
|
|
9473
9726
|
id: chapterId,
|
|
9474
9727
|
reason: "provider_identity",
|
|
9475
9728
|
paths: [
|
|
9476
|
-
...paidTierIds.map((
|
|
9729
|
+
...paidTierIds.map((id11) => `tiers[${id11}].stripePriceId`),
|
|
9477
9730
|
"groups.stripePublishableKey",
|
|
9478
9731
|
"applications.stripeCustomerId",
|
|
9479
9732
|
"applications.stripeSubscriptionId",
|
|
@@ -9515,9 +9768,9 @@ async function exportChapterPromotion(input) {
|
|
|
9515
9768
|
sourceIds: [`signup-control:${document.revision}`, document.source.appId, document.target.chapterId].sort(),
|
|
9516
9769
|
receiptDigests: [input.digest]
|
|
9517
9770
|
};
|
|
9518
|
-
const artifact = (kind,
|
|
9771
|
+
const artifact = (kind, id11, content) => ({
|
|
9519
9772
|
kind,
|
|
9520
|
-
id:
|
|
9773
|
+
id: id11,
|
|
9521
9774
|
sourceRevision: revision,
|
|
9522
9775
|
provenance: promotionProvenance,
|
|
9523
9776
|
content: (0, import_promotion.canonicalPromotionValue)(content)
|
|
@@ -9562,7 +9815,7 @@ var MEMBER_SYSTEM_RECORD_OWNERS = {
|
|
|
9562
9815
|
provider: "originating-provider",
|
|
9563
9816
|
protectedRoles: "odla-platform"
|
|
9564
9817
|
};
|
|
9565
|
-
var rows2 = (group) => group.fields.map((
|
|
9818
|
+
var rows2 = (group) => group.fields.map((id11) => ({ ...group, id: id11 }));
|
|
9566
9819
|
var MEMBER_SYSTEM_AUTHORITY_MATRIX = [
|
|
9567
9820
|
...rows2({
|
|
9568
9821
|
scope: "network",
|