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