@odla-ai/chapter 0.54.2 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -133,10 +133,10 @@ async function clerkGetUserByEmail(secretKey, email, fetchImpl = fetch, source =
133
133
  const user = Array.isArray(data) ? data[0] : void 0;
134
134
  return user ? toRecord(user, source) : null;
135
135
  }
136
- async function clerkGetUser(secretKey, id6, fetchImpl = fetch, source = "public") {
137
- const data = await clerkGet(`/v1/users/${encodeURIComponent(id6)}`, secretKey, fetchImpl).catch(() => null);
136
+ async function clerkGetUser(secretKey, id7, fetchImpl = fetch, source = "public") {
137
+ const data = await clerkGet(`/v1/users/${encodeURIComponent(id7)}`, secretKey, fetchImpl).catch(() => null);
138
138
  const record4 = data ? toRecord(data, source) : null;
139
- return record4?.id === id6 ? record4 : null;
139
+ return record4?.id === id7 ? record4 : null;
140
140
  }
141
141
  async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
142
142
  const out = [];
@@ -152,8 +152,8 @@ async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
152
152
  }
153
153
  return out;
154
154
  }
155
- async function clerkSetRole(secretKey, id6, role, fetchImpl = fetch, source = "public") {
156
- const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id6)}/metadata`, {
155
+ async function clerkSetRole(secretKey, id7, role, fetchImpl = fetch, source = "public") {
156
+ const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id7)}/metadata`, {
157
157
  method: "PATCH",
158
158
  headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
159
159
  redirect: "manual",
@@ -212,10 +212,10 @@ async function ensureProjectedPerson(deps, input, options = {}) {
212
212
  const existing = await find();
213
213
  if (existing) return { record: existing, created: false };
214
214
  const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`chapter:person:v1:${email}`)));
215
- const id6 = `person-${Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
215
+ const id7 = `person-${Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
216
216
  try {
217
217
  const created = await (0, import_crm.createRecord)({ ...deps, db: deps.db }, {
218
- id: id6,
218
+ id: id7,
219
219
  type: "person",
220
220
  input: { ...input, email },
221
221
  ...options
@@ -668,7 +668,10 @@ function verifyInitialMembershipInvoice(intent, evidence) {
668
668
  if (!intent.providerSubscriptionId || !intent.providerCustomerId) return { status: "retry" };
669
669
  try {
670
670
  const terms2 = intent.billingTerms ? subscriptionBillingTerms(intent.billingTerms) : void 0;
671
- const due = subscriptionAmountDue({ priceCents: Number(intent.priceCents), billingTerms: terms2 });
671
+ const quote = intent.paymentQuote;
672
+ const seatCents = quote?.namedSeat ? quote.lineItems.namedSeatCents : 0;
673
+ if (!Number.isSafeInteger(seatCents) || Number(seatCents) < 0 || quote?.namedSeat && quote.seatOffer?.amountCents !== seatCents) return { status: "mismatch" };
674
+ const due = subscriptionAmountDue({ priceCents: Number(intent.priceCents), billingTerms: terms2 }) + Number(seatCents);
672
675
  if (!evidence || !/^in_[A-Za-z0-9]+$/.test(evidence.invoiceId ?? "") || evidence.subscriptionId !== intent.providerSubscriptionId || evidence.customerId !== intent.providerCustomerId || evidence.currency !== intent.currency || evidence.total !== due || evidence.amountPaid !== due || due <= 0 || evidence.status !== "paid") return { status: "mismatch" };
673
676
  return { status: "verified", ...terms2?.initialTermEndsAt ? { renewalAt: terms2.initialTermEndsAt } : {} };
674
677
  } catch {
@@ -1019,8 +1022,8 @@ async function rebuildNetworkCommercialProjection(db, sources, mutationId, now =
1019
1022
  return projection;
1020
1023
  }
1021
1024
  async function collide(db, sources, stored, fact, now, legacyDigest, alerts) {
1022
- const id6 = exceptionId(fact.eventId, "collision");
1023
- const exception = (await db.query({ networkCommercialExceptions: { $: { where: { id: id6 }, limit: 1 } } })).networkCommercialExceptions?.[0];
1025
+ const id7 = exceptionId(fact.eventId, "collision");
1026
+ const exception = (await db.query({ networkCommercialExceptions: { $: { where: { id: id7 }, limit: 1 } } })).networkCommercialExceptions?.[0];
1024
1027
  const exact = stored.digest === fact.digest || stored.digest === legacyDigest;
1025
1028
  const canonical3 = !exact && sameNetworkCommercialFact(stored.fact, fact);
1026
1029
  if (exact || canonical3) {
@@ -1041,7 +1044,7 @@ async function collide(db, sources, stored, fact, now, legacyDigest, alerts) {
1041
1044
  action: "canonical_replay",
1042
1045
  reason: "a resend matched the retained canonical fact"
1043
1046
  } },
1044
- { t: "update", ns: "networkCommercialExceptions", id: id6, attrs: {
1047
+ { t: "update", ns: "networkCommercialExceptions", id: id7, attrs: {
1045
1048
  status: "resolved",
1046
1049
  resolvedDigest: stored.digest,
1047
1050
  reviewedDigest: fact.digest,
@@ -1050,7 +1053,7 @@ async function collide(db, sources, stored, fact, now, legacyDigest, alerts) {
1050
1053
  updatedAt: now
1051
1054
  } }
1052
1055
  ], { mutationId: `network-commercial-resolve:${fact.eventId}:${fact.digest}`, guards: [
1053
- { ns: "networkCommercialExceptions", id: id6, exists: true, equals: { status: "active", updatedAt: exception.updatedAt } }
1056
+ { ns: "networkCommercialExceptions", id: id7, exists: true, equals: { status: "active", updatedAt: exception.updatedAt } }
1054
1057
  ] });
1055
1058
  const projection2 = await rebuildNetworkCommercialProjection(db, sources, `network-commercial-resolve-rebuild:${fact.eventId}:${fact.digest}`, now, alerts);
1056
1059
  return { status: "replayed", resolvedCollision: true, projection: projection2 };
@@ -1060,9 +1063,9 @@ async function collide(db, sources, stored, fact, now, legacyDigest, alerts) {
1060
1063
  }
1061
1064
  const stale = RESOLUTION_ATTRS.filter((attr3) => exception !== void 0 && Object.hasOwn(exception, attr3));
1062
1065
  await db.transact([
1063
- ...stale.length ? [{ t: "retract", ns: "networkCommercialExceptions", id: id6, attrs: stale }] : [],
1064
- { t: "update", ns: "networkCommercialExceptions", id: id6, attrs: {
1065
- id: id6,
1066
+ ...stale.length ? [{ t: "retract", ns: "networkCommercialExceptions", id: id7, attrs: stale }] : [],
1067
+ { t: "update", ns: "networkCommercialExceptions", id: id7, attrs: {
1068
+ id: id7,
1066
1069
  eventId: fact.eventId,
1067
1070
  kind: "collision",
1068
1071
  status: "active",
@@ -1082,12 +1085,12 @@ async function ingestNetworkCommercialFact(db, sources, fact, now = Date.now(),
1082
1085
  const found = await db.query({ networkCommercialEvents: { $: { where: { eventId: fact.eventId }, limit: 1 } } });
1083
1086
  const existing = found.networkCommercialEvents?.[0];
1084
1087
  if (existing) return collide(db, sources, existing, fact, now, legacyDigest, alerts);
1085
- const id6 = eventRowId(fact.eventId);
1088
+ const id7 = eventRowId(fact.eventId);
1086
1089
  let lostRace = false;
1087
1090
  let duplicate = false;
1088
1091
  try {
1089
- const write = await db.transact([{ t: "update", ns: "networkCommercialEvents", id: id6, attrs: {
1090
- id: id6,
1092
+ const write = await db.transact([{ t: "update", ns: "networkCommercialEvents", id: id7, attrs: {
1093
+ id: id7,
1091
1094
  eventId: fact.eventId,
1092
1095
  digest: fact.digest,
1093
1096
  eventType: fact.eventType,
@@ -1097,7 +1100,7 @@ async function ingestNetworkCommercialFact(db, sources, fact, now = Date.now(),
1097
1100
  receivedAt: now
1098
1101
  } }], {
1099
1102
  mutationId: `network-commercial-ingest:${fact.eventId}:${fact.digest}`,
1100
- guards: [{ ns: "networkCommercialEvents", id: id6, exists: false }]
1103
+ guards: [{ ns: "networkCommercialEvents", id: id7, exists: false }]
1101
1104
  });
1102
1105
  duplicate = write.duplicate;
1103
1106
  } catch (error) {
@@ -1141,10 +1144,10 @@ async function recoverNetworkCommercialEvent(db, sources, eventId, actorUserId,
1141
1144
  const provider = await readStripeRecoveryEvent(db, eventId);
1142
1145
  const fact = normalizeNetworkCommercialStripeEvent(provider.event, provider.digest);
1143
1146
  if (!sameNetworkCommercialFact(stored.fact, fact)) throw new Error("Stripe facts differ; collision requires investigation");
1144
- const id6 = crypto.randomUUID();
1147
+ const id7 = crypto.randomUUID();
1145
1148
  await db.transact([
1146
- { t: "update", ns: "paymentRecoveryReviews", id: id6, attrs: {
1147
- id: id6,
1149
+ { t: "update", ns: "paymentRecoveryReviews", id: id7, attrs: {
1150
+ id: id7,
1148
1151
  eventId,
1149
1152
  actorUserId,
1150
1153
  reviewedAt: now,
@@ -1159,9 +1162,9 @@ async function recoverNetworkCommercialEvent(db, sources, eventId, actorUserId,
1159
1162
  t: "update",
1160
1163
  ns: "networkCommercialExceptions",
1161
1164
  id: exceptionId2,
1162
- attrs: { status: "resolved", resolvedDigest: provider.digest, resolvedBy: actorUserId, reviewId: id6, updatedAt: now }
1165
+ attrs: { status: "resolved", resolvedDigest: provider.digest, resolvedBy: actorUserId, reviewId: id7, updatedAt: now }
1163
1166
  }] : []
1164
- ], { mutationId: `payment-recovery:${id6}`, guards: [
1167
+ ], { mutationId: `payment-recovery:${id7}`, guards: [
1165
1168
  { ns: "networkCommercialEvents", id: eventRowId2, exists: true, equals: { digest: stored.digest } },
1166
1169
  {
1167
1170
  ns: "networkCommercialExceptions",
@@ -1170,8 +1173,8 @@ async function recoverNetworkCommercialEvent(db, sources, eventId, actorUserId,
1170
1173
  ...exception ? { equals: { status: exception.status, updatedAt: exception.updatedAt } } : {}
1171
1174
  }
1172
1175
  ] });
1173
- await rebuildNetworkCommercialProjection(db, sources, `payment-recovery-rebuild:${id6}`, now, alerts);
1174
- return { recovered: true, eventId, reviewId: id6 };
1176
+ await rebuildNetworkCommercialProjection(db, sources, `payment-recovery-rebuild:${id7}`, now, alerts);
1177
+ return { recovered: true, eventId, reviewId: id7 };
1175
1178
  }
1176
1179
 
1177
1180
  // src/payments.ts
@@ -1950,11 +1953,11 @@ async function sendTemplated(deps, input) {
1950
1953
  const previous = prior[0];
1951
1954
  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" };
1952
1955
  const hash2 = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input.dedupeKey));
1953
- const id6 = previous ? String(previous.id) : `email_${Array.from(new Uint8Array(hash2), (b) => b.toString(16).padStart(2, "0")).join("")}`;
1956
+ const id7 = previous ? String(previous.id) : `email_${Array.from(new Uint8Array(hash2), (b) => b.toString(16).padStart(2, "0")).join("")}`;
1954
1957
  const attempt = Number(previous?.deliveryAttempt ?? 0) + 1;
1955
1958
  if (!Number.isSafeInteger(attempt) || attempt < 1) return { sent: false, reason: "delivery-state-invalid" };
1956
1959
  const row2 = {
1957
- id: id6,
1960
+ id: id7,
1958
1961
  groupId: input.group.id,
1959
1962
  to: decision.to,
1960
1963
  template: input.template,
@@ -1972,15 +1975,15 @@ async function sendTemplated(deps, input) {
1972
1975
  if (isRetiredEmailCopy(String(row2.subject), String(row2.body))) return { sent: false, reason: "template-retired" };
1973
1976
  if ((0, import_crm7.validateEmailCopy)({ subject: row2.subject, text: row2.body }, { allowedVariables: [] }).length) return { sent: false, reason: "template-invalid" };
1974
1977
  try {
1975
- const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id: id6, attrs: row2 }], {
1976
- mutationId: `email-claim:${id6}:${attempt}`,
1978
+ const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id: id7, attrs: row2 }], {
1979
+ mutationId: `email-claim:${id7}:${attempt}`,
1977
1980
  guards: [previous ? {
1978
1981
  ns: "emailLog",
1979
- id: id6,
1982
+ id: id7,
1980
1983
  exists: true,
1981
1984
  equals: { deliveryState: "retry_ready", ...previous.deliveryAttempt !== void 0 ? { deliveryAttempt: previous.deliveryAttempt } : {} },
1982
1985
  ...previous.deliveryAttempt === void 0 ? { absent: ["deliveryAttempt"] } : {}
1983
- } : { ns: "emailLog", id: id6, exists: false }]
1986
+ } : { ns: "emailLog", id: id7, exists: false }]
1984
1987
  });
1985
1988
  if (claim.duplicate) return { sent: false, reason: "delivery-uncertain" };
1986
1989
  } catch (error) {
@@ -2000,16 +2003,16 @@ async function sendTemplated(deps, input) {
2000
2003
  if (!response || typeof response.messageId !== "string" || !response.messageId) throw new Error("missing delivery receipt");
2001
2004
  messageId = response.messageId;
2002
2005
  } catch {
2003
- await deps.db.transact([{ t: "update", ns: "emailLog", id: id6, attrs: { deliveryState: "uncertain", error: "delivery-uncertain" } }], {
2004
- guards: [{ ns: "emailLog", id: id6, exists: true, equals: { deliveryState: "sending", deliveryAttempt: attempt } }]
2006
+ await deps.db.transact([{ t: "update", ns: "emailLog", id: id7, attrs: { deliveryState: "uncertain", error: "delivery-uncertain" } }], {
2007
+ guards: [{ ns: "emailLog", id: id7, exists: true, equals: { deliveryState: "sending", deliveryAttempt: attempt } }]
2005
2008
  });
2006
2009
  return { sent: false, reason: "delivery-uncertain" };
2007
2010
  }
2008
- await deps.db.transact([{ t: "update", ns: "emailLog", id: id6, attrs: { deliveryState: "sent", error: "", messageId, sentAt: deps.now() } }], {
2009
- mutationId: `email-confirmed:${id6}:${attempt}`,
2011
+ await deps.db.transact([{ t: "update", ns: "emailLog", id: id7, attrs: { deliveryState: "sent", error: "", messageId, sentAt: deps.now() } }], {
2012
+ mutationId: `email-confirmed:${id7}:${attempt}`,
2010
2013
  guards: [{
2011
2014
  ns: "emailLog",
2012
- id: id6,
2015
+ id: id7,
2013
2016
  exists: true,
2014
2017
  equals: { deliveryState: "sending", deliveryAttempt: attempt }
2015
2018
  }]
@@ -2194,8 +2197,8 @@ async function hash(value) {
2194
2197
  async function membershipDiscountCouponId(input, policyId) {
2195
2198
  return policyId ? `odla_m_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.skuId, policyId])}` : void 0;
2196
2199
  }
2197
- async function load(db, ns, id6) {
2198
- return (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
2200
+ async function load(db, ns, id7) {
2201
+ return (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
2199
2202
  }
2200
2203
  async function membershipOfferIdForRequest(input) {
2201
2204
  return `offer_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.applicationId, input.requestId])}`;
@@ -2329,20 +2332,20 @@ async function issueMembershipCommercialOffer(db, input) {
2329
2332
 
2330
2333
  // src/commercial-evidence-guard.ts
2331
2334
  async function commercialEvidenceGuard(db, eventId, digest, code = "membership_payment_evidence_quarantined") {
2332
- const ns = "networkCommercialExceptions", id6 = `network-commercial-exception:${eventId}:collision`;
2333
- const row2 = (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
2334
- if (!row2) return { ns, id: id6, exists: false };
2335
+ const ns = "networkCommercialExceptions", id7 = `network-commercial-exception:${eventId}:collision`;
2336
+ const row2 = (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
2337
+ if (!row2) return { ns, id: id7, exists: false };
2335
2338
  if (row2.status !== "resolved" || row2.resolvedDigest !== digest || typeof digest !== "string") {
2336
2339
  throw Object.assign(new Error(code), { code });
2337
2340
  }
2338
- return { ns, id: id6, exists: true, equals: { status: "resolved", resolvedDigest: digest, updatedAt: row2.updatedAt } };
2341
+ return { ns, id: id7, exists: true, equals: { status: "resolved", resolvedDigest: digest, updatedAt: row2.updatedAt } };
2339
2342
  }
2340
2343
 
2341
2344
  // src/membership-authority-state.ts
2342
2345
  var membershipFail = (code) => {
2343
2346
  throw Object.assign(new Error(code), { code });
2344
2347
  };
2345
- var membershipLoad = async (db, ns, id6) => (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
2348
+ var membershipLoad = async (db, ns, id7) => (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
2346
2349
  var membershipScopeKeys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId"];
2347
2350
  var membershipScope = (input) => Object.fromEntries(membershipScopeKeys.map((key3) => [key3, input[key3]]));
2348
2351
  function requireMembershipScope(row2, input) {
@@ -2353,18 +2356,18 @@ async function loadMembershipAuthorityState(db, input) {
2353
2356
  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");
2354
2357
  return row2;
2355
2358
  }
2356
- var membershipUpdate = (ns, id6, attrs) => ({ t: "update", ns, id: id6, attrs });
2359
+ var membershipUpdate = (ns, id7, attrs) => ({ t: "update", ns, id: id7, attrs });
2357
2360
  var membershipClock = (now) => {
2358
2361
  if (!Number.isSafeInteger(now) || now < 0) membershipFail("invalid_membership_clock");
2359
2362
  };
2360
2363
 
2361
2364
  // src/membership-restart-payment.ts
2362
2365
  async function membershipRestartPayment(db, target, member, now) {
2363
- const id6 = String(member.id), restart = await membershipLoad(db, "membershipRestart", id6);
2366
+ const id7 = String(member.id), restart = await membershipLoad(db, "membershipRestart", id7);
2364
2367
  requireMembershipScope(restart, target);
2365
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id6}:primary`);
2368
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id7}:primary`);
2366
2369
  requireMembershipScope(entitlement, target);
2367
- if (entitlement.membershipId !== id6 || entitlement.kind !== "primary") membershipFail("membership_restart_conflict");
2370
+ if (entitlement.membershipId !== id7 || entitlement.kind !== "primary") membershipFail("membership_restart_conflict");
2368
2371
  if (restart.status !== "ready" || member.status !== "restarting" || member.restartId !== restart.restartId || member.memberNumber !== restart.memberNumber || !Number.isSafeInteger(member.journeyApprovedAt)) membershipFail("membership_restart_conflict");
2369
2372
  return {
2370
2373
  restartId: String(restart.restartId),
@@ -2381,49 +2384,117 @@ async function membershipRestartPayment(db, target, member, now) {
2381
2384
  graceEndsAt: 0
2382
2385
  },
2383
2386
  ops: [
2384
- membershipUpdate("membershipRestart", id6, { status: "complete" }),
2385
- { t: "retract", ns: "networkMembership", id: id6, attrs: ["refundRequestedAt", "cancellationRequestedAt", "discountPolicyVersionId", "discountSnapshot"] }
2387
+ membershipUpdate("membershipRestart", id7, { status: "complete" }),
2388
+ { t: "retract", ns: "networkMembership", id: id7, attrs: ["refundRequestedAt", "cancellationRequestedAt", "discountPolicyVersionId", "discountSnapshot"] }
2386
2389
  ],
2387
2390
  guards: [
2388
- { ns: "networkMembership", id: id6, exists: true, equals: { status: "restarting", revision: member.revision, restartId: restart.restartId } },
2389
- { ns: "membershipRestart", id: id6, exists: true, equals: { status: "ready", restartId: restart.restartId } },
2391
+ { ns: "networkMembership", id: id7, exists: true, equals: { status: "restarting", revision: member.revision, restartId: restart.restartId } },
2392
+ { ns: "membershipRestart", id: id7, exists: true, equals: { status: "ready", restartId: restart.restartId } },
2390
2393
  { ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision, status: entitlement.status } }
2391
2394
  ]
2392
2395
  };
2393
2396
  }
2394
2397
 
2398
+ // src/checkout-named-seat.ts
2399
+ function checkoutNamedSeatSelection(value, purchaserEmail) {
2400
+ const row2 = value;
2401
+ const name = typeof row2?.recipientName === "string" ? row2.recipientName.trim().replace(/\s+/g, " ") : "";
2402
+ const email = normalizeChapterEmail(typeof row2?.recipientEmail === "string" ? row2.recipientEmail : "");
2403
+ const purchaser = normalizeChapterEmail(purchaserEmail);
2404
+ 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) {
2405
+ throw Object.assign(new Error("Enter a different recipient's name and email."), { code: "invalid_named_seat_recipient" });
2406
+ }
2407
+ return { recipientName: name, recipientEmail: email };
2408
+ }
2409
+ async function checkoutNamedSeatIdentity(target, email) {
2410
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([
2411
+ target.appId,
2412
+ target.chapterId,
2413
+ target.environment,
2414
+ target.runtime,
2415
+ email
2416
+ ])));
2417
+ return {
2418
+ seatId: (await membershipOfferIdForRequest({ ...target, requestId: "named_seat" })).replace(/^offer_/, "seat_"),
2419
+ membershipId: (await membershipOfferIdForRequest({ ...target, requestId: "checkout_binding" })).replace(/^offer_/, "membership_"),
2420
+ recipientClaimId: `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`
2421
+ };
2422
+ }
2423
+
2395
2424
  // src/membership-checkout-binding.ts
2396
2425
  var keys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId", "offerId", "offerDigest", "quoteDigest", "priceId"];
2397
2426
  var reject2 = (code) => {
2398
2427
  throw Object.assign(new Error(code), { code });
2399
2428
  };
2400
- var load2 = async (db, ns, id6) => (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
2429
+ var load2 = async (db, ns, id7) => (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
2401
2430
  async function membershipCheckoutBindingId(input) {
2402
2431
  return (await membershipOfferIdForRequest({ ...input, requestId: "checkout_binding" })).replace(/^offer_/, "checkout_");
2403
2432
  }
2404
2433
  async function verifyMembershipCheckoutBinding(value, input) {
2405
2434
  const row2 = value;
2406
2435
  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");
2407
- return { id: row2.id, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: row2.boundAt };
2436
+ if (Boolean(row2.namedSeat) !== Boolean(input.namedSeat)) return reject2("membership_checkout_conflict");
2437
+ if (input.namedSeat) {
2438
+ const expected = {
2439
+ ...checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail),
2440
+ purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail)
2441
+ };
2442
+ const identity2 = await checkoutNamedSeatIdentity(input, expected.recipientEmail);
2443
+ 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");
2444
+ }
2445
+ return {
2446
+ id: row2.id,
2447
+ ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])),
2448
+ boundAt: row2.boundAt,
2449
+ ...row2.namedSeat ? { namedSeat: row2.namedSeat } : {}
2450
+ };
2408
2451
  }
2409
- async function bindMembershipCheckout(db, input, now) {
2452
+ async function bindMembershipCheckout(db, input, now, seatPriceId) {
2410
2453
  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");
2411
2454
  const stateId = `${input.chapterId}:${input.environment}`;
2412
2455
  const state = await load2(db, "membershipAuthorityState", stateId);
2413
2456
  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");
2414
- const id6 = await membershipCheckoutBindingId(input);
2415
- const existing = await load2(db, "membershipOfferCheckout", id6);
2457
+ const id7 = await membershipCheckoutBindingId(input);
2458
+ const existing = await load2(db, "membershipOfferCheckout", id7);
2416
2459
  if (existing) return verifyMembershipCheckoutBinding(existing, input);
2417
2460
  const row2 = await load2(db, "membershipCheckoutOffer", input.offerId);
2418
2461
  const offer = await verifyIssuedMembershipOffer(row2, { ...input, now, lifetimeMs: 6e4 });
2419
2462
  if (offer.digest !== input.offerDigest || offer.expiresAt <= now || offer.issuedAt > now) return reject2("membership_offer_expired");
2420
- const restart = await load2(db, "membershipRestart", id6.replace(/^checkout_/, "membership_"));
2421
- const receipt = { id: id6, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: now };
2463
+ const restart = await load2(db, "membershipRestart", id7.replace(/^checkout_/, "membership_"));
2464
+ let namedSeat;
2465
+ if (input.namedSeat) {
2466
+ if (!seatPriceId || !/^price_[A-Za-z0-9_]+$/.test(seatPriceId) || seatPriceId === input.priceId || restart) return reject2("named_seat_purchase_unavailable");
2467
+ const selection = checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail);
2468
+ namedSeat = {
2469
+ ...selection,
2470
+ purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail),
2471
+ ...await checkoutNamedSeatIdentity(input, selection.recipientEmail),
2472
+ priceId: seatPriceId,
2473
+ amountCents: offer.contract.offering.seatPriceCents
2474
+ };
2475
+ }
2476
+ const receipt = { id: id7, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: now, ...namedSeat ? { namedSeat } : {} };
2422
2477
  try {
2423
- await db.transact([{ t: "update", ns: "membershipOfferCheckout", id: id6, attrs: receipt }], {
2424
- mutationId: `membership-checkout:${id6}${restart?.restartId ? `:${restart.restartId}` : ""}`,
2478
+ await db.transact([
2479
+ { t: "update", ns: "membershipOfferCheckout", id: id7, attrs: receipt },
2480
+ ...namedSeat ? [{ t: "update", ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, attrs: {
2481
+ id: namedSeat.recipientClaimId,
2482
+ appId: input.appId,
2483
+ chapterId: input.chapterId,
2484
+ environment: input.environment,
2485
+ runtime: input.runtime,
2486
+ seatId: namedSeat.seatId,
2487
+ recipientEmailNormalized: namedSeat.recipientEmail,
2488
+ createdAt: now
2489
+ } }] : []
2490
+ ], {
2491
+ mutationId: `membership-checkout:${id7}${restart?.restartId ? `:${restart.restartId}` : ""}`,
2425
2492
  guards: [
2426
- { ns: "membershipOfferCheckout", id: id6, exists: false },
2493
+ { ns: "membershipOfferCheckout", id: id7, exists: false },
2494
+ ...namedSeat ? [
2495
+ { ns: "networkSeat", id: namedSeat.seatId, exists: false },
2496
+ { ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, exists: false }
2497
+ ] : [],
2427
2498
  { ns: "membershipCheckoutOffer", id: input.offerId, exists: true, equals: { digest: input.offerDigest } },
2428
2499
  { ns: "membershipAuthorityState", id: stateId, exists: true, equals: { mode: "authoritative", revision: state.revision } }
2429
2500
  ]
@@ -2431,11 +2502,78 @@ async function bindMembershipCheckout(db, input, now) {
2431
2502
  } catch (error) {
2432
2503
  if (error.code !== "transact_guard_failed") throw error;
2433
2504
  }
2434
- const saved = await load2(db, "membershipOfferCheckout", id6);
2505
+ const saved = await load2(db, "membershipOfferCheckout", id7);
2435
2506
  if (!saved) return reject2("membership_binding_unconfirmed");
2436
2507
  return verifyMembershipCheckoutBinding(saved, input);
2437
2508
  }
2438
2509
 
2510
+ // src/membership-checkout-seat-payment.ts
2511
+ function checkoutSeatPaymentOperations(target, seat, input) {
2512
+ const { now, paidAt, endsAt } = input;
2513
+ const included = seat.amountCents === 0, entitlementId = `${seat.seatId}:entitlement`;
2514
+ const effectId = `${seat.seatId}:${included ? "invite" : "attach-renewal"}`;
2515
+ return [
2516
+ membershipUpdate("networkSeat", seat.seatId, {
2517
+ id: seat.seatId,
2518
+ ...membershipScope(target),
2519
+ primaryMembershipId: seat.membershipId,
2520
+ purchaserEmailNormalized: seat.purchaserEmail,
2521
+ recipientName: seat.recipientName,
2522
+ recipientEmailNormalized: seat.recipientEmail,
2523
+ recipientClaimId: seat.recipientClaimId,
2524
+ priceId: seat.priceId,
2525
+ primaryPriceId: input.primaryPriceId,
2526
+ amountCents: seat.amountCents,
2527
+ currency: input.offering.currency,
2528
+ interval: input.offering.interval,
2529
+ providerCustomerId: input.customerId,
2530
+ providerSubscriptionId: input.subscriptionId,
2531
+ combinedInvoice: { invoiceId: input.invoiceId, totalCents: input.invoiceTotalCents },
2532
+ status: included ? "pending_acceptance" : "renewal_pending",
2533
+ preparedAt: input.boundAt,
2534
+ purchasedAt: paidAt,
2535
+ termStartedAt: paidAt,
2536
+ termEndsAt: endsAt,
2537
+ acceptanceEndsAt: endsAt,
2538
+ seatPurchaseEndsAt: seatPurchaseDeadline(input.offering, { initialTermStartedAt: paidAt, termEndsAt: endsAt }),
2539
+ offeringSnapshot: input.offering,
2540
+ paymentEventId: input.eventId,
2541
+ ...included ? { entitlementId } : {},
2542
+ revision: 1,
2543
+ createdAt: now,
2544
+ updatedAt: now
2545
+ }),
2546
+ ...included ? [membershipUpdate("networkEntitlement", entitlementId, {
2547
+ id: entitlementId,
2548
+ ...membershipScope(target),
2549
+ membershipId: seat.membershipId,
2550
+ seatId: seat.seatId,
2551
+ kind: "named_seat",
2552
+ status: "pending_acceptance",
2553
+ termStartedAt: paidAt,
2554
+ termEndsAt: endsAt,
2555
+ offeringSnapshot: input.offering,
2556
+ sourceEventId: input.eventId,
2557
+ revision: 1,
2558
+ createdAt: now,
2559
+ updatedAt: now
2560
+ })] : [],
2561
+ membershipUpdate("membershipEffect", effectId, {
2562
+ id: effectId,
2563
+ chapterId: target.chapterId,
2564
+ environment: target.environment,
2565
+ runtime: target.runtime,
2566
+ membershipId: seat.membershipId,
2567
+ seatId: seat.seatId,
2568
+ kind: included ? "invite_named_recipient" : "attach_seat_renewal",
2569
+ status: "pending",
2570
+ attempts: 0,
2571
+ createdAt: now,
2572
+ updatedAt: now
2573
+ })
2574
+ ];
2575
+ }
2576
+
2439
2577
  // src/membership-payment-authority.ts
2440
2578
  async function networkMembershipId(input) {
2441
2579
  return (await membershipCheckoutBindingId(input)).replace(/^checkout_/, "membership_");
@@ -2451,11 +2589,14 @@ async function confirmMembershipPayment(db, input, now) {
2451
2589
  { ...input, offerId: String(binding.offerId), now, lifetimeMs: 6e4 }
2452
2590
  );
2453
2591
  if (binding.offerDigest !== offer.digest) membershipFail("membership_checkout_conflict");
2592
+ const { namedSeat } = await verifyMembershipCheckoutBinding(binding, binding);
2593
+ if (namedSeat && namedSeat.amountCents !== offer.contract.offering.seatPriceCents) membershipFail("membership_checkout_conflict");
2594
+ const totalCents = offer.contract.dueTodayCents + (namedSeat?.amountCents ?? 0);
2454
2595
  const eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
2455
2596
  if (!eventRow) membershipFail("membership_payment_evidence_pending");
2456
2597
  const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow.digest);
2457
2598
  const fact = eventRow.fact;
2458
- 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 !== offer.contract.dueTodayCents || fact.amountPaidCents !== offer.contract.dueTodayCents || Object.entries({
2599
+ 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({
2459
2600
  appId: input.appId,
2460
2601
  chapterId: input.chapterId,
2461
2602
  environment: input.environment,
@@ -2467,14 +2608,14 @@ async function confirmMembershipPayment(db, input, now) {
2467
2608
  const paidAt = fact.invoicePaidAt;
2468
2609
  const endsAt = offer.contract.initialTermEndsAt ?? fact.invoicePeriodEndsAt;
2469
2610
  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");
2470
- const id6 = await networkMembershipId(input), entitlementId = `${id6}:primary`;
2471
- let member = await membershipLoad(db, "networkMembership", id6);
2611
+ const id7 = await networkMembershipId(input), entitlementId = `${id7}:primary`;
2612
+ let member = await membershipLoad(db, "networkMembership", id7);
2472
2613
  if (!member || member.status === "restarting") {
2473
2614
  const offering = offer.contract.offering;
2474
2615
  const restart = member ? await membershipRestartPayment(db, input, member, now) : null;
2475
2616
  const attrs = {
2476
- id: id6,
2477
- membershipId: id6,
2617
+ id: id7,
2618
+ membershipId: id7,
2478
2619
  ...membershipScope(input),
2479
2620
  baseSkuId: input.skuId,
2480
2621
  status: "paid_pending_approval",
@@ -2490,8 +2631,9 @@ async function confirmMembershipPayment(db, input, now) {
2490
2631
  founding: offer.contract.founding,
2491
2632
  cohort: offer.contract.founding ? offer.contract.discountPolicy.cohortLabel : "standard",
2492
2633
  baseAmountCents: offering.basePriceCents,
2493
- amountCents: offer.contract.dueTodayCents,
2634
+ amountCents: totalCents,
2494
2635
  renewalAmountCents: offer.contract.renewalCents,
2636
+ ...namedSeat ? { namedSeatId: namedSeat.seatId } : {},
2495
2637
  currency: offering.currency,
2496
2638
  interval: offering.interval,
2497
2639
  initialTermStartedAt: paidAt,
@@ -2508,11 +2650,11 @@ async function confirmMembershipPayment(db, input, now) {
2508
2650
  };
2509
2651
  try {
2510
2652
  await db.transact([
2511
- membershipUpdate("networkMembership", id6, attrs),
2653
+ membershipUpdate("networkMembership", id7, attrs),
2512
2654
  membershipUpdate("networkEntitlement", entitlementId, {
2513
2655
  id: entitlementId,
2514
2656
  ...membershipScope(input),
2515
- membershipId: id6,
2657
+ membershipId: id7,
2516
2658
  kind: "primary",
2517
2659
  status: restart?.attrs.status === "active" ? "active" : "pending_approval",
2518
2660
  termStartedAt: paidAt,
@@ -2524,23 +2666,45 @@ async function confirmMembershipPayment(db, input, now) {
2524
2666
  updatedAt: now,
2525
2667
  ...restart?.entitlementAttrs
2526
2668
  }),
2527
- ...restart?.ops ?? []
2528
- ], { mutationId: `membership-paid:${id6}${restart ? `:${restart.restartId}` : ""}`, guards: [
2529
- ...restart?.guards ?? [{ ns: "networkMembership", id: id6, exists: false }],
2669
+ ...restart?.ops ?? [],
2670
+ ...namedSeat ? checkoutSeatPaymentOperations(input, namedSeat, {
2671
+ offering,
2672
+ primaryPriceId: String(binding.priceId),
2673
+ invoiceId: fact.invoiceId,
2674
+ invoiceTotalCents: totalCents,
2675
+ subscriptionId: input.providerSubscriptionId,
2676
+ customerId: input.providerCustomerId,
2677
+ eventId: input.eventId,
2678
+ paidAt: Number(paidAt),
2679
+ endsAt: Number(endsAt),
2680
+ boundAt: Number(binding.boundAt),
2681
+ now
2682
+ }) : []
2683
+ ], { mutationId: `membership-paid:${id7}${restart ? `:${restart.restartId}` : ""}`, guards: [
2684
+ ...restart?.guards ?? [{ ns: "networkMembership", id: id7, exists: false }],
2685
+ ...namedSeat ? [
2686
+ { ns: "networkSeat", id: namedSeat.seatId, exists: false },
2687
+ {
2688
+ ns: "networkSeatRecipient",
2689
+ id: namedSeat.recipientClaimId,
2690
+ exists: true,
2691
+ equals: { seatId: namedSeat.seatId, recipientEmailNormalized: namedSeat.recipientEmail }
2692
+ }
2693
+ ] : [],
2530
2694
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
2531
- { ns: "membershipOfferCheckout", id: String(binding.id), exists: true, equals: { offerDigest: offer.digest } },
2695
+ { ns: "membershipOfferCheckout", id: String(binding.id), exists: true, equals: { offerDigest: offer.digest, quoteDigest: binding.quoteDigest } },
2532
2696
  { ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
2533
2697
  evidenceGuard
2534
2698
  ] });
2535
2699
  } catch (error) {
2536
2700
  if (error.code !== "transact_guard_failed") throw error;
2537
2701
  }
2538
- member = await membershipLoad(db, "networkMembership", id6);
2702
+ member = await membershipLoad(db, "networkMembership", id7);
2539
2703
  }
2540
2704
  requireMembershipScope(member, input);
2541
2705
  if (member.purchaseOfferDigest !== offer.digest || member.providerInitialInvoiceId !== fact.invoiceId || member.providerSubscriptionId !== fact.subscriptionId || member.providerCustomerId !== fact.customerId) membershipFail("membership_purchase_conflict");
2542
2706
  return {
2543
- membershipId: id6,
2707
+ membershipId: id7,
2544
2708
  status: String(member.status),
2545
2709
  cohort: String(member.cohort),
2546
2710
  initialTermStartedAt: Number(member.initialTermStartedAt),
@@ -2611,19 +2775,19 @@ async function stopPreviousSubscription(member, target, secret) {
2611
2775
  async function prepareMembershipRestart(db, input, now, stripeSecret) {
2612
2776
  membershipClock(now);
2613
2777
  if (!/^sub_[A-Za-z0-9_]+$/.test(input.previousSubscriptionId)) membershipFail("membership_restart_conflict");
2614
- const id6 = await networkMembershipId(input), bindingId = await membershipCheckoutBindingId(input);
2615
- const restartId = `restart_${await stripePayloadDigest(JSON.stringify([id6, input.previousSubscriptionId]))}`;
2778
+ const id7 = await networkMembershipId(input), bindingId = await membershipCheckoutBindingId(input);
2779
+ const restartId = `restart_${await stripePayloadDigest(JSON.stringify([id7, input.previousSubscriptionId]))}`;
2616
2780
  const state = await loadMembershipAuthorityState(db, input);
2617
- const previous = await membershipLoad(db, "networkMembership", id6);
2781
+ const previous = await membershipLoad(db, "networkMembership", id7);
2618
2782
  requireMembershipScope(previous, input);
2619
2783
  const receipt = {
2620
2784
  applicationId: input.applicationId,
2621
- membershipId: id6,
2785
+ membershipId: id7,
2622
2786
  restartId,
2623
2787
  memberNumber: Number(previous.memberNumber),
2624
2788
  status: "ready"
2625
2789
  };
2626
- const existing = await membershipLoad(db, "membershipRestart", id6);
2790
+ const existing = await membershipLoad(db, "membershipRestart", id7);
2627
2791
  if (existing?.restartId === restartId && existing.status === "ready") {
2628
2792
  requireMembershipScope(existing, input);
2629
2793
  return receipt;
@@ -2639,33 +2803,33 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
2639
2803
  membershipUpdate("membershipPurchaseHistory", restartId, {
2640
2804
  id: restartId,
2641
2805
  ...membershipScope(input),
2642
- membershipId: id6,
2806
+ membershipId: id7,
2643
2807
  providerSubscriptionId: input.previousSubscriptionId,
2644
2808
  memberSnapshot: previous,
2645
2809
  bindingSnapshot: binding2,
2646
2810
  archivedAt: now
2647
2811
  }),
2648
- membershipUpdate("networkMembership", id6, { status: "restarting", restartId, revision: Number(previous.revision) + 1, updatedAt: now })
2812
+ membershipUpdate("networkMembership", id7, { status: "restarting", restartId, revision: Number(previous.revision) + 1, updatedAt: now })
2649
2813
  ], { mutationId: `membership-restart-reserve:${restartId}`, guards: [
2650
2814
  ...seats.guards,
2651
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: previous.revision, status: previous.status } },
2815
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: previous.revision, status: previous.status } },
2652
2816
  { ns: "membershipPurchaseHistory", id: restartId, exists: false },
2653
2817
  { ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding2.offerDigest } },
2654
2818
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
2655
2819
  ] });
2656
2820
  } else if (previous.restartId !== restartId) membershipFail("membership_restart_conflict");
2657
- const reserved = await membershipLoad(db, "networkMembership", id6);
2821
+ const reserved = await membershipLoad(db, "networkMembership", id7);
2658
2822
  requireMembershipScope(reserved, input);
2659
2823
  if (reserved.status !== "restarting" || reserved.restartId !== restartId) membershipFail("membership_restart_conflict");
2660
2824
  await stopPreviousSubscription(reserved, input, stripeSecret);
2661
2825
  const binding = await membershipLoad(db, "membershipOfferCheckout", bindingId);
2662
2826
  requireMembershipScope(binding, input);
2663
2827
  await db.transact([
2664
- membershipUpdate("membershipRestart", id6, { id: id6, ...membershipScope(input), ...receipt, previousSubscriptionId: input.previousSubscriptionId, createdAt: now }),
2828
+ membershipUpdate("membershipRestart", id7, { id: id7, ...membershipScope(input), ...receipt, previousSubscriptionId: input.previousSubscriptionId, createdAt: now }),
2665
2829
  { t: "delete", ns: "membershipOfferCheckout", id: bindingId }
2666
2830
  ], { mutationId: `membership-restart-ready:${restartId}`, guards: [
2667
- { ns: "membershipRestart", id: id6, exists: Boolean(existing), ...existing ? { equals: { restartId: existing.restartId, status: existing.status } } : {} },
2668
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: reserved.revision, status: "restarting", restartId } },
2831
+ { ns: "membershipRestart", id: id7, exists: Boolean(existing), ...existing ? { equals: { restartId: existing.restartId, status: existing.status } } : {} },
2832
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: reserved.revision, status: "restarting", restartId } },
2669
2833
  { ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding.offerDigest } }
2670
2834
  ] });
2671
2835
  return receipt;
@@ -2675,15 +2839,15 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
2675
2839
  async function readMembershipAccess(db, input, now) {
2676
2840
  membershipClock(now);
2677
2841
  await loadMembershipAuthorityState(db, input);
2678
- const id6 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id6);
2679
- if (!member) return { membershipId: id6, allowed: false, status: "unpaid", accessEndsAt: 0, revision: 0 };
2842
+ const id7 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id7);
2843
+ if (!member) return { membershipId: id7, allowed: false, status: "unpaid", accessEndsAt: 0, revision: 0 };
2680
2844
  requireMembershipScope(member, input);
2681
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id6}:primary`);
2845
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id7}:primary`);
2682
2846
  requireMembershipScope(entitlement, input);
2683
2847
  const endsAt = member.status === "payment_grace" ? Number(member.graceEndsAt) : Number(member.termEndsAt);
2684
- 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 === id6 && entitlement.kind === "primary" && Number.isSafeInteger(endsAt) && endsAt > now && Number(member.termStartedAt) <= now;
2848
+ 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 === id7 && entitlement.kind === "primary" && Number.isSafeInteger(endsAt) && endsAt > now && Number(member.termStartedAt) <= now;
2685
2849
  return {
2686
- membershipId: id6,
2850
+ membershipId: id7,
2687
2851
  allowed: allowed2,
2688
2852
  status: String(member.status),
2689
2853
  accessEndsAt: Number.isSafeInteger(endsAt) ? endsAt : 0,
@@ -2801,7 +2965,7 @@ async function refundChapterNetworkMembership(db, env, ctx, app) {
2801
2965
  const target = await membershipTargetForIntent(intent, app, env, ctx);
2802
2966
  const receipt = await ctx.membershipAuthority.refundMembership(target, env);
2803
2967
  const offer = intent.membershipOffer;
2804
- if (receipt.membershipId !== await networkMembershipId(target) || receipt.status !== "refunded" || receipt.subscriptionCanceled !== true || receipt.refundedCents !== offer.contract?.dueTodayCents) throw new Error("membership refund unconfirmed");
2968
+ if (receipt.membershipId !== await networkMembershipId(target) || receipt.status !== "refunded" || receipt.subscriptionCanceled !== true || receipt.refundedCents !== Number(offer.contract?.dueTodayCents) + Number(intent.paymentQuote?.lineItems?.namedSeatCents ?? 0)) throw new Error("membership refund unconfirmed");
2805
2969
  return receipt;
2806
2970
  }
2807
2971
  async function renewChapterNetworkMembership(db, env, ctx, app, eventId) {
@@ -2852,14 +3016,14 @@ function facts(snapshots) {
2852
3016
  ["payment_intent.updated", snapshots.payments?.objects ?? []]
2853
3017
  ];
2854
3018
  return groups.flatMap(([eventType, objects]) => objects.map((object2) => {
2855
- const id6 = typeof object2.id === "string" ? object2.id : "invalid";
3019
+ const id7 = typeof object2.id === "string" ? object2.id : "invalid";
2856
3020
  return normalizeNetworkCommercialStripeEvent({
2857
- id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id6}`,
3021
+ id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id7}`,
2858
3022
  type: eventType,
2859
3023
  created: typeof object2.created === "number" ? object2.created : 0,
2860
3024
  livemode: object2.livemode === true,
2861
3025
  data: { object: object2 }
2862
- }, `stripe-snapshot:${eventType}:${id6}`);
3026
+ }, `stripe-snapshot:${eventType}:${id7}`);
2863
3027
  }));
2864
3028
  }
2865
3029
  var selected = (row2) => ({
@@ -2934,9 +3098,9 @@ async function chapterBillingSubscriptions(secretKey, chapterId, env) {
2934
3098
  // src/approve-application.ts
2935
3099
  async function approveApplication(input) {
2936
3100
  const { db, env, ctx } = input;
2937
- const id6 = String(input.app.id ?? "");
2938
- if (!id6) return { ok: false, status: 404, error: "not found" };
2939
- const app = (await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } })).applications?.[0];
3101
+ const id7 = String(input.app.id ?? "");
3102
+ if (!id7) return { ok: false, status: 404, error: "not found" };
3103
+ const app = (await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } })).applications?.[0];
2940
3104
  if (!app || !chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME)) {
2941
3105
  return { ok: false, status: 404, error: "not found" };
2942
3106
  }
@@ -2970,10 +3134,10 @@ async function approveApplication(input) {
2970
3134
  try {
2971
3135
  if (!duplicate) {
2972
3136
  const write = await db.transact(
2973
- [{ t: "update", ns: "applications", id: id6, attrs: { status: "approved", approvalEffectsPending: true, approvalEffectsOrigin: input.origin } }],
2974
- { mutationId: `approve:${id6}`, guards: [{
3137
+ [{ t: "update", ns: "applications", id: id7, attrs: { status: "approved", approvalEffectsPending: true, approvalEffectsOrigin: input.origin } }],
3138
+ { mutationId: `approve:${id7}`, guards: [{
2975
3139
  ns: "applications",
2976
- id: id6,
3140
+ id: id7,
2977
3141
  exists: true,
2978
3142
  equals: Object.fromEntries(guardFields.filter((key3) => app[key3] !== void 0).map((key3) => [key3, app[key3]])),
2979
3143
  absent: guardFields.filter((key3) => app[key3] === void 0)
@@ -2985,7 +3149,7 @@ async function approveApplication(input) {
2985
3149
  if (error.code !== "transact_guard_failed") throw error;
2986
3150
  return { ok: false, status: 409, error: "application changed; reload before approving" };
2987
3151
  }
2988
- const current = (await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } })).applications?.[0];
3152
+ const current = (await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } })).applications?.[0];
2989
3153
  if (!current || current.status !== "approved" || current.canceled === true || guardFields.filter((key3) => key3 !== "status").some((key3) => current[key3] !== app[key3])) {
2990
3154
  return { ok: false, status: 409, error: "application changed; approval effects were not applied" };
2991
3155
  }
@@ -2995,7 +3159,7 @@ async function approveApplication(input) {
2995
3159
  } catch {
2996
3160
  return { ok: false, status: 503, error: "network membership approval reconciliation pending" };
2997
3161
  }
2998
- const afterAuthority = (await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } })).applications?.[0];
3162
+ const afterAuthority = (await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } })).applications?.[0];
2999
3163
  if (!afterAuthority || afterAuthority.status !== "approved" || afterAuthority.canceled === true || guardFields.filter((key3) => key3 !== "status").some((key3) => afterAuthority[key3] !== app[key3])) {
3000
3164
  return { ok: false, status: 409, error: "application changed during network approval" };
3001
3165
  }
@@ -3008,7 +3172,7 @@ async function approveApplication(input) {
3008
3172
  if (!userId && typeof app.email === "string") {
3009
3173
  const found = await clerkGetUserByEmail(secret, app.email, fetch, clerkRoleMetadata(ctx.chapter.auth));
3010
3174
  userId = found?.id ?? null;
3011
- if (userId) await db.transact([{ t: "update", ns: "applications", id: id6, attrs: { clerkUserId: userId } }]);
3175
+ if (userId) await db.transact([{ t: "update", ns: "applications", id: id7, attrs: { clerkUserId: userId } }]);
3012
3176
  }
3013
3177
  if (userId) {
3014
3178
  const user = await clerkGetUser(secret, userId, fetch, clerkRoleMetadata(ctx.chapter.auth));
@@ -3030,8 +3194,8 @@ async function approveApplication(input) {
3030
3194
  template: send,
3031
3195
  to: app.email,
3032
3196
  vars: { firstName: String(app.firstName ?? ""), membersUrl: `${input.origin}/members/` },
3033
- applicationId: id6,
3034
- dedupeKey: `approve:${id6}`
3197
+ applicationId: id7,
3198
+ dedupeKey: `approve:${id7}`
3035
3199
  }
3036
3200
  );
3037
3201
  emailLogged = result.sent;
@@ -3039,10 +3203,10 @@ async function approveApplication(input) {
3039
3203
  return { ok: false, status: 503, error: "approval notice reconciliation pending", rolePromoted, emailLogged };
3040
3204
  }
3041
3205
  }
3042
- await db.transact([{ t: "update", ns: "applications", id: id6, attrs: { approvalEffectsPending: false } }], {
3206
+ await db.transact([{ t: "update", ns: "applications", id: id7, attrs: { approvalEffectsPending: false } }], {
3043
3207
  guards: [{
3044
3208
  ns: "applications",
3045
- id: id6,
3209
+ id: id7,
3046
3210
  exists: true,
3047
3211
  equals: { status: "approved", ...app.canceled !== void 0 ? { canceled: app.canceled } : {} },
3048
3212
  ...app.canceled === void 0 ? { absent: ["canceled"] } : {}
@@ -3264,10 +3428,10 @@ async function redeemGiftClaim(db, claim, beneficiary, now) {
3264
3428
  tierId: target.tierId,
3265
3429
  ...typeof existing?.clerkUserId === "string" ? { clerkUserId: existing.clerkUserId } : {}
3266
3430
  };
3267
- const id6 = existing ? String(existing.id) : `gift-application:${String(claim.id)}`;
3431
+ const id7 = existing ? String(existing.id) : `gift-application:${String(claim.id)}`;
3268
3432
  const status2 = existing && existing.status !== "submitted" ? String(existing.status) : "paid_pending_vetting";
3269
- ops.push({ t: "update", ns: "applications", id: id6, attrs: {
3270
- id: id6,
3433
+ ops.push({ t: "update", ns: "applications", id: id7, attrs: {
3434
+ id: id7,
3271
3435
  groupId: chapterId,
3272
3436
  email: beneficiary.email,
3273
3437
  emailNormalized,
@@ -3278,12 +3442,12 @@ async function redeemGiftClaim(db, claim, beneficiary, now) {
3278
3442
  clerkUserId: beneficiary.userId,
3279
3443
  ...existing ? {} : { createdAt: now }
3280
3444
  } });
3281
- result = { kind: "application", id: id6 };
3445
+ result = { kind: "application", id: id7 };
3282
3446
  } else {
3283
3447
  if (beneficiary.applicationId) return null;
3284
- const id6 = `gift-entitlement:${String(claim.id)}`;
3285
- ops.push({ t: "update", ns: "giftEntitlements", id: id6, attrs: {
3286
- id: id6,
3448
+ const id7 = `gift-entitlement:${String(claim.id)}`;
3449
+ ops.push({ t: "update", ns: "giftEntitlements", id: id7, attrs: {
3450
+ id: id7,
3287
3451
  claimId: String(claim.id),
3288
3452
  orderId: String(claim.orderId),
3289
3453
  chapterId,
@@ -3293,7 +3457,7 @@ async function redeemGiftClaim(db, claim, beneficiary, now) {
3293
3457
  active: true,
3294
3458
  createdAt: now
3295
3459
  } });
3296
- result = { kind: "entitlement", id: id6 };
3460
+ result = { kind: "entitlement", id: id7 };
3297
3461
  }
3298
3462
  ops.unshift({ t: "update", ns: "giftClaims", id: String(claim.id), attrs: {
3299
3463
  status: "redeemed",
@@ -3467,9 +3631,9 @@ async function storeStripeReceipt(db, input) {
3467
3631
  return "stored";
3468
3632
  }
3469
3633
  if (existing) return existing.digest === input.digest || existing.digest === input.legacyDigest ? "replayed" : "collision";
3470
- const id6 = `stripe-event:${input.eventId}`;
3471
- const write = await db.transact([{ t: "update", ns: "stripeEventReceipts", id: id6, attrs: {
3472
- id: id6,
3634
+ const id7 = `stripe-event:${input.eventId}`;
3635
+ const write = await db.transact([{ t: "update", ns: "stripeEventReceipts", id: id7, attrs: {
3636
+ id: id7,
3473
3637
  eventId: input.eventId,
3474
3638
  digest: input.digest,
3475
3639
  disposition: input.disposition,
@@ -3701,9 +3865,9 @@ var EVENT_ID = /^evt_[A-Za-z0-9_]{1,150}$/;
3701
3865
  var fail3 = (message, code) => Object.assign(new Error(message), { code });
3702
3866
  async function resolveNetworkCommercialCollision(db, sources, input, now = Date.now(), alerts) {
3703
3867
  if (!EVENT_ID.test(input.eventId)) throw fail3("invalid event reference", "invalid_event");
3704
- const id6 = exceptionId(input.eventId, "collision"), reviewId = `resolve:${input.mutationId}`;
3868
+ const id7 = exceptionId(input.eventId, "collision"), reviewId = `resolve:${input.mutationId}`;
3705
3869
  const found = await db.query({
3706
- networkCommercialExceptions: { $: { where: { id: id6 }, limit: 1 } },
3870
+ networkCommercialExceptions: { $: { where: { id: id7 }, limit: 1 } },
3707
3871
  networkCommercialEvents: { $: { where: { eventId: input.eventId }, limit: 1 } }
3708
3872
  });
3709
3873
  const exception = found.networkCommercialExceptions?.[0], stored = found.networkCommercialEvents?.[0];
@@ -3726,7 +3890,7 @@ async function resolveNetworkCommercialCollision(db, sources, input, now = Date.
3726
3890
  action: "resolve",
3727
3891
  reason: input.reason
3728
3892
  } },
3729
- { t: "update", ns: "networkCommercialExceptions", id: id6, attrs: {
3893
+ { t: "update", ns: "networkCommercialExceptions", id: id7, attrs: {
3730
3894
  status: "resolved",
3731
3895
  resolvedDigest: stored.digest,
3732
3896
  ...dismissed ? { reviewedDigest: dismissed } : {},
@@ -3735,7 +3899,7 @@ async function resolveNetworkCommercialCollision(db, sources, input, now = Date.
3735
3899
  updatedAt: now
3736
3900
  } }
3737
3901
  ], { mutationId: input.mutationId, guards: [
3738
- { ns: "networkCommercialExceptions", id: id6, exists: true, equals: { status: "active", updatedAt: exception.updatedAt } }
3902
+ { ns: "networkCommercialExceptions", id: id7, exists: true, equals: { status: "active", updatedAt: exception.updatedAt } }
3739
3903
  ] });
3740
3904
  if (write.duplicate) throw fail3("mutationId was already used for another change", "mutation_reused");
3741
3905
  const projection = await rebuildNetworkCommercialProjection(db, sources, `${input.mutationId}:rebuild`, now, alerts);
@@ -3850,10 +4014,10 @@ var handlePaymentRecovery = async (req, url, env, ctx) => {
3850
4014
  const provider = await readStripeRecoveryEvent(db, eventId);
3851
4015
  const event2 = normalizeWebhookEvent(provider.event, { requireProviderPayment: Boolean(ctx.membershipAuthority) });
3852
4016
  if (provider.event.livemode !== (env.ODLA_ENV === "prod") || classifyMemberStripeEvent(stripeEventIdentity(event2), target, false) !== "apply") return json({ error: "provider event belongs to another or unknown runtime" }, 409);
3853
- const id6 = crypto.randomUUID();
4017
+ const id7 = crypto.randomUUID();
3854
4018
  await db.transact([
3855
- { t: "update", ns: "paymentRecoveryReviews", id: id6, attrs: {
3856
- id: id6,
4019
+ { t: "update", ns: "paymentRecoveryReviews", id: id7, attrs: {
4020
+ id: id7,
3857
4021
  eventId,
3858
4022
  actorUserId: user.userId,
3859
4023
  reviewedAt: Date.now(),
@@ -3862,8 +4026,8 @@ var handlePaymentRecovery = async (req, url, env, ctx) => {
3862
4026
  scope: `${target.appId}:${target.environment}:${target.runtime}`,
3863
4027
  previousState: row2
3864
4028
  } },
3865
- { t: "update", ns: "stripeEventReceipts", id: String(row2.id), attrs: { retryReady: true, reviewedDigest: provider.digest, reviewId: id6 } }
3866
- ], { mutationId: `payment-recovery:${id6}`, guards: [{
4029
+ { t: "update", ns: "stripeEventReceipts", id: String(row2.id), attrs: { retryReady: true, reviewedDigest: provider.digest, reviewId: id7 } }
4030
+ ], { mutationId: `payment-recovery:${id7}`, guards: [{
3867
4031
  ns: "stripeEventReceipts",
3868
4032
  id: String(row2.id),
3869
4033
  exists: true,
@@ -3932,43 +4096,121 @@ async function closeRecipientApplication(db, env, ctx, seatId) {
3932
4096
  }
3933
4097
 
3934
4098
  // src/membership-seat-refund-stripe.ts
4099
+ var import_stripe7 = require("@odla-ai/stripe");
4100
+
4101
+ // src/membership-checkout-seat-refund.ts
3935
4102
  var import_stripe6 = require("@odla-ai/stripe");
3936
4103
  var fail4 = (code = "named_seat_refund_mismatch") => {
3937
4104
  throw Object.assign(new Error(code), { code });
3938
4105
  };
4106
+ var id2 = (value) => typeof value === "string" ? value : value?.id ?? "";
4107
+ async function refundCheckoutNamedSeat(secretKey, seat) {
4108
+ const invoiceRef = seat.combinedInvoice;
4109
+ 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();
4110
+ const call = async (method, path, params, key3) => {
4111
+ const result = await (0, import_stripe6.stripeRequest)({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
4112
+ if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
4113
+ return result.body;
4114
+ };
4115
+ const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId };
4116
+ const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
4117
+ if (sub.id !== seat.providerSubscriptionId || id2(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();
4118
+ const invoice = await call("GET", `/v1/invoices/${encodeURIComponent(invoiceRef.invoiceId)}`);
4119
+ if (invoice.id !== invoiceRef.invoiceId || id2(invoice.subscription ?? invoice.parent?.subscription_details?.subscription) !== sub.id || id2(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();
4120
+ let chargeId = id2(invoice.charge), paymentId = id2(invoice.payment_intent);
4121
+ if (!chargeId && !paymentId) {
4122
+ const payments = await call("GET", "/v1/invoice_payments", { invoice: invoice.id, status: "paid", limit: 100 });
4123
+ if (!Array.isArray(payments.data) || payments.data.length !== 1 || payments.has_more !== false) fail4();
4124
+ const payment = payments.data[0];
4125
+ if (id2(payment.invoice) !== invoice.id || payment.status !== "paid" || payment.payment?.type !== "payment_intent") fail4();
4126
+ paymentId = id2(payment.payment.payment_intent);
4127
+ }
4128
+ if (!chargeId) {
4129
+ if (!/^pi_[A-Za-z0-9_]+$/.test(paymentId)) fail4();
4130
+ const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(paymentId)}`);
4131
+ if (payment.id !== paymentId || id2(payment.customer) !== seat.providerCustomerId || payment.status !== "succeeded" || payment.amount_received !== invoiceRef.totalCents || payment.currency !== seat.currency || payment.livemode !== (seat.environment === "prod")) fail4();
4132
+ chargeId = id2(payment.latest_charge);
4133
+ }
4134
+ if (!/^ch_[A-Za-z0-9_]+$/.test(chargeId)) fail4();
4135
+ const charge = await call("GET", `/v1/charges/${encodeURIComponent(chargeId)}`);
4136
+ if (charge.id !== chargeId || id2(charge.customer) !== seat.providerCustomerId || charge.status !== "succeeded" || charge.livemode !== (seat.environment === "prod") || charge.amount !== invoiceRef.totalCents || charge.currency !== seat.currency || charge.invoice != null && id2(charge.invoice) !== invoice.id || paymentId && id2(charge.payment_intent) !== paymentId || !Number.isSafeInteger(charge.amount_refunded) || charge.amount_refunded < 0 || charge.amount_refunded > charge.amount) fail4();
4137
+ const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
4138
+ if (items.length > 1) fail4();
4139
+ if (items.length && sub.status !== "canceled") {
4140
+ const item = items[0];
4141
+ if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.quantity !== 1 || item.price?.id !== seat.priceId) fail4();
4142
+ const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
4143
+ if (removed.id !== item.id || removed.deleted !== true) fail4("named_seat_renewal_removal_unconfirmed");
4144
+ }
4145
+ if (charge.amount_refunded === charge.amount && charge.refunded === true) return { refundedCents: seat.amountCents, refundIds: [] };
4146
+ const list = await call("GET", "/v1/refunds", { charge: chargeId, limit: 100 });
4147
+ if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail4("named_seat_refund_history_incomplete");
4148
+ let refundedCents = 0;
4149
+ const refundIds = [];
4150
+ for (const refund of list.data) {
4151
+ if (id2(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();
4152
+ if (["pending", "requires_action"].includes(refund.status)) fail4("named_seat_refund_pending");
4153
+ if (refund.status === "succeeded") {
4154
+ if (refund.metadata?.seatId !== seat.id || refund.metadata?.membershipId !== seat.primaryMembershipId) fail4("named_seat_refund_allocation_ambiguous");
4155
+ refundedCents += refund.amount;
4156
+ refundIds.push(refund.id);
4157
+ }
4158
+ }
4159
+ if (refundedCents !== charge.amount_refunded || refundedCents > seat.amountCents) fail4();
4160
+ if (refundedCents < seat.amountCents) {
4161
+ const remaining = seat.amountCents - refundedCents;
4162
+ const refund = await call("POST", "/v1/refunds", {
4163
+ charge: chargeId,
4164
+ amount: remaining,
4165
+ reason: "requested_by_customer",
4166
+ metadata: { ...metadata, membershipId: seat.primaryMembershipId, seatId: seat.id }
4167
+ }, `combined-seat-refund:${seat.id}:${remaining}`);
4168
+ if (id2(refund.charge) !== chargeId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail4();
4169
+ if (refund.status !== "succeeded") fail4("named_seat_refund_pending");
4170
+ refundedCents += remaining;
4171
+ refundIds.push(refund.id);
4172
+ }
4173
+ return { refundedCents, refundIds };
4174
+ }
4175
+
4176
+ // src/membership-seat-refund-stripe.ts
4177
+ var fail5 = (code = "named_seat_refund_mismatch") => {
4178
+ throw Object.assign(new Error(code), { code });
4179
+ };
3939
4180
  async function refundNamedSeatStripePayment(secretKey, seat) {
3940
- if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail4();
4181
+ if (seat.combinedInvoice) return refundCheckoutNamedSeat(secretKey, seat);
4182
+ if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail5();
3941
4183
  const call = async (method, path, params, idempotencyKey) => {
3942
- const result = await (0, import_stripe6.stripeRequest)({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
3943
- if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
4184
+ const result = await (0, import_stripe7.stripeRequest)({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
4185
+ if (!result.ok || !result.body) return fail5("named_seat_refund_provider_unavailable");
3944
4186
  return result.body;
3945
4187
  };
3946
4188
  const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
3947
4189
  const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`);
3948
- 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)) fail4();
4190
+ 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();
3949
4191
  const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
3950
- 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)) fail4();
4192
+ 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();
3951
4193
  const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
3952
- if (items.length > 1) fail4();
4194
+ if (items.length > 1) fail5();
3953
4195
  if (items.length && sub.status !== "canceled") {
3954
4196
  const item = items[0];
3955
- if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail4();
4197
+ if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail5();
3956
4198
  const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
3957
- if (removed.id !== item.id || removed.deleted !== true) fail4("named_seat_renewal_removal_unconfirmed");
4199
+ if (removed.id !== item.id || removed.deleted !== true) fail5("named_seat_renewal_removal_unconfirmed");
3958
4200
  }
3959
4201
  const list = await call("GET", "/v1/refunds", { payment_intent: seat.providerPaymentIntentId, limit: 100 });
3960
- if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail4("named_seat_refund_history_incomplete");
4202
+ if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail5("named_seat_refund_history_incomplete");
3961
4203
  let refundedCents = 0, pendingCents = 0;
3962
4204
  const refundIds = [];
3963
4205
  for (const refund of list.data) {
3964
- 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)) fail4();
4206
+ 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();
3965
4207
  if (refund.status === "succeeded") {
3966
4208
  refundedCents += refund.amount;
3967
4209
  refundIds.push(refund.id);
3968
4210
  } else if (refund.status === "pending" || refund.status === "requires_action") pendingCents += refund.amount;
3969
4211
  }
3970
- if (refundedCents + pendingCents > seat.amountCents) fail4();
3971
- if (pendingCents) fail4("named_seat_refund_pending");
4212
+ if (refundedCents + pendingCents > seat.amountCents) fail5();
4213
+ if (pendingCents) fail5("named_seat_refund_pending");
3972
4214
  if (refundedCents < seat.amountCents) {
3973
4215
  const remaining = seat.amountCents - refundedCents;
3974
4216
  const refund = await call("POST", "/v1/refunds", {
@@ -3977,8 +4219,8 @@ async function refundNamedSeatStripePayment(secretKey, seat) {
3977
4219
  reason: "requested_by_customer",
3978
4220
  metadata
3979
4221
  }, `named-seat-refund:${seat.id}:${remaining}`);
3980
- if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail4();
3981
- if (refund.status !== "succeeded") fail4("named_seat_refund_pending");
4222
+ if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail5();
4223
+ if (refund.status !== "succeeded") fail5("named_seat_refund_pending");
3982
4224
  refundedCents += remaining;
3983
4225
  refundIds.push(refund.id);
3984
4226
  }
@@ -4000,8 +4242,8 @@ async function reserveNamedMembershipSeat(db, input, now) {
4000
4242
  const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
4001
4243
  requireMembershipScope(member, input);
4002
4244
  if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
4003
- const id6 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
4004
- let seat = await membershipLoad(db, "networkSeat", id6);
4245
+ const id7 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
4246
+ let seat = await membershipLoad(db, "networkSeat", id7);
4005
4247
  if (!seat) {
4006
4248
  const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
4007
4249
  const purchaseEndsAt = seatPurchaseDeadline(offering, member);
@@ -4009,7 +4251,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4009
4251
  membershipFail("named_seat_purchase_unavailable");
4010
4252
  }
4011
4253
  const attrs = {
4012
- id: id6,
4254
+ id: id7,
4013
4255
  ...membershipScope(input),
4014
4256
  primaryMembershipId: memberId,
4015
4257
  recipientName: name,
@@ -4034,20 +4276,20 @@ async function reserveNamedMembershipSeat(db, input, now) {
4034
4276
  };
4035
4277
  try {
4036
4278
  await db.transact([
4037
- membershipUpdate("networkSeat", id6, attrs),
4279
+ membershipUpdate("networkSeat", id7, attrs),
4038
4280
  membershipUpdate("networkSeatRecipient", claimId, {
4039
4281
  id: claimId,
4040
4282
  appId: input.appId,
4041
4283
  chapterId: input.chapterId,
4042
4284
  environment: input.environment,
4043
4285
  runtime: input.runtime,
4044
- seatId: id6,
4286
+ seatId: id7,
4045
4287
  recipientEmailNormalized: email,
4046
4288
  createdAt: now
4047
4289
  }),
4048
- membershipUpdate("networkMembership", memberId, { namedSeatId: id6, revision: Number(member.revision) + 1, updatedAt: now })
4049
- ], { mutationId: `named-seat-reserve:${id6}`, guards: [
4050
- { ns: "networkSeat", id: id6, exists: false },
4290
+ membershipUpdate("networkMembership", memberId, { namedSeatId: id7, revision: Number(member.revision) + 1, updatedAt: now })
4291
+ ], { mutationId: `named-seat-reserve:${id7}`, guards: [
4292
+ { ns: "networkSeat", id: id7, exists: false },
4051
4293
  { ns: "networkSeatRecipient", id: claimId, exists: false },
4052
4294
  { ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
4053
4295
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
@@ -4055,7 +4297,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4055
4297
  } catch (error) {
4056
4298
  if (error.code !== "transact_guard_failed") throw error;
4057
4299
  }
4058
- seat = await membershipLoad(db, "networkSeat", id6);
4300
+ seat = await membershipLoad(db, "networkSeat", id7);
4059
4301
  if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
4060
4302
  }
4061
4303
  requireMembershipScope(seat, input);
@@ -4065,7 +4307,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4065
4307
 
4066
4308
  // src/membership-seat-included.ts
4067
4309
  var namedSeatIncluded = (seat) => Number(seat.amountCents) === 0;
4068
- var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean(seat.providerPaymentIntentId && seat.providerSubscriptionItemId);
4310
+ var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean((seat.providerPaymentIntentId || seat.combinedInvoice) && seat.providerSubscriptionItemId);
4069
4311
  function namedSeatRecipientState(seat) {
4070
4312
  switch (String(seat.status)) {
4071
4313
  case "renewal_pending":
@@ -4169,7 +4411,7 @@ async function requestNamedSeatRefund(db, input, now) {
4169
4411
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
4170
4412
  requireMembershipScope(member, input);
4171
4413
  if (seat.status === "refunded" || seat.status === "refund_pending") return;
4172
- 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");
4414
+ 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");
4173
4415
  const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
4174
4416
  await db.transact([
4175
4417
  membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
@@ -4282,8 +4524,8 @@ async function lowerSeatPrice(db, scope2, now, limit = 100) {
4282
4524
  const receipt = { seatPriceCents: target, repriced: 0, skipped: [], more: rows5.length > limit };
4283
4525
  for (const member of rows5.slice(0, limit)) {
4284
4526
  if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) throw new Error("membership reprice scope mismatch");
4285
- const id6 = String(member.id), skip = (reason) => {
4286
- receipt.skipped.push({ membershipId: id6, reason });
4527
+ const id7 = String(member.id), skip = (reason) => {
4528
+ receipt.skipped.push({ membershipId: id7, reason });
4287
4529
  };
4288
4530
  const offering = membershipOfferingVersion(member.offeringSnapshot);
4289
4531
  if (offering.seatPriceCents <= target) {
@@ -4299,16 +4541,16 @@ async function lowerSeatPrice(db, scope2, now, limit = 100) {
4299
4541
  skip(`seat is ${String(seat.status)}`);
4300
4542
  continue;
4301
4543
  }
4302
- const ops = [membershipUpdate("networkMembership", id6, { offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(member.revision) + 1, updatedAt: now })];
4544
+ const ops = [membershipUpdate("networkMembership", id7, { offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(member.revision) + 1, updatedAt: now })];
4303
4545
  const guards = [
4304
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
4546
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
4305
4547
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative" } }
4306
4548
  ];
4307
4549
  if (seat) {
4308
4550
  ops.push(membershipUpdate("networkSeat", String(seat.id), { amountCents: target, offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(seat.revision) + 1, updatedAt: now }));
4309
4551
  guards.push({ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision, status: "payment_pending" } });
4310
4552
  }
4311
- await db.transact(ops, { mutationId: `seat-reprice:${id6}:${member.revision}`, guards });
4553
+ await db.transact(ops, { mutationId: `seat-reprice:${id7}:${member.revision}`, guards });
4312
4554
  receipt.repriced++;
4313
4555
  }
4314
4556
  return receipt;
@@ -4509,25 +4751,28 @@ var handleAdminMembershipPolicyVersions = async (req, url, env, ctx) => {
4509
4751
  };
4510
4752
 
4511
4753
  // src/membership-discount-reterm.ts
4512
- var import_stripe8 = require("@odla-ai/stripe");
4754
+ var import_stripe9 = require("@odla-ai/stripe");
4513
4755
 
4514
4756
  // src/subscription-provider-contract.ts
4515
- var import_stripe7 = require("@odla-ai/stripe");
4757
+ var import_stripe8 = require("@odla-ai/stripe");
4516
4758
  function subscriptionContractMismatch() {
4517
4759
  throw Object.assign(new Error("Stripe subscription does not match the checkout contract"), { code: "stripe_contract_mismatch" });
4518
4760
  }
4519
4761
  async function verifySubscriptionPrice(secretKey, input, resume = false) {
4520
4762
  if (!/^price_[A-Za-z0-9]+$/.test(input.priceId) || !Number.isSafeInteger(input.priceCents) || input.priceCents <= 0 || !/^[a-z]{3}$/.test(input.currency) || !["month", "year"].includes(input.interval)) subscriptionContractMismatch();
4521
- const result = await (0, import_stripe7.stripeRequest)({ secretKey }, "GET", `/v1/prices/${encodeURIComponent(input.priceId)}`);
4763
+ const result = await (0, import_stripe8.stripeRequest)({ secretKey }, "GET", `/v1/prices/${encodeURIComponent(input.priceId)}`);
4522
4764
  const price = result.body;
4523
4765
  if (!result.ok || !price || price.id !== input.priceId || !resume && price.active !== true || price.unit_amount !== input.priceCents || price.currency !== input.currency || price.type !== "recurring" || price.billing_scheme !== "per_unit" || price.custom_unit_amount || price.transform_quantity || price.recurring?.interval !== input.interval || price.recurring?.interval_count !== 1 || price.recurring?.usage_type !== "licensed" || price.livemode !== (input.environment === "prod")) subscriptionContractMismatch();
4524
4766
  return typeof price.product === "string" ? price.product : void 0;
4525
4767
  }
4526
4768
  function verifySubscriptionResult(sub, input, customerId, metadata) {
4527
4769
  const items = sub?.items;
4528
- if (!sub || !/^sub_[A-Za-z0-9]+$/.test(sub.id) || sub.customer !== customerId || input.existingSubscriptionId && sub.id !== input.existingSubscriptionId || !(input.billingTerms?.initialTermEndsAt ? ["incomplete", "active", "trialing"] : ["incomplete", "active"]).includes(sub.status) || sub.livemode !== (input.environment === "prod") || !Object.entries(metadata).every(([key3, value]) => sub.metadata?.[key3] === value) || !Array.isArray(items?.data) || items.has_more !== false || items.data.length !== 1 || items.data[0]?.price?.id !== input.priceId || items.data[0]?.quantity !== 1) subscriptionContractMismatch();
4770
+ const seat = input.namedSeat?.amountCents ? input.namedSeat : void 0;
4771
+ const primary = items?.data?.find((item) => item.price?.id === input.priceId);
4772
+ const seatItem = seat && items?.data?.find((item) => item.price?.id === seat.priceId);
4773
+ if (!sub || !/^sub_[A-Za-z0-9]+$/.test(sub.id) || sub.customer !== customerId || input.existingSubscriptionId && sub.id !== input.existingSubscriptionId || !(input.billingTerms?.initialTermEndsAt ? ["incomplete", "active", "trialing"] : ["incomplete", "active"]).includes(sub.status) || sub.livemode !== (input.environment === "prod") || !Object.entries(metadata).every(([key3, value]) => sub.metadata?.[key3] === value) || !Array.isArray(items?.data) || items.has_more !== false || items.data.length !== (seat ? 2 : 1) || primary?.quantity !== 1 || seat && (seatItem?.quantity !== 1 || seatItem.metadata?.seatId !== seat.seatId || seatItem.metadata?.membershipId !== seat.membershipId)) subscriptionContractMismatch();
4529
4774
  const invoice = sub.latest_invoice;
4530
- if (!invoice || typeof invoice !== "object" || invoice.customer !== customerId || invoice.currency !== input.currency || invoice.total !== subscriptionAmountDue(input) || invoice.billing_reason !== "subscription_create") subscriptionContractMismatch();
4775
+ if (!invoice || typeof invoice !== "object" || invoice.customer !== customerId || invoice.currency !== input.currency || invoice.total !== subscriptionAmountDue(input) + (seat?.amountCents ?? 0) || invoice.billing_reason !== "subscription_create") subscriptionContractMismatch();
4531
4776
  verifySubscriptionBillingResult(sub, input);
4532
4777
  }
4533
4778
 
@@ -4539,8 +4784,8 @@ var couponOf = (discount) => {
4539
4784
  const ref = discount?.source?.coupon ?? discount?.coupon;
4540
4785
  return typeof ref === "string" ? ref : typeof ref?.id === "string" ? ref.id : void 0;
4541
4786
  };
4542
- async function retermPolicy(db, scope2, id6) {
4543
- const row2 = await membershipLoad(db, "discountPolicyVersion", id6);
4787
+ async function retermPolicy(db, scope2, id7) {
4788
+ const row2 = await membershipLoad(db, "discountPolicyVersion", id7);
4544
4789
  if (!row2) membershipFail("membership_reterm_policy_unknown");
4545
4790
  let policy2;
4546
4791
  try {
@@ -4548,12 +4793,12 @@ async function retermPolicy(db, scope2, id6) {
4548
4793
  } catch {
4549
4794
  return membershipFail("membership_reterm_policy_ineligible");
4550
4795
  }
4551
- if (policy2.id !== id6 || policy2.chapterId !== scope2.chapterId || policy2.status !== "published" || policy2.benefitDuration.kind !== "while_active" || policy2.percentOffBps <= 0 || policy2.discountEligibleSkuIds.length === 0) membershipFail("membership_reterm_policy_ineligible");
4796
+ if (policy2.id !== id7 || policy2.chapterId !== scope2.chapterId || policy2.status !== "published" || policy2.benefitDuration.kind !== "while_active" || policy2.percentOffBps <= 0 || policy2.discountEligibleSkuIds.length === 0) membershipFail("membership_reterm_policy_ineligible");
4552
4797
  return policy2;
4553
4798
  }
4554
4799
  async function attachProviderDiscount(sk, scope2, member, policy2, couponId, productId) {
4555
4800
  const subId = String(member.providerSubscriptionId), path = `/v1/subscriptions/${encodeURIComponent(subId)}`;
4556
- const read2 = await (0, import_stripe8.stripeRequest)({ secretKey: sk }, "GET", path, { "expand[0]": "discounts" });
4801
+ const read2 = await (0, import_stripe9.stripeRequest)({ secretKey: sk }, "GET", path, { "expand[0]": "discounts" });
4557
4802
  const sub = read2.body, items = sub?.items?.data;
4558
4803
  const metadata = { ...stripePartitionMetadata(scope2), applicationId: String(member.applicationId) };
4559
4804
  if (!read2.ok || sub.id !== subId || sub.customer !== member.providerCustomerId || sub.livemode !== (scope2.environment === "prod") || !PROVIDER_LIVE.has(sub.status) || !Object.entries(metadata).every(([key3, value]) => sub.metadata?.[key3] === value) || sub.items?.has_more !== false || !Array.isArray(items) || items.length < 1 || items.length > 2 || items.filter((item) => item.price?.id === member.providerPriceId && item.quantity === 1 && item.price?.product === productId).length !== 1) {
@@ -4564,7 +4809,7 @@ async function attachProviderDiscount(sk, scope2, member, policy2, couponId, pro
4564
4809
  const present = discounts.map(couponOf);
4565
4810
  if (present.length === 1 && present[0] === couponId) return false;
4566
4811
  if (present.length > 0) throw Object.assign(new Error("provider subscription carries an unrelated discount"), { code: "provider_mismatch" });
4567
- const written = await (0, import_stripe8.stripeRequest)({ secretKey: sk }, "POST", path, {
4812
+ const written = await (0, import_stripe9.stripeRequest)({ secretKey: sk }, "POST", path, {
4568
4813
  "discounts[0][coupon]": couponId,
4569
4814
  "expand[0]": "discounts",
4570
4815
  "metadata[discountPolicyVersionId]": policy2.id
@@ -4580,7 +4825,7 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4580
4825
  const limit = input.limit ?? 100;
4581
4826
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 200) throw new TypeError("invalid reterm limit");
4582
4827
  if (typeof input.policyVersionId !== "string" || !SAFE.test(input.policyVersionId)) throw new TypeError("invalid policy version id");
4583
- if (input.membershipIds !== void 0 && (!Array.isArray(input.membershipIds) || input.membershipIds.length === 0 || input.membershipIds.length > limit || input.membershipIds.some((id6) => typeof id6 !== "string" || !SAFE.test(id6)))) throw new TypeError("invalid membership ids");
4828
+ if (input.membershipIds !== void 0 && (!Array.isArray(input.membershipIds) || input.membershipIds.length === 0 || input.membershipIds.length > limit || input.membershipIds.some((id7) => typeof id7 !== "string" || !SAFE.test(id7)))) throw new TypeError("invalid membership ids");
4584
4829
  const state = await loadMembershipAuthorityState(db, scope2);
4585
4830
  const policy2 = await retermPolicy(db, scope2, input.policyVersionId);
4586
4831
  const receipt = { policyVersionId: policy2.id, retermed: [], unchanged: [], skipped: [], more: false };
@@ -4590,10 +4835,10 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4590
4835
  let rows5;
4591
4836
  if (input.membershipIds) {
4592
4837
  rows5 = [];
4593
- for (const id6 of input.membershipIds) {
4594
- const member = await membershipLoad(db, "networkMembership", id6);
4595
- if (!member) skip(id6, "not_found");
4596
- else if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) skip(id6, "scope_mismatch");
4838
+ for (const id7 of input.membershipIds) {
4839
+ const member = await membershipLoad(db, "networkMembership", id7);
4840
+ if (!member) skip(id7, "not_found");
4841
+ else if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) skip(id7, "scope_mismatch");
4597
4842
  else rows5.push(member);
4598
4843
  }
4599
4844
  } else {
@@ -4603,32 +4848,32 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4603
4848
  rows5 = found.slice(0, limit);
4604
4849
  }
4605
4850
  for (const member of rows5) {
4606
- const id6 = String(member.id);
4851
+ const id7 = String(member.id);
4607
4852
  if (member.discountPolicyVersionId === policy2.id) {
4608
- receipt.unchanged.push(id6);
4853
+ receipt.unchanged.push(id7);
4609
4854
  continue;
4610
4855
  }
4611
4856
  if (!RETERMABLE.has(String(member.status))) {
4612
- skip(id6, "status");
4857
+ skip(id7, "status");
4613
4858
  continue;
4614
4859
  }
4615
4860
  if (member.founding !== true) {
4616
- skip(id6, "not_founding");
4861
+ skip(id7, "not_founding");
4617
4862
  continue;
4618
4863
  }
4619
4864
  if (member.cohort !== policy2.cohortLabel) {
4620
- skip(id6, "cohort_mismatch");
4865
+ skip(id7, "cohort_mismatch");
4621
4866
  continue;
4622
4867
  }
4623
4868
  const skuId = String(member.baseSkuId);
4624
4869
  if (!policy2.discountEligibleSkuIds.includes(skuId)) {
4625
- skip(id6, "sku_not_discounted");
4870
+ skip(id7, "sku_not_discounted");
4626
4871
  continue;
4627
4872
  }
4628
4873
  const base = Number(member.baseAmountCents), current = Number(member.renewalAmountCents);
4629
4874
  const renewal = Math.round(base * (1e4 - policy2.percentOffBps) / 1e4);
4630
4875
  if (!Number.isSafeInteger(base) || base <= 0 || !Number.isSafeInteger(current) || renewal > current) {
4631
- skip(id6, "would_not_widen");
4876
+ skip(id7, "would_not_widen");
4632
4877
  continue;
4633
4878
  }
4634
4879
  const couponId = await membershipDiscountCouponId({ ...scope2, skuId }, policy2.id);
@@ -4656,22 +4901,22 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4656
4901
  } catch (error) {
4657
4902
  const code = error.code;
4658
4903
  if (code === "provider_mismatch" || code === "stripe_contract_mismatch") {
4659
- skip(id6, "provider_mismatch");
4904
+ skip(id7, "provider_mismatch");
4660
4905
  continue;
4661
4906
  }
4662
4907
  throw error;
4663
4908
  }
4664
4909
  try {
4665
- await db.transact([membershipUpdate("networkMembership", id6, {
4910
+ await db.transact([membershipUpdate("networkMembership", id7, {
4666
4911
  discountPolicyVersionId: policy2.id,
4667
4912
  discountSnapshot: policy2,
4668
4913
  renewalAmountCents: renewal,
4669
4914
  revision: Number(member.revision) + 1,
4670
4915
  updatedAt: now
4671
4916
  })], {
4672
- mutationId: `membership-reterm:${id6}:${policy2.id}:${member.revision}`,
4917
+ mutationId: `membership-reterm:${id7}:${policy2.id}:${member.revision}`,
4673
4918
  guards: [
4674
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision, status: member.status } },
4919
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision, status: member.status } },
4675
4920
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative" } },
4676
4921
  { ns: "discountPolicyVersion", id: policy2.id, exists: true }
4677
4922
  ]
@@ -4681,7 +4926,7 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4681
4926
  return membershipFail("membership_reterm_retry_required");
4682
4927
  }
4683
4928
  receipt.retermed.push({
4684
- membershipId: id6,
4929
+ membershipId: id7,
4685
4930
  ...typeof member.discountPolicyVersionId === "string" ? { previousPolicyVersionId: member.discountPolicyVersionId } : {},
4686
4931
  previousRenewalAmountCents: current,
4687
4932
  renewalAmountCents: renewal,
@@ -4717,7 +4962,7 @@ var handleAdminMembershipReterm = async (req, url, env, ctx) => {
4717
4962
  return json({ error: BAD }, 400);
4718
4963
  }
4719
4964
  const appId = body.appId ?? body.chapterId, runtime = body.runtime ?? env.ODLA_RUNTIME;
4720
- if (typeof body.chapterId !== "string" || !SCOPE3.test(body.chapterId) || typeof appId !== "string" || !SCOPE3.test(appId) || typeof runtime !== "string" || !/^[a-z0-9]{2,12}$/.test(runtime) || typeof body.policyVersionId !== "string" || !ID.test(body.policyVersionId) || body.membershipIds !== void 0 && (!Array.isArray(body.membershipIds) || body.membershipIds.length === 0 || body.membershipIds.length > 200 || body.membershipIds.some((id6) => typeof id6 !== "string" || !ID.test(id6))) || body.limit !== void 0 && (!Number.isSafeInteger(body.limit) || Number(body.limit) < 1 || Number(body.limit) > 200)) {
4965
+ if (typeof body.chapterId !== "string" || !SCOPE3.test(body.chapterId) || typeof appId !== "string" || !SCOPE3.test(appId) || typeof runtime !== "string" || !/^[a-z0-9]{2,12}$/.test(runtime) || typeof body.policyVersionId !== "string" || !ID.test(body.policyVersionId) || body.membershipIds !== void 0 && (!Array.isArray(body.membershipIds) || body.membershipIds.length === 0 || body.membershipIds.length > 200 || body.membershipIds.some((id7) => typeof id7 !== "string" || !ID.test(id7))) || body.limit !== void 0 && (!Number.isSafeInteger(body.limit) || Number(body.limit) < 1 || Number(body.limit) > 200)) {
4721
4966
  return json({ error: BAD }, 400);
4722
4967
  }
4723
4968
  const secret = await getVaultSecret(db, operator.stripeSecretName ?? "stripe_secret_key");
@@ -4992,12 +5237,12 @@ var handleMembershipRestart = async (req, url, env, ctx) => {
4992
5237
  if (!app || app.clerkUserId !== user.userId || app.namedSeatId || app.giftClaimId) return json({ error: "membership is not eligible to restart" }, 409);
4993
5238
  if (app.membershipRestartId && app.status === "submitted") return json({ applicationId: app.id });
4994
5239
  try {
4995
- const id6 = String(app.id), intent = await membershipLoad(db, "subscriptionCheckoutIntents", id6);
5240
+ const id7 = String(app.id), intent = await membershipLoad(db, "subscriptionCheckoutIntents", id7);
4996
5241
  if (!intent || !app.stripeSubscriptionId || intent.providerSubscriptionId !== app.stripeSubscriptionId) return json({ error: "membership history needs reconciliation" }, 409);
4997
5242
  const target = await membershipTargetForIntent(intent, app, env, ctx);
4998
5243
  const restart = await ctx.membershipAuthority.restart({ ...target, previousSubscriptionId: String(app.stripeSubscriptionId) }, env);
4999
- if (restart.status !== "ready" || restart.applicationId !== id6 || restart.membershipId !== await networkMembershipId(target) || !/^restart_[a-f0-9]{64}$/.test(restart.restartId) || !Number.isSafeInteger(restart.memberNumber) || restart.memberNumber < 1) throw new Error("invalid restart receipt");
5000
- const now = Date.now(), quote = await membershipLoad(db, "membershipQuoteProjections", id6);
5244
+ if (restart.status !== "ready" || restart.applicationId !== id7 || restart.membershipId !== await networkMembershipId(target) || !/^restart_[a-f0-9]{64}$/.test(restart.restartId) || !Number.isSafeInteger(restart.memberNumber) || restart.memberNumber < 1) throw new Error("invalid restart receipt");
5245
+ const now = Date.now(), quote = await membershipLoad(db, "membershipQuoteProjections", id7);
5001
5246
  const attrs = {
5002
5247
  status: "submitted",
5003
5248
  membershipRestartId: restart.restartId,
@@ -5017,8 +5262,8 @@ var handleMembershipRestart = async (req, url, env, ctx) => {
5017
5262
  quoteSnapshot: quote ?? {},
5018
5263
  archivedAt: now
5019
5264
  } },
5020
- { t: "delete", ns: "subscriptionCheckoutIntents", id: id6 },
5021
- { t: "retract", ns: "applications", id: id6, attrs: [
5265
+ { t: "delete", ns: "subscriptionCheckoutIntents", id: id7 },
5266
+ { t: "retract", ns: "applications", id: id7, attrs: [
5022
5267
  "stripeSubscriptionId",
5023
5268
  "canceled",
5024
5269
  "renewalAt",
@@ -5029,28 +5274,28 @@ var handleMembershipRestart = async (req, url, env, ctx) => {
5029
5274
  "approvalEffectsOrigin",
5030
5275
  "approvalEffectsLastAttemptAt"
5031
5276
  ] },
5032
- { t: "update", ns: "applications", id: id6, attrs },
5033
- ...quote ? [{ t: "update", ns: "membershipQuoteProjections", id: id6, attrs: { status: "restart", updatedAt: now } }] : []
5277
+ { t: "update", ns: "applications", id: id7, attrs },
5278
+ ...quote ? [{ t: "update", ns: "membershipQuoteProjections", id: id7, attrs: { status: "restart", updatedAt: now } }] : []
5034
5279
  ];
5035
5280
  try {
5036
5281
  await db.transact(ops, { mutationId: `membership-restart-application:${restart.restartId}`, guards: [
5037
- { ns: "applications", id: id6, exists: true, equals: {
5282
+ { ns: "applications", id: id7, exists: true, equals: {
5038
5283
  clerkUserId: user.userId,
5039
5284
  groupId: ctx.chapter.id,
5040
5285
  status: app.status,
5041
5286
  stripeSubscriptionId: app.stripeSubscriptionId
5042
5287
  } },
5043
- { ns: "subscriptionCheckoutIntents", id: id6, exists: true, equals: { providerSubscriptionId: app.stripeSubscriptionId } },
5288
+ { ns: "subscriptionCheckoutIntents", id: id7, exists: true, equals: { providerSubscriptionId: app.stripeSubscriptionId } },
5044
5289
  { ns: "membershipCheckoutHistory", id: restart.restartId, exists: false },
5045
- { ns: "membershipQuoteProjections", id: id6, exists: Boolean(quote), ...quote ? { equals: { revision: quote.revision, status: quote.status } } : {} }
5290
+ { ns: "membershipQuoteProjections", id: id7, exists: Boolean(quote), ...quote ? { equals: { revision: quote.revision, status: quote.status } } : {} }
5046
5291
  ] });
5047
5292
  } catch (error) {
5048
5293
  if (error.code !== "transact_guard_failed") throw error;
5049
5294
  }
5050
- const saved = await membershipLoad(db, "applications", id6);
5295
+ const saved = await membershipLoad(db, "applications", id7);
5051
5296
  if (!saved || saved.clerkUserId !== user.userId || saved.membershipRestartId !== restart.restartId || saved.groupId !== ctx.chapter.id || saved.stripeRuntime !== target.runtime) throw new Error("restart persistence pending");
5052
5297
  await syncApplicationToCrm(crmDeps(db, ctx), { app: saved, stage: String(saved.status) }).catch(() => void 0);
5053
- return json({ applicationId: id6 });
5298
+ return json({ applicationId: id7 });
5054
5299
  } catch {
5055
5300
  return json({ error: "We could not prepare your new subscription. Your account is saved. Please retry." }, 503);
5056
5301
  }
@@ -5181,8 +5426,8 @@ async function submitApplication(db, chapter, fields, opts) {
5181
5426
  if (app.requireDisclaimerAck && !acked) {
5182
5427
  return { ok: false, error: "disclaimerAck is required" };
5183
5428
  }
5184
- const id6 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
5185
- const row2 = { id: id6, status: chapter.pipeline.initial, createdAt: opts.now };
5429
+ const id7 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
5430
+ const row2 = { id: id7, status: chapter.pipeline.initial, createdAt: opts.now };
5186
5431
  for (const f of [...app.required, ...app.optional]) {
5187
5432
  if (fieldStates[f]?.visible === false) continue;
5188
5433
  if (typeof fields[f] === "string") row2[f] = fields[f].trim();
@@ -5216,12 +5461,12 @@ async function submitApplication(db, chapter, fields, opts) {
5216
5461
  }
5217
5462
  if (acked) row2.disclaimerAckAt = opts.now;
5218
5463
  const ops = [
5219
- { t: "update", ns: "applications", id: id6, attrs: row2 }
5464
+ { t: "update", ns: "applications", id: id7, attrs: row2 }
5220
5465
  ];
5221
5466
  if (opts.admissionGrant?.decision === "waive_interview") {
5222
5467
  ops.push({ t: "update", ns: "admissionGrants", id: opts.admissionGrant.id, attrs: {
5223
5468
  consumedAt: opts.now,
5224
- consumedApplicationId: id6
5469
+ consumedApplicationId: id7
5225
5470
  } });
5226
5471
  }
5227
5472
  try {
@@ -5229,7 +5474,7 @@ async function submitApplication(db, chapter, fields, opts) {
5229
5474
  ops,
5230
5475
  opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
5231
5476
  );
5232
- return { ok: true, id: id6, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
5477
+ return { ok: true, id: id7, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
5233
5478
  } catch (error) {
5234
5479
  if (error.code === "unique_violation") return {
5235
5480
  ok: false,
@@ -5435,14 +5680,14 @@ function validateAdmissionGrantDocument(input) {
5435
5680
  const seenEmails = /* @__PURE__ */ new Set();
5436
5681
  for (const [index2, raw2] of rawGrants.slice(0, MAX_GRANTS).entries()) {
5437
5682
  const grant = raw2 && typeof raw2 === "object" ? raw2 : {};
5438
- const id6 = typeof grant.id === "string" ? grant.id : "";
5683
+ const id7 = typeof grant.id === "string" ? grant.id : "";
5439
5684
  const email = typeof grant.email === "string" ? grant.email.trim() : "";
5440
5685
  const normalized = normalizeAdmissionEmail(email);
5441
5686
  const decision = grant.decision;
5442
5687
  const note = typeof grant.note === "string" ? grant.note.trim() : void 0;
5443
5688
  const expiresAt = grant.expiresAt === void 0 ? void 0 : Number(grant.expiresAt);
5444
- if (!ID2.test(id6)) errors.push(`document.grants[${index2}].id must be an identifier`);
5445
- if (seenIds.has(id6)) errors.push(`document.grants repeats id "${id6}"`);
5689
+ if (!ID2.test(id7)) errors.push(`document.grants[${index2}].id must be an identifier`);
5690
+ if (seenIds.has(id7)) errors.push(`document.grants repeats id "${id7}"`);
5446
5691
  if (!isAdmissionEmail(normalized)) errors.push(`document.grants[${index2}].email must be valid`);
5447
5692
  if (seenEmails.has(normalized)) errors.push(`document.grants repeats email "${normalized}"`);
5448
5693
  if (decision !== "waive_interview" && decision !== "require_interview") {
@@ -5452,10 +5697,10 @@ function validateAdmissionGrantDocument(input) {
5452
5697
  if (expiresAt !== void 0 && (!Number.isFinite(expiresAt) || expiresAt <= 0)) {
5453
5698
  errors.push(`document.grants[${index2}].expiresAt must be epoch milliseconds`);
5454
5699
  }
5455
- seenIds.add(id6);
5700
+ seenIds.add(id7);
5456
5701
  seenEmails.add(normalized);
5457
5702
  grants.push({
5458
- id: id6,
5703
+ id: id7,
5459
5704
  email,
5460
5705
  decision,
5461
5706
  ...note ? { note } : {},
@@ -5527,8 +5772,8 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
5527
5772
  const incomingIds = new Set(document.grants.map((grant) => `admission:parent:${document.source.appId}:${grant.id}`));
5528
5773
  const ops = [];
5529
5774
  for (const row2 of current) {
5530
- const id6 = String(row2.id ?? "");
5531
- if (id6 && !incomingIds.has(id6)) ops.push({ t: "update", ns: "admissionGrants", id: id6, attrs: {
5775
+ const id7 = String(row2.id ?? "");
5776
+ if (id7 && !incomingIds.has(id7)) ops.push({ t: "update", ns: "admissionGrants", id: id7, attrs: {
5532
5777
  active: false,
5533
5778
  revokedAt: receivedAt,
5534
5779
  revokedBy: document.source.actor,
@@ -5537,10 +5782,10 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
5537
5782
  } });
5538
5783
  }
5539
5784
  for (const grant of document.grants) {
5540
- const id6 = `admission:parent:${document.source.appId}:${grant.id}`;
5541
- const existing = current.find((row2) => row2.id === id6);
5542
- ops.push({ t: "update", ns: "admissionGrants", id: id6, attrs: {
5543
- id: id6,
5785
+ const id7 = `admission:parent:${document.source.appId}:${grant.id}`;
5786
+ const existing = current.find((row2) => row2.id === id7);
5787
+ ops.push({ t: "update", ns: "admissionGrants", id: id7, attrs: {
5788
+ id: id7,
5544
5789
  sourceGrantId: grant.id,
5545
5790
  sourceScope: "parent",
5546
5791
  sourceId: document.source.appId,
@@ -5561,7 +5806,7 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
5561
5806
  ...!grant.note && existing?.note !== void 0 ? ["note"] : [],
5562
5807
  ...grant.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
5563
5808
  ];
5564
- if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id6, attrs: retract });
5809
+ if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id7, attrs: retract });
5565
5810
  }
5566
5811
  if (previous?.active) ops.push({ t: "update", ns: "admissionGrantRevisions", id: previous.id, attrs: { active: false } });
5567
5812
  const revisionId = `admission-revision:${document.source.appId}:${document.target.environment}:${document.revision}`;
@@ -5592,9 +5837,9 @@ var APPLICATION_LABELS = {
5592
5837
  phone: "Phone",
5593
5838
  state: "State"
5594
5839
  };
5595
- function humanize(id6) {
5596
- const text8 = id6.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
5597
- return text8 ? `${text8[0].toUpperCase()}${text8.slice(1)}` : id6;
5840
+ function humanize(id7) {
5841
+ const text8 = id7.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
5842
+ return text8 ? `${text8[0].toUpperCase()}${text8.slice(1)}` : id7;
5598
5843
  }
5599
5844
  function applicationDefs(chapter) {
5600
5845
  try {
@@ -5603,23 +5848,23 @@ function applicationDefs(chapter) {
5603
5848
  return {};
5604
5849
  }
5605
5850
  }
5606
- function applicationType(id6, def) {
5851
+ function applicationType(id7, def) {
5607
5852
  if (def && ["string", "email", "url", "enum"].includes(def.type)) return def.type;
5608
- return id6 === "email" ? "email" : id6 === "linkedin" ? "url" : "string";
5853
+ return id7 === "email" ? "email" : id7 === "linkedin" ? "url" : "string";
5609
5854
  }
5610
5855
  function applicationFlowFields(chapter, application = chapter.application) {
5611
5856
  const defs = applicationDefs(chapter);
5612
- return [...application.required, ...application.optional].map((id6, order2) => {
5613
- const def = defs[id6];
5614
- const conditions = application.conditions[id6];
5615
- const type = applicationType(id6, def);
5857
+ return [...application.required, ...application.optional].map((id7, order2) => {
5858
+ const def = defs[id7];
5859
+ const conditions = application.conditions[id7];
5860
+ const type = applicationType(id7, def);
5616
5861
  return {
5617
- id: id6,
5618
- label: def?.label ?? APPLICATION_LABELS[id6] ?? humanize(id6),
5862
+ id: id7,
5863
+ label: def?.label ?? APPLICATION_LABELS[id7] ?? humanize(id7),
5619
5864
  type,
5620
- required: application.required.includes(id6),
5865
+ required: application.required.includes(id7),
5621
5866
  order: order2,
5622
- maxLength: application.maxLen[id6] ?? application.defaultMaxLen,
5867
+ maxLength: application.maxLen[id7] ?? application.defaultMaxLen,
5623
5868
  ...type === "enum" && def?.options ? { options: [...def.options] } : {},
5624
5869
  ...conditions ? { conditions: { ...conditions } } : {},
5625
5870
  ...type === "email" ? { emailIdentity: "literal" } : {}
@@ -5629,17 +5874,17 @@ function applicationFlowFields(chapter, application = chapter.application) {
5629
5874
  function resolveFormationFlowFields(crm, formation) {
5630
5875
  if (!formation.enabled) return [];
5631
5876
  const def = crm.type(formation.type);
5632
- return [...formation.required, ...formation.optional].map((id6, order2) => {
5633
- const field = def.fields[id6];
5877
+ return [...formation.required, ...formation.optional].map((id7, order2) => {
5878
+ const field = def.fields[id7];
5634
5879
  return {
5635
- id: id6,
5636
- label: field.label ?? humanize(id6),
5880
+ id: id7,
5881
+ label: field.label ?? humanize(id7),
5637
5882
  type: field.type,
5638
- required: formation.required.includes(id6),
5883
+ required: formation.required.includes(id7),
5639
5884
  order: order2,
5640
- maxLength: formation.maxLen[id6] ?? formation.defaultMaxLen,
5885
+ maxLength: formation.maxLen[id7] ?? formation.defaultMaxLen,
5641
5886
  ...field.options ? { options: [...field.options] } : {},
5642
- ...id6 === def.emailField || field.type === "email" ? { emailIdentity: "literal" } : {}
5887
+ ...id7 === def.emailField || field.type === "email" ? { emailIdentity: "literal" } : {}
5643
5888
  };
5644
5889
  });
5645
5890
  }
@@ -5689,10 +5934,10 @@ function validateGiftOfferCatalog(input, tierIds) {
5689
5934
  }
5690
5935
  const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
5691
5936
  for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
5692
- const id6 = text3(item.id, `${path}.id`, errors, 80);
5693
- if (id6 && !ID3.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
5694
- if (ids2.has(id6)) errors.push(`${path}.id duplicates "${id6}"`);
5695
- ids2.add(id6);
5937
+ const id7 = text3(item.id, `${path}.id`, errors, 80);
5938
+ if (id7 && !ID3.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
5939
+ if (ids2.has(id7)) errors.push(`${path}.id duplicates "${id7}"`);
5940
+ ids2.add(id7);
5696
5941
  const targetRaw = record(item.target);
5697
5942
  let target = { type: "entitlement", entitlementKey: "invalid" };
5698
5943
  if (targetRaw?.type === "tier") {
@@ -5718,7 +5963,7 @@ function validateGiftOfferCatalog(input, tierIds) {
5718
5963
  if (activeUntil !== void 0 && (!Number.isSafeInteger(activeUntil) || activeUntil < 1)) errors.push(`${path}.activeUntil must be epoch milliseconds`);
5719
5964
  if (activeFrom !== void 0 && activeUntil !== void 0 && activeUntil <= activeFrom) errors.push(`${path}.activeUntil must follow activeFrom`);
5720
5965
  offers.push({
5721
- id: id6,
5966
+ id: id7,
5722
5967
  target,
5723
5968
  billingMode: item.billingMode,
5724
5969
  stripePriceId,
@@ -5818,9 +6063,9 @@ async function materializeSignupControl(db, envelope, previous, receivedAt) {
5818
6063
  const ops = [];
5819
6064
  if (previous?.active) ops.push({ t: "update", ns: "signupControls", id: previous.id, attrs: { active: false } });
5820
6065
  for (const row2 of currentRows) {
5821
- const id7 = String(row2.id ?? "");
5822
- if (id7 && !incoming.has(id7)) {
5823
- ops.push({ t: "update", ns: "tiers", id: byPublicId("tiers", id7), attrs: { active: false } });
6066
+ const id8 = String(row2.id ?? "");
6067
+ if (id8 && !incoming.has(id8)) {
6068
+ ops.push({ t: "update", ns: "tiers", id: byPublicId("tiers", id8), attrs: { active: false } });
5824
6069
  }
5825
6070
  }
5826
6071
  for (const tier of document.tiers) {
@@ -5855,9 +6100,9 @@ async function materializeSignupControl(db, envelope, previous, receivedAt) {
5855
6100
  if (!("stripePriceId" in attrs)) {
5856
6101
  ops.push({ t: "retract", ns: "groups", id: groupRef, attrs: ["stripePriceId"] });
5857
6102
  }
5858
- const id6 = `signup-control:${document.target.environment}:${document.revision}`;
5859
- ops.push({ t: "update", ns: "signupControls", id: id6, attrs: {
5860
- id: id6,
6103
+ const id7 = `signup-control:${document.target.environment}:${document.revision}`;
6104
+ ops.push({ t: "update", ns: "signupControls", id: id7, attrs: {
6105
+ id: id7,
5861
6106
  sourceId: document.source.appId,
5862
6107
  targetId: document.target.chapterId,
5863
6108
  environment: document.target.environment,
@@ -5896,6 +6141,8 @@ function foundingOf(offer) {
5896
6141
  async function createPaymentQuote(input) {
5897
6142
  const { group, tier, policy: policy2 } = input;
5898
6143
  const terms2 = input.billingTerms ? subscriptionBillingTerms(input.billingTerms) : void 0;
6144
+ if (input.seatOffer && (!terms2 || !Number.isSafeInteger(input.seatOffer.amountCents) || input.seatOffer.amountCents < 0) || input.namedSeat && !input.seatOffer) throw new Error("invalid checkout seat offer");
6145
+ const seatCents = input.namedSeat ? input.seatOffer.amountCents : 0;
5899
6146
  const compareAt = input.presentation?.compareAtPriceCents;
5900
6147
  const presentation = Number.isSafeInteger(compareAt) && Number(compareAt) > tier.priceCents ? { compareAtPriceCents: Number(compareAt) } : void 0;
5901
6148
  const dueToday = terms2 ? subscriptionAmountDue({ priceCents: tier.priceCents, billingTerms: terms2 }) : void 0;
@@ -5904,6 +6151,7 @@ async function createPaymentQuote(input) {
5904
6151
  discountCents: tier.priceCents - dueToday,
5905
6152
  dueTodayCents: dueToday
5906
6153
  };
6154
+ if (!Number.isSafeInteger(lines.dueTodayCents + seatCents) || !Number.isSafeInteger(tier.priceCents + seatCents)) throw new Error("invalid checkout total");
5907
6155
  const quote = {
5908
6156
  applicationId: input.applicationId,
5909
6157
  chapterId: input.chapterId,
@@ -5911,11 +6159,17 @@ async function createPaymentQuote(input) {
5911
6159
  tierName: tier.name,
5912
6160
  currency: tier.currency ?? String(group.currency ?? "usd"),
5913
6161
  interval: tier.interval ?? (group.interval === "month" ? "month" : "year"),
5914
- lineItems: presentation ? {
5915
- standardCents: presentation.compareAtPriceCents,
5916
- discountCents: presentation.compareAtPriceCents - lines.dueTodayCents,
5917
- dueTodayCents: lines.dueTodayCents
5918
- } : lines,
6162
+ lineItems: {
6163
+ ...presentation ? {
6164
+ standardCents: presentation.compareAtPriceCents,
6165
+ discountCents: presentation.compareAtPriceCents - lines.dueTodayCents,
6166
+ dueTodayCents: lines.dueTodayCents
6167
+ } : lines,
6168
+ dueTodayCents: lines.dueTodayCents + seatCents,
6169
+ ...input.namedSeat ? { namedSeatCents: seatCents } : {}
6170
+ },
6171
+ ...input.seatOffer ? { seatOffer: input.seatOffer } : {},
6172
+ ...input.namedSeat ? { namedSeat: input.namedSeat } : {},
5919
6173
  ...presentation ? { presentation } : {},
5920
6174
  refundPolicyText: policy2?.refundPolicyText ?? String(group.refundPolicyText ?? ""),
5921
6175
  merchantDisclosureText: String(group.merchantDisclosureText ?? ""),
@@ -5927,7 +6181,7 @@ async function createPaymentQuote(input) {
5927
6181
  discountDuration: terms2.discount.duration === "once" ? "first_term" : "while_active"
5928
6182
  } : {},
5929
6183
  ...terms2.initialTermEndsAt !== void 0 ? { initialTermEndsAt: terms2.initialTermEndsAt } : {},
5930
- renewalCents: terms2.discount?.duration === "forever" ? dueToday : tier.priceCents,
6184
+ renewalCents: (terms2.discount?.duration === "forever" ? dueToday : tier.priceCents) + seatCents,
5931
6185
  autoRenew: true
5932
6186
  } } : {}
5933
6187
  };
@@ -5947,22 +6201,23 @@ async function createPaymentQuote(input) {
5947
6201
  // src/membership-quote-projection.ts
5948
6202
  var MEMBERSHIP_QUOTE_NAMESPACE = "membershipQuoteProjections";
5949
6203
  var keys2 = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId"];
5950
- var fail5 = (code) => {
6204
+ var fail6 = (code) => {
5951
6205
  throw Object.assign(new Error(code), { code });
5952
6206
  };
5953
- var load3 = async (db, ns, id6) => (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
6207
+ var load3 = async (db, ns, id7) => (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
5954
6208
  async function restore2(row2, target, now, allowExpired = false) {
5955
- if (!row2 || keys2.some((key3) => row2[key3] !== target[key3]) || !Number.isSafeInteger(row2.revision) || Number(row2.revision) < 1 || typeof row2.requestId !== "string") return fail5("membership_quote_unavailable");
5956
- if (row2.status !== "ready") return fail5("membership_quote_pending");
6209
+ if (!row2 || keys2.some((key3) => row2[key3] !== target[key3]) || !Number.isSafeInteger(row2.revision) || Number(row2.revision) < 1 || typeof row2.requestId !== "string") return fail6("membership_quote_unavailable");
6210
+ if (row2.status !== "ready") return fail6("membership_quote_pending");
5957
6211
  const offer = await verifyIssuedMembershipOffer(row2.offer, { ...target, offerId: await membershipOfferIdForRequest({
5958
6212
  ...target,
5959
6213
  requestId: row2.requestId
5960
6214
  }), now, lifetimeMs: 6e4 });
5961
- if (offer.issuedAt > now + 3e4 || !allowExpired && offer.expiresAt <= now) return fail5("membership_quote_expired");
6215
+ if (offer.issuedAt > now + 3e4 || !allowExpired && offer.expiresAt <= now) return fail6("membership_quote_expired");
5962
6216
  const tier = row2.tier, quote = row2.quote;
5963
6217
  const offering = offer.contract.offering;
6218
+ if (quote?.seatOffer && quote.seatOffer.amountCents !== offering.seatPriceCents) return fail6("membership_quote_conflict");
5964
6219
  if (!tier || tier.id !== target.skuId || tier.groupId !== target.chapterId || !tier.stripePriceId || tier.priceCents !== offering.basePriceCents || tier.currency !== offering.currency || tier.interval !== offering.interval || !quote) {
5965
- return fail5("membership_catalog_mismatch");
6220
+ return fail6("membership_catalog_mismatch");
5966
6221
  }
5967
6222
  const expected = await createPaymentQuote({
5968
6223
  ...target,
@@ -5971,19 +6226,21 @@ async function restore2(row2, target, now, allowExpired = false) {
5971
6226
  policy: { refundPolicyText: quote.refundPolicyText, trustCopy: quote.trustCopy },
5972
6227
  billingTerms: offer.contract.billingTerms,
5973
6228
  presentation: quote.presentation,
5974
- founding: foundingOf(offer)
6229
+ founding: foundingOf(offer),
6230
+ seatOffer: quote.seatOffer,
6231
+ namedSeat: quote.namedSeat
5975
6232
  });
5976
- if (quote.digest !== expected.digest) return fail5("membership_quote_conflict");
6233
+ if (quote.digest !== expected.digest) return fail6("membership_quote_conflict");
5977
6234
  return { tier, quote: expected, billingTerms: offer.contract.billingTerms, membershipOffer: offer, projectionRevision: Number(row2.revision) };
5978
6235
  }
5979
6236
  async function readMembershipQuote(db, target, now) {
5980
6237
  return restore2(await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, target.applicationId), target, now);
5981
6238
  }
5982
6239
  async function prepareMembershipQuote(db, input, issue) {
5983
- const { applicationId: id6, now, app } = input;
5984
- if (app.id !== id6 || app.groupId !== input.chapterId || app.tierId !== input.skuId || app.stripeRuntime !== input.runtime || app.status !== "submitted" || app.canceled === true || app.giftOfferId || app.giftClaimId || app.namedSeatId || app.namedSeatClaimPending) return fail5("membership_quote_unavailable");
5985
- let row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
5986
- if (row2 && keys2.some((key3) => row2[key3] !== input[key3])) return fail5("membership_quote_conflict");
6240
+ const { applicationId: id7, now, app } = input;
6241
+ if (app.id !== id7 || app.groupId !== input.chapterId || app.tierId !== input.skuId || app.stripeRuntime !== input.runtime || app.status !== "submitted" || app.canceled === true || app.giftOfferId || app.giftClaimId || app.namedSeatId || app.namedSeatClaimPending) return fail6("membership_quote_unavailable");
6242
+ let row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7);
6243
+ if (row2 && keys2.some((key3) => row2[key3] !== input[key3])) return fail6("membership_quote_conflict");
5987
6244
  if (row2?.status === "ready") {
5988
6245
  try {
5989
6246
  return await restore2(row2, input, now);
@@ -5994,15 +6251,15 @@ async function prepareMembershipQuote(db, input, issue) {
5994
6251
  const identity2 = ["groupId", "tierId", "stripeRuntime", "email", "emailNormalized", "status", "canceled", "membershipRestartId", "namedSeatId", "namedSeatClaimPending", "giftClaimId", "giftOfferId"];
5995
6252
  const guards = [{
5996
6253
  ns: "applications",
5997
- id: id6,
6254
+ id: id7,
5998
6255
  exists: true,
5999
6256
  equals: Object.fromEntries(identity2.filter((key3) => app[key3] !== void 0).map((key3) => [key3, app[key3]])),
6000
6257
  absent: identity2.filter((key3) => app[key3] === void 0)
6001
- }, { ns: "subscriptionCheckoutIntents", id: id6, exists: false }];
6258
+ }, { ns: "subscriptionCheckoutIntents", id: id7, exists: false }];
6002
6259
  if (!row2 || row2.status === "ready" || row2.status === "restart") {
6003
6260
  const revision = Number(row2?.revision ?? 0) + 1;
6004
6261
  const pending = {
6005
- id: id6,
6262
+ id: id7,
6006
6263
  ...Object.fromEntries(keys2.map((key3) => [key3, input[key3]])),
6007
6264
  revision,
6008
6265
  status: "pending",
@@ -6010,25 +6267,25 @@ async function prepareMembershipQuote(db, input, issue) {
6010
6267
  updatedAt: now
6011
6268
  };
6012
6269
  try {
6013
- await db.transact([{ t: "update", ns: MEMBERSHIP_QUOTE_NAMESPACE, id: id6, attrs: pending }], {
6014
- mutationId: `membership-quote:${id6}:${revision}`,
6270
+ await db.transact([{ t: "update", ns: MEMBERSHIP_QUOTE_NAMESPACE, id: id7, attrs: pending }], {
6271
+ mutationId: `membership-quote:${id7}:${revision}`,
6015
6272
  guards: [...guards, {
6016
6273
  ns: MEMBERSHIP_QUOTE_NAMESPACE,
6017
- id: id6,
6274
+ id: id7,
6018
6275
  exists: Boolean(row2),
6019
6276
  ...row2 ? { equals: { revision: row2.revision, status: row2.status } } : {}
6020
6277
  }]
6021
6278
  });
6022
6279
  } catch (error) {
6023
6280
  if (error.code !== "transact_guard_failed") throw error;
6024
- const winner = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
6281
+ const winner = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7);
6025
6282
  if (!winner || winner.revision !== revision) throw error;
6026
6283
  }
6027
- row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
6284
+ row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7);
6028
6285
  }
6029
6286
  if (row2?.status === "ready") return restore2(row2, input, now);
6030
6287
  if (!row2 || row2.status !== "pending" || keys2.some((key3) => row2[key3] !== input[key3]) || typeof row2.requestId !== "string") {
6031
- return fail5("membership_quote_pending");
6288
+ return fail6("membership_quote_pending");
6032
6289
  }
6033
6290
  const target = Object.fromEntries(keys2.map((key3) => [key3, input[key3]]));
6034
6291
  const offer = await verifyIssuedMembershipOffer(await issue({ ...target, requestId: row2.requestId }), {
@@ -6049,7 +6306,8 @@ async function prepareMembershipQuote(db, input, issue) {
6049
6306
  policy: input.policy,
6050
6307
  billingTerms: offer.contract.billingTerms,
6051
6308
  presentation: input.presentation,
6052
- founding: foundingOf(offer)
6309
+ founding: foundingOf(offer),
6310
+ ...input.namedSeatsEnabled ? { seatOffer: { amountCents: offer.contract.offering.seatPriceCents } } : {}
6053
6311
  });
6054
6312
  const ready = { ...row2, offer, tier, quote, status: "ready", updatedAt: now };
6055
6313
  await restore2(ready, target, now, true);
@@ -6057,23 +6315,100 @@ async function prepareMembershipQuote(db, input, issue) {
6057
6315
  await db.transact([{
6058
6316
  t: "update",
6059
6317
  ns: MEMBERSHIP_QUOTE_NAMESPACE,
6060
- id: id6,
6318
+ id: id7,
6061
6319
  attrs: { offer, offerDigest: offer.digest, tier, quote, status: "ready", updatedAt: now }
6062
6320
  }], {
6063
- mutationId: `membership-quote-ready:${id6}:${row2.revision}`,
6321
+ mutationId: `membership-quote-ready:${id7}:${row2.revision}`,
6064
6322
  guards: [...guards, {
6065
6323
  ns: MEMBERSHIP_QUOTE_NAMESPACE,
6066
- id: id6,
6324
+ id: id7,
6067
6325
  exists: true,
6068
6326
  equals: { status: "pending", revision: row2.revision, requestId: row2.requestId }
6069
6327
  }]
6070
6328
  });
6071
6329
  } catch (error) {
6072
6330
  if (error.code !== "transact_guard_failed") throw error;
6073
- const winner = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
6331
+ const winner = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7);
6074
6332
  if (winner?.status !== "ready" || winner.requestId !== row2.requestId || winner.offerDigest !== offer.digest) throw error;
6075
6333
  }
6076
- return restore2(await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6), target, now);
6334
+ return restore2(await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7), target, now);
6335
+ }
6336
+ async function selectCheckoutNamedSeat(db, target, input) {
6337
+ if (!input.enabled) return fail6("named_seat_purchase_unavailable");
6338
+ const row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, target.applicationId);
6339
+ const current = await restore2(row2, target, input.now);
6340
+ const namedSeat = input.selection === null ? void 0 : checkoutNamedSeatSelection(input.selection, input.purchaserEmail);
6341
+ const quote = await createPaymentQuote({
6342
+ ...target,
6343
+ tier: current.tier,
6344
+ group: { merchantDisclosureText: current.quote.merchantDisclosureText },
6345
+ policy: { refundPolicyText: current.quote.refundPolicyText, trustCopy: current.quote.trustCopy },
6346
+ billingTerms: current.billingTerms,
6347
+ presentation: current.quote.presentation,
6348
+ founding: current.quote.founding,
6349
+ seatOffer: { amountCents: current.membershipOffer.contract.offering.seatPriceCents },
6350
+ namedSeat
6351
+ });
6352
+ if (quote.digest === current.quote.digest) return current;
6353
+ await db.transact([{
6354
+ t: "update",
6355
+ ns: MEMBERSHIP_QUOTE_NAMESPACE,
6356
+ id: target.applicationId,
6357
+ attrs: { quote, revision: current.projectionRevision + 1, updatedAt: input.now }
6358
+ }], {
6359
+ mutationId: `membership-quote-selection:${target.applicationId}:${current.projectionRevision}:${quote.digest}`,
6360
+ guards: [
6361
+ {
6362
+ ns: MEMBERSHIP_QUOTE_NAMESPACE,
6363
+ id: target.applicationId,
6364
+ exists: true,
6365
+ equals: { revision: current.projectionRevision, status: "ready" }
6366
+ },
6367
+ { ns: "subscriptionCheckoutIntents", id: target.applicationId, exists: false }
6368
+ ]
6369
+ });
6370
+ return restore2(await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, target.applicationId), target, input.now);
6371
+ }
6372
+
6373
+ // src/bounded-request-text.ts
6374
+ async function readBoundedRequestText(req, maxBytes) {
6375
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) throw new TypeError("invalid request body cap");
6376
+ const reader = req.body?.getReader();
6377
+ const cancel = () => {
6378
+ void reader?.cancel().catch(() => {
6379
+ });
6380
+ };
6381
+ const invalid2 = () => json({ error: "invalid JSON body" }, 400);
6382
+ let complete = false;
6383
+ req.signal.addEventListener("abort", cancel, { once: true });
6384
+ try {
6385
+ if (req.signal.aborted) return invalid2();
6386
+ const declared = req.headers.get("content-length");
6387
+ if (declared && /^\d+$/.test(declared) && Number(declared) > maxBytes) {
6388
+ return json({ error: "request body too large" }, 413);
6389
+ }
6390
+ if (!reader) return "";
6391
+ const decoder = new TextDecoder();
6392
+ let bytes = 0;
6393
+ let text8 = "";
6394
+ for (; ; ) {
6395
+ const { done, value } = await reader.read();
6396
+ if (req.signal.aborted) return invalid2();
6397
+ if (done) {
6398
+ complete = true;
6399
+ return text8 + decoder.decode();
6400
+ }
6401
+ bytes += value.byteLength;
6402
+ if (bytes > maxBytes) return json({ error: "request body too large" }, 413);
6403
+ text8 += decoder.decode(value, { stream: true });
6404
+ }
6405
+ } catch {
6406
+ return invalid2();
6407
+ } finally {
6408
+ req.signal.removeEventListener("abort", cancel);
6409
+ if (!complete) cancel();
6410
+ reader?.releaseLock();
6411
+ }
6077
6412
  }
6078
6413
 
6079
6414
  // src/payment-quote-route.ts
@@ -6107,8 +6442,11 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
6107
6442
  policy: { refundPolicyText: quote2.refundPolicyText, trustCopy: quote2.trustCopy },
6108
6443
  billingTerms,
6109
6444
  presentation: quote2.presentation,
6110
- founding: foundingOf(intent.membershipOffer)
6445
+ founding: foundingOf(intent.membershipOffer),
6446
+ seatOffer: quote2.seatOffer,
6447
+ namedSeat: quote2.namedSeat
6111
6448
  });
6449
+ if (quote2.seatOffer && quote2.seatOffer.amountCents !== intent.membershipOffer?.contract.offering.seatPriceCents) throw new Error("invalid seat snapshot");
6112
6450
  if (expected.digest !== quote2.digest) throw new Error("invalid commercial snapshot");
6113
6451
  return { tier: tier2, quote: expected, billingTerms, membershipOffer: intent.membershipOffer };
6114
6452
  }
@@ -6137,7 +6475,16 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
6137
6475
  const presentation = tierPresentationFor(control3?.document, tier.id);
6138
6476
  if (ctx.membershipAuthority) return prepareMembershipQuote(
6139
6477
  db,
6140
- { ...target, app, group, tier, policy: policy2, presentation, now: Date.now() },
6478
+ {
6479
+ ...target,
6480
+ app,
6481
+ group,
6482
+ tier,
6483
+ policy: policy2,
6484
+ presentation,
6485
+ namedSeatsEnabled: Boolean(ctx.membershipAuthority.namedSeats) && !app.membershipRestartId,
6486
+ now: Date.now()
6487
+ },
6141
6488
  (input) => ctx.membershipAuthority.issueOffer(input, env)
6142
6489
  );
6143
6490
  const quote = await createPaymentQuote({
@@ -6153,20 +6500,54 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
6153
6500
  });
6154
6501
  return { tier, quote, billingTerms: void 0 };
6155
6502
  }
6156
- async function paymentQuoteRoute(url, env, ctx, prepare = false) {
6157
- const id6 = url.searchParams.get("application");
6158
- if (!id6 || id6.length > 160) return json({ error: "application required" }, 400);
6503
+ async function paymentQuoteRoute(url, env, ctx, prepare = false, request) {
6504
+ let selection;
6505
+ if (request?.body) {
6506
+ try {
6507
+ const text8 = await readBoundedRequestText(request, 2048);
6508
+ if (text8 instanceof Response) return text8;
6509
+ const body = JSON.parse(text8);
6510
+ if (!body || typeof body !== "object" || Array.isArray(body) || Object.keys(body).some((key3) => key3 !== "namedSeat")) throw new Error("invalid body");
6511
+ selection = body.namedSeat;
6512
+ } catch {
6513
+ return json({ error: "invalid seat selection" }, 400);
6514
+ }
6515
+ }
6516
+ const id7 = url.searchParams.get("application");
6517
+ if (!id7 || id7.length > 160) return json({ error: "application required" }, 400);
6159
6518
  const db = ctx.makeDb(env);
6160
- const app = (await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } })).applications?.[0];
6519
+ const app = (await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } })).applications?.[0];
6161
6520
  if (!app || String(app.groupId ?? ctx.chapter.id) !== ctx.chapter.id) return json({ error: "not found" }, 404);
6162
6521
  if (typeof app.stripeRuntime === "string" && app.stripeRuntime !== stripeRuntimeIdentity(env.ODLA_RUNTIME)) return json({ error: "not found" }, 404);
6163
6522
  if (app.status !== "submitted" || app.giftOfferId || app.namedSeatId || app.namedSeatClaimPending || app.giftClaimId || app.canceled === true) return json({ error: "membership checkout unavailable" }, 409);
6164
6523
  const group = (await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups?.[0];
6165
6524
  if (!group) return json({ error: "payments not configured" }, 503);
6166
6525
  try {
6167
- const { quote } = await checkoutQuoteForApplication(db, app, group, env, ctx, prepare);
6168
- return new Response(JSON.stringify(quote), { headers: { "content-type": "application/json", "cache-control": "no-store" } });
6526
+ let { quote } = await checkoutQuoteForApplication(db, app, group, env, ctx, prepare);
6527
+ const intent = (await db.query({ subscriptionCheckoutIntents: { $: { where: { id: id7 }, limit: 1 } } })).subscriptionCheckoutIntents?.[0];
6528
+ if (prepare && !intent && !quote.seatOffer && ctx.membershipAuthority?.namedSeats && !app.membershipRestartId && selection === void 0) selection = null;
6529
+ if (selection !== void 0) {
6530
+ if (intent) return json({ error: "checkout has already started", code: "checkout_intent_mismatch" }, 409);
6531
+ ({ quote } = await selectCheckoutNamedSeat(
6532
+ db,
6533
+ {
6534
+ appId: env.ODLA_APP_ID,
6535
+ chapterId: ctx.chapter.id,
6536
+ environment: env.ODLA_ENV,
6537
+ runtime: stripeRuntimeIdentity(env.ODLA_RUNTIME),
6538
+ applicationId: id7,
6539
+ skuId: String(app.tierId)
6540
+ },
6541
+ { selection, purchaserEmail: String(app.email ?? ""), now: Date.now(), enabled: Boolean(ctx.membershipAuthority?.namedSeats) && !app.membershipRestartId }
6542
+ ));
6543
+ }
6544
+ return new Response(JSON.stringify(quote), { headers: {
6545
+ "content-type": "application/json",
6546
+ "cache-control": "no-store",
6547
+ "x-chapter-checkout-started": intent ? "true" : "false"
6548
+ } });
6169
6549
  } catch (error) {
6550
+ if (error.code === "invalid_named_seat_recipient") return json({ error: "Enter a different recipient's name and email." }, 400);
6170
6551
  const expired = error.code === "membership_quote_expired";
6171
6552
  return json({ error: "payment quote unavailable", ...expired ? { code: "payment_quote_expired" } : {} }, expired ? 409 : 503);
6172
6553
  }
@@ -6272,47 +6653,6 @@ async function resumeJoin(url, env, ctx) {
6272
6653
  return json({ error: "Application continuation is unavailable. Retry when its payment or membership configuration is restored." }, 409);
6273
6654
  }
6274
6655
 
6275
- // src/bounded-request-text.ts
6276
- async function readBoundedRequestText(req, maxBytes) {
6277
- if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) throw new TypeError("invalid request body cap");
6278
- const reader = req.body?.getReader();
6279
- const cancel = () => {
6280
- void reader?.cancel().catch(() => {
6281
- });
6282
- };
6283
- const invalid2 = () => json({ error: "invalid JSON body" }, 400);
6284
- let complete = false;
6285
- req.signal.addEventListener("abort", cancel, { once: true });
6286
- try {
6287
- if (req.signal.aborted) return invalid2();
6288
- const declared = req.headers.get("content-length");
6289
- if (declared && /^\d+$/.test(declared) && Number(declared) > maxBytes) {
6290
- return json({ error: "request body too large" }, 413);
6291
- }
6292
- if (!reader) return "";
6293
- const decoder = new TextDecoder();
6294
- let bytes = 0;
6295
- let text8 = "";
6296
- for (; ; ) {
6297
- const { done, value } = await reader.read();
6298
- if (req.signal.aborted) return invalid2();
6299
- if (done) {
6300
- complete = true;
6301
- return text8 + decoder.decode();
6302
- }
6303
- bytes += value.byteLength;
6304
- if (bytes > maxBytes) return json({ error: "request body too large" }, 413);
6305
- text8 += decoder.decode(value, { stream: true });
6306
- }
6307
- } catch {
6308
- return invalid2();
6309
- } finally {
6310
- req.signal.removeEventListener("abort", cancel);
6311
- if (!complete) cancel();
6312
- reader?.releaseLock();
6313
- }
6314
- }
6315
-
6316
6656
  // src/named-seat-application.ts
6317
6657
  async function namedSeatPurchaserName(db, applicationId, chapterId, runtime) {
6318
6658
  const app = await membershipLoad(db, "applications", applicationId);
@@ -7207,10 +7547,10 @@ function tiersOf(value, errors) {
7207
7547
  return [];
7208
7548
  }
7209
7549
  unknownKeys(item, TIER_KEYS2, path, errors);
7210
- const id6 = text4(item.id, `${path}.id`, errors, 80);
7211
- if (id6 && !TIER.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7212
- if (seen.has(id6)) errors.push(`${path}.id duplicates "${id6}"`);
7213
- seen.add(id6);
7550
+ const id7 = text4(item.id, `${path}.id`, errors, 80);
7551
+ if (id7 && !TIER.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7552
+ if (seen.has(id7)) errors.push(`${path}.id duplicates "${id7}"`);
7553
+ seen.add(id7);
7214
7554
  const name = text4(item.name, `${path}.name`, errors, 160);
7215
7555
  if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
7216
7556
  if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
@@ -7231,7 +7571,7 @@ function tiersOf(value, errors) {
7231
7571
  }
7232
7572
  const blurb = item.blurb === void 0 ? void 0 : text4(item.blurb, `${path}.blurb`, errors, 4e3);
7233
7573
  return [{
7234
- id: id6,
7574
+ id: id7,
7235
7575
  name,
7236
7576
  available: item.available === true,
7237
7577
  sortOrder: Number(item.sortOrder) || 0,
@@ -7293,15 +7633,15 @@ function policyOf(value, errors, tiers) {
7293
7633
  if (input.tierPolicies !== void 0) {
7294
7634
  const overrides = recordOf(input.tierPolicies);
7295
7635
  if (!overrides || Object.keys(overrides).length > 20) errors.push("document.policy.tierPolicies must be a bounded tier map");
7296
- else for (const [id6, raw2] of Object.entries(overrides)) {
7297
- const path = `document.policy.tierPolicies.${id6}`;
7636
+ else for (const [id7, raw2] of Object.entries(overrides)) {
7637
+ const path = `document.policy.tierPolicies.${id7}`;
7298
7638
  const policy2 = recordOf(raw2);
7299
- if (!tiers.some((tier) => tier.id === id6) || !policy2) {
7639
+ if (!tiers.some((tier) => tier.id === id7) || !policy2) {
7300
7640
  errors.push(`${path} must name a declared tier and policy`);
7301
7641
  continue;
7302
7642
  }
7303
7643
  unknownKeys(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
7304
- tierPolicies[id6] = {
7644
+ tierPolicies[id7] = {
7305
7645
  refundPolicyText: text4(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
7306
7646
  ...policy2.trustCopy !== void 0 ? { trustCopy: text4(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
7307
7647
  };
@@ -7594,14 +7934,14 @@ function validateMemberContentDocument(input, options = {}) {
7594
7934
  continue;
7595
7935
  }
7596
7936
  unknownKeys2(raw2, PAGE_KEYS, path, errors);
7597
- const id6 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
7937
+ const id7 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
7598
7938
  const slug = textValue(raw2.slug, `${path}.slug`, errors, 120, true) ?? "";
7599
7939
  const title = textValue(raw2.title, `${path}.title`, errors, MAX_TEXT, true) ?? "";
7600
- if (id6 && !ID4.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7940
+ if (id7 && !ID4.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7601
7941
  if (slug && !SLUG2.test(slug)) errors.push(`${path}.slug must be a lowercase URL slug`);
7602
- if (ids2.has(id6) || knownParentIds.has(id6)) errors.push(`${path}.id duplicates page "${id6}"`);
7942
+ if (ids2.has(id7) || knownParentIds.has(id7)) errors.push(`${path}.id duplicates page "${id7}"`);
7603
7943
  if (slugs.has(slug) || (options.parentPages ?? []).some((page) => page.slug === slug)) errors.push(`${path}.slug duplicates "${slug}"`);
7604
- ids2.add(id6);
7944
+ ids2.add(id7);
7605
7945
  slugs.add(slug);
7606
7946
  const navRaw = record2(raw2.navigation);
7607
7947
  let navigation = { section: "General", order: index2 };
@@ -7657,7 +7997,7 @@ function validateMemberContentDocument(input, options = {}) {
7657
7997
  else if (owner === "chapter") upsell = { owner, ...offer ? { offer } : {} };
7658
7998
  }
7659
7999
  }
7660
- pages.push({ id: id6, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
8000
+ pages.push({ id: id7, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
7661
8001
  }
7662
8002
  const allIds = /* @__PURE__ */ new Set([...knownParentIds, ...ids2]);
7663
8003
  for (const [index2, page] of pages.entries()) {
@@ -7800,20 +8140,20 @@ async function loadLatestMemberContentControl(db, targetId, environment) {
7800
8140
  return control(Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
7801
8141
  }
7802
8142
  async function loadLocalMemberContent(db, chapterId, environment) {
7803
- const id6 = `member-content-local:${environment}`;
7804
- const rows5 = (await db.query({ memberContentLocal: { $: { where: { id: id6 }, limit: 1 } } })).memberContentLocal;
8143
+ const id7 = `member-content-local:${environment}`;
8144
+ const rows5 = (await db.query({ memberContentLocal: { $: { where: { id: id7 }, limit: 1 } } })).memberContentLocal;
7805
8145
  return local2(Array.isArray(rows5) ? rows5[0] : void 0);
7806
8146
  }
7807
8147
  async function materializeMemberContentControl(db, envelope, previous, receivedAt) {
7808
8148
  const { document, digest } = envelope;
7809
8149
  const target = document.target;
7810
- const id6 = `member-content-control:${target.environment}:${document.revision}`;
8150
+ const id7 = `member-content-control:${target.environment}:${document.revision}`;
7811
8151
  const ops = [];
7812
8152
  if (previous?.active) {
7813
8153
  ops.push({ t: "update", ns: "memberContentControls", id: previous.id, attrs: { active: false } });
7814
8154
  }
7815
- ops.push({ t: "update", ns: "memberContentControls", id: id6, attrs: {
7816
- id: id6,
8155
+ ops.push({ t: "update", ns: "memberContentControls", id: id7, attrs: {
8156
+ id: id7,
7817
8157
  sourceId: document.source.appId,
7818
8158
  targetId: target.chapterId,
7819
8159
  environment: target.environment,
@@ -7826,11 +8166,11 @@ async function materializeMemberContentControl(db, envelope, previous, receivedA
7826
8166
  await db.transact(ops, { mutationId: `member-content-control:${target.environment}:${document.revision}:${digest}` });
7827
8167
  }
7828
8168
  async function saveLocalMemberContent(db, document, environment, expectedRevision, updatedAt, updatedBy) {
7829
- const id6 = `member-content-local:${environment}`;
8169
+ const id7 = `member-content-local:${environment}`;
7830
8170
  const current = await loadLocalMemberContent(db, document.source.appId, environment);
7831
8171
  if ((current?.revision ?? 0) !== expectedRevision) throw new Error("member_content_revision_conflict");
7832
8172
  const row2 = {
7833
- id: id6,
8173
+ id: id7,
7834
8174
  chapterId: document.source.appId,
7835
8175
  environment,
7836
8176
  revision: document.revision,
@@ -7838,9 +8178,9 @@ async function saveLocalMemberContent(db, document, environment, expectedRevisio
7838
8178
  updatedAt,
7839
8179
  ...updatedBy ? { updatedBy } : {}
7840
8180
  };
7841
- await db.transact([{ t: "update", ns: "memberContentLocal", id: id6, attrs: row2 }], {
8181
+ await db.transact([{ t: "update", ns: "memberContentLocal", id: id7, attrs: row2 }], {
7842
8182
  mutationId: `member-content-local:${environment}:${document.revision}`,
7843
- guards: current ? [{ ns: "memberContentLocal", id: id6, exists: true, equals: { revision: expectedRevision } }] : [{ ns: "memberContentLocal", id: id6, exists: false }]
8183
+ guards: current ? [{ ns: "memberContentLocal", id: id7, exists: true, equals: { revision: expectedRevision } }] : [{ ns: "memberContentLocal", id: id7, exists: false }]
7844
8184
  });
7845
8185
  return row2;
7846
8186
  }
@@ -7977,7 +8317,7 @@ var handleAdminMemberContent = async (req, url, env, ctx) => {
7977
8317
  parent: parent ? { revision: parent.revision, digest: parent.digest, document: parent.document } : null,
7978
8318
  chapter: chapter ? { revision: chapter.revision, document: chapter.document } : null,
7979
8319
  effective,
7980
- tiers: tiers.map(({ id: id6, name, priceCents, active }) => ({ id: id6, name, priceCents, active })),
8320
+ tiers: tiers.map(({ id: id7, name, priceCents, active }) => ({ id: id7, name, priceCents, active })),
7981
8321
  localAuthoring: ctx.chapter.memberContent.localAuthoring
7982
8322
  });
7983
8323
  }
@@ -8135,11 +8475,11 @@ var handleAdminAdmissionGrants = async (req, url, env, ctx) => {
8135
8475
  return json({ error: "expiresAt must be a future epoch timestamp" }, 400);
8136
8476
  }
8137
8477
  const sourceGrantId = crypto.randomUUID();
8138
- const id6 = `admission:local:${ctx.chapter.id}:${sourceGrantId}`;
8478
+ const id7 = `admission:local:${ctx.chapter.id}:${sourceGrantId}`;
8139
8479
  const createdAt = Date.now();
8140
8480
  const actor = auth.user.email ?? auth.user.userId;
8141
- await auth.db.transact([{ t: "update", ns: "admissionGrants", id: id6, attrs: {
8142
- id: id6,
8481
+ await auth.db.transact([{ t: "update", ns: "admissionGrants", id: id7, attrs: {
8482
+ id: id7,
8143
8483
  sourceGrantId,
8144
8484
  sourceScope: "local",
8145
8485
  sourceId: ctx.chapter.id,
@@ -8155,7 +8495,7 @@ var handleAdminAdmissionGrants = async (req, url, env, ctx) => {
8155
8495
  updatedAt: createdAt,
8156
8496
  updatedBy: actor
8157
8497
  } }], { mutationId: `admission-local-create:${sourceGrantId}` });
8158
- return json({ grant: { id: id6, sourceGrantId, sourceScope: "local", email, decision, active: true, createdAt, ...note ? { note } : {}, ...expiresAt ? { expiresAt } : {} } }, 201);
8498
+ return json({ grant: { id: id7, sourceGrantId, sourceScope: "local", email, decision, active: true, createdAt, ...note ? { note } : {}, ...expiresAt ? { expiresAt } : {} } }, 201);
8159
8499
  }
8160
8500
  const prefix = "/api/admin/admission-grants/";
8161
8501
  let revokeId = "";
@@ -8169,20 +8509,20 @@ var handleAdminAdmissionGrants = async (req, url, env, ctx) => {
8169
8509
  if (/^admission:local:[a-z0-9-]+:[0-9a-f-]+$/.test(revokeId) && req.method === "DELETE") {
8170
8510
  const auth = await gate(req, env, ctx);
8171
8511
  if (auth instanceof Response) return auth;
8172
- const id6 = revokeId;
8173
- const found = await auth.db.query({ admissionGrants: { $: { where: { id: id6 }, limit: 1 } } });
8512
+ const id7 = revokeId;
8513
+ const found = await auth.db.query({ admissionGrants: { $: { where: { id: id7 }, limit: 1 } } });
8174
8514
  const row2 = Array.isArray(found.admissionGrants) ? found.admissionGrants[0] : void 0;
8175
8515
  if (!row2 || row2.targetId !== ctx.chapter.id || row2.sourceScope !== "local") return json({ error: "not found" }, 404);
8176
8516
  if (row2.active !== true) return json({ ok: true, replayed: true });
8177
8517
  const revokedAt = Date.now();
8178
8518
  const actor = auth.user.email ?? auth.user.userId;
8179
- await auth.db.transact([{ t: "update", ns: "admissionGrants", id: id6, attrs: {
8519
+ await auth.db.transact([{ t: "update", ns: "admissionGrants", id: id7, attrs: {
8180
8520
  active: false,
8181
8521
  revokedAt,
8182
8522
  revokedBy: actor,
8183
8523
  updatedAt: revokedAt,
8184
8524
  updatedBy: actor
8185
- } }], { mutationId: `admission-local-revoke:${id6}` });
8525
+ } }], { mutationId: `admission-local-revoke:${id7}` });
8186
8526
  return json({ ok: true, replayed: false });
8187
8527
  }
8188
8528
  return null;
@@ -8244,8 +8584,8 @@ async function loadLeaderAdmissionAssignments(db, targetId) {
8244
8584
  return await readAdmissionRows(db, "leaderAdmissionAssignments", { targetId }, MAX_ASSIGNMENTS);
8245
8585
  }
8246
8586
  async function loadLeaderAdmissionDelivery(db, targetId, environment) {
8247
- const id6 = deliveryId(targetId, environment);
8248
- const result = await db.query({ leaderAdmissionDeliveries: { $: { where: { id: id6 }, limit: 1 } } });
8587
+ const id7 = deliveryId(targetId, environment);
8588
+ const result = await db.query({ leaderAdmissionDeliveries: { $: { where: { id: id7 }, limit: 1 } } });
8249
8589
  return admissionRows(result.leaderAdmissionDeliveries)[0] ?? null;
8250
8590
  }
8251
8591
  function personAdmissionEmail(ctx, record4) {
@@ -8273,8 +8613,8 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8273
8613
  const now = Date.now();
8274
8614
  const previous = await loadLeaderAdmissionDelivery(opts.db, opts.target.id, opts.env.ODLA_ENV);
8275
8615
  const current = await loadLeaderAdmissionAssignments(opts.db, opts.target.id);
8276
- const id6 = assignmentId(opts.target.id, opts.personId);
8277
- const existing = current.find((row2) => row2.id === id6);
8616
+ const id7 = assignmentId(opts.target.id, opts.personId);
8617
+ const existing = current.find((row2) => row2.id === id7);
8278
8618
  const digest = await signupControlDigest({
8279
8619
  targetId: opts.target.id,
8280
8620
  personId: opts.personId,
@@ -8304,7 +8644,7 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8304
8644
  return { delivery: saved, duplicate: true };
8305
8645
  }
8306
8646
  const assignment = {
8307
- id: id6,
8647
+ id: id7,
8308
8648
  targetId: opts.target.id,
8309
8649
  personId: opts.personId,
8310
8650
  grantId: existing?.grantId ?? crypto.randomUUID().replaceAll("-", ""),
@@ -8319,7 +8659,7 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8319
8659
  updatedAt: now,
8320
8660
  updatedBy: opts.actor
8321
8661
  };
8322
- const desired = current.filter((row2) => row2.id !== id6 && row2.active).concat(assignment.active ? [assignment] : []);
8662
+ const desired = current.filter((row2) => row2.id !== id7 && row2.active).concat(assignment.active ? [assignment] : []);
8323
8663
  const grants = await Promise.all(desired.map((row2) => grantInput(opts.db, opts.ctx, row2, opts.resolveEmail)));
8324
8664
  const revision = (previous?.revision ?? 0) + 1;
8325
8665
  const envelope = await createAdmissionGrantEnvelope({
@@ -8342,7 +8682,7 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8342
8682
  updatedAt: now
8343
8683
  };
8344
8684
  const ops = [
8345
- { t: "update", ns: "leaderAdmissionAssignments", id: id6, attrs: assignment },
8685
+ { t: "update", ns: "leaderAdmissionAssignments", id: id7, attrs: assignment },
8346
8686
  { t: "update", ns: "leaderAdmissionDeliveries", id: delivery.id, attrs: delivery },
8347
8687
  { t: "update", ns: "leaderAdmissionMutations", id: mutationReceiptId, attrs: {
8348
8688
  id: mutationReceiptId,
@@ -8357,7 +8697,7 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8357
8697
  ...!opts.note && existing?.note !== void 0 ? ["note"] : [],
8358
8698
  ...opts.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
8359
8699
  ];
8360
- if (retract.length) ops.push({ t: "retract", ns: "leaderAdmissionAssignments", id: id6, attrs: retract });
8700
+ if (retract.length) ops.push({ t: "retract", ns: "leaderAdmissionAssignments", id: id7, attrs: retract });
8361
8701
  const tx = await opts.db.transact(ops, {
8362
8702
  mutationId: `leader-admission:${opts.target.id}:${opts.mutationId}`,
8363
8703
  guards: previous ? [{ ns: "leaderAdmissionDeliveries", id: previous.id, exists: true, equals: { revision: previous.revision } }] : [{ ns: "leaderAdmissionDeliveries", id: delivery.id, exists: false }]
@@ -8417,11 +8757,11 @@ function projectRecord(record4, allowed2, editable, stagesEnabled, crm) {
8417
8757
  allowed2.flatMap((name) => Object.prototype.hasOwnProperty.call(record4.fields, name) ? [[name, record4.fields[name]]] : [])
8418
8758
  );
8419
8759
  const def = crm.type(record4.type);
8420
- const editFields = editable.map((id6) => {
8421
- const field = def.fields[id6];
8760
+ const editFields = editable.map((id7) => {
8761
+ const field = def.fields[id7];
8422
8762
  return {
8423
- id: id6,
8424
- label: field.label ?? id6,
8763
+ id: id7,
8764
+ label: field.label ?? id7,
8425
8765
  type: field.type,
8426
8766
  required: field.required ?? false,
8427
8767
  ...field.options ? { options: field.options } : {}
@@ -8538,8 +8878,8 @@ async function gate2(req, env, ctx) {
8538
8878
  if (!await networkWriteAllowed(req, db, user, ctx, true)) return { response: reject6(403, "super_admin_required", "super-admin required") };
8539
8879
  return { db, user };
8540
8880
  }
8541
- function targetFor(ctx, id6) {
8542
- return ctx.chapter.network.targets.find((target) => target.id === id6 && target.admissionGrants);
8881
+ function targetFor(ctx, id7) {
8882
+ return ctx.chapter.network.targets.find((target) => target.id === id7 && target.admissionGrants);
8543
8883
  }
8544
8884
  async function deliverSaved(db, env, ctx, target, delivery) {
8545
8885
  if (delivery.status === "acknowledged") return delivery;
@@ -8643,7 +8983,7 @@ var handleAdminNetworkAdmissions = async (req, url, env, ctx) => {
8643
8983
  if (!personId || !MUTATION_ID2.test(mutationId)) {
8644
8984
  return reject6(422, "invalid_request", "personId and a stable mutationId are required");
8645
8985
  }
8646
- const resolveEmail = (id6) => resolveAdmissionPersonEmail(db, env, ctx, target, id6);
8986
+ const resolveEmail = (id7) => resolveAdmissionPersonEmail(db, env, ctx, target, id7);
8647
8987
  let email;
8648
8988
  try {
8649
8989
  email = await resolveEmail(personId);
@@ -8882,28 +9222,28 @@ var handleSchedule = async (req, url, env, ctx) => {
8882
9222
  };
8883
9223
 
8884
9224
  // src/payments-stripe.ts
8885
- var import_stripe11 = require("@odla-ai/stripe");
9225
+ var import_stripe12 = require("@odla-ai/stripe");
8886
9226
 
8887
9227
  // src/payments-refund.ts
8888
- var import_stripe9 = require("@odla-ai/stripe");
9228
+ var import_stripe10 = require("@odla-ai/stripe");
8889
9229
  var row = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
8890
- var id2 = (value) => typeof value === "string" ? value : String(row(value).id ?? "");
9230
+ var id3 = (value) => typeof value === "string" ? value : String(row(value).id ?? "");
8891
9231
  var providerId2 = (value, prefix) => new RegExp(`^${prefix}_[A-Za-z0-9]+$`).test(value);
8892
- function fail6(code = "refund_target_mismatch") {
9232
+ function fail7(code = "refund_target_mismatch") {
8893
9233
  throw Object.assign(new Error(code), { code });
8894
9234
  }
8895
9235
  async function refundSubscriptionPayment(secretKey, input) {
8896
- if (!providerId2(input.customerId, "cus") || !providerId2(input.subscriptionId, "sub")) fail6();
9236
+ if (!providerId2(input.customerId, "cus") || !providerId2(input.subscriptionId, "sub")) fail7();
8897
9237
  const call = async (method, path, params, key3) => {
8898
- const result = await (0, import_stripe9.stripeRequest)({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
8899
- if (!result.ok) fail6("stripe_error");
8900
- if (!result.body || typeof result.body !== "object" || Array.isArray(result.body)) fail6("stripe_invalid_response");
9238
+ const result = await (0, import_stripe10.stripeRequest)({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
9239
+ if (!result.ok) fail7("stripe_error");
9240
+ if (!result.body || typeof result.body !== "object" || Array.isArray(result.body)) fail7("stripe_invalid_response");
8901
9241
  return result.body;
8902
9242
  };
8903
9243
  const sub = await call("GET", `/v1/subscriptions/${input.subscriptionId}`);
8904
- if (id2(sub) !== input.subscriptionId || id2(sub.customer) !== input.customerId) fail6();
9244
+ if (id3(sub) !== input.subscriptionId || id3(sub.customer) !== input.customerId) fail7();
8905
9245
  for (const [key3, value] of Object.entries(input.metadata ?? {})) {
8906
- if (row(sub.metadata)[key3] !== value) fail6();
9246
+ if (row(sub.metadata)[key3] !== value) fail7();
8907
9247
  }
8908
9248
  let cursor;
8909
9249
  let earliest;
@@ -8916,79 +9256,79 @@ async function refundSubscriptionPayment(secretKey, input) {
8916
9256
  limit: 100,
8917
9257
  ...cursor ? { starting_after: cursor } : {}
8918
9258
  });
8919
- if (!Array.isArray(list.data) || typeof list.has_more !== "boolean") fail6("stripe_invalid_response");
9259
+ if (!Array.isArray(list.data) || typeof list.has_more !== "boolean") fail7("stripe_invalid_response");
8920
9260
  for (const value of list.data) {
8921
9261
  const invoice2 = row(value);
8922
9262
  const subscription = invoice2.subscription ?? row(row(invoice2.parent).subscription_details).subscription;
8923
- if (!providerId2(id2(invoice2), "in") || seen.has(id2(invoice2)) || id2(subscription) !== input.subscriptionId || id2(invoice2.customer) !== input.customerId || invoice2.status !== "paid" || !Number.isSafeInteger(invoice2.created) || !Number.isSafeInteger(invoice2.amount_paid) || Number(invoice2.amount_paid) < 0) fail6();
8924
- seen.add(id2(invoice2));
9263
+ if (!providerId2(id3(invoice2), "in") || seen.has(id3(invoice2)) || id3(subscription) !== input.subscriptionId || id3(invoice2.customer) !== input.customerId || invoice2.status !== "paid" || !Number.isSafeInteger(invoice2.created) || !Number.isSafeInteger(invoice2.amount_paid) || Number(invoice2.amount_paid) < 0) fail7();
9264
+ seen.add(id3(invoice2));
8925
9265
  if (Number(invoice2.amount_paid) > 0 && (!earliest || Number(invoice2.created) < Number(earliest.created))) earliest = invoice2;
8926
9266
  }
8927
9267
  if (!list.has_more) break;
8928
- if (!list.data.length || page === 9) fail6("refund_history_incomplete");
8929
- cursor = id2(list.data[list.data.length - 1]);
9268
+ if (!list.data.length || page === 9) fail7("refund_history_incomplete");
9269
+ cursor = id3(list.data[list.data.length - 1]);
8930
9270
  }
8931
- if (!earliest) fail6("no_charge");
9271
+ if (!earliest) fail7("no_charge");
8932
9272
  const invoice = earliest;
8933
- if (input.expectedInvoiceId !== void 0 && id2(invoice) !== input.expectedInvoiceId || input.expectedAmountCents !== void 0 && invoice.amount_paid !== input.expectedAmountCents) fail6();
8934
- let chargeId = id2(invoice.charge);
8935
- let paymentIntentId = id2(invoice.payment_intent);
9273
+ if (input.expectedInvoiceId !== void 0 && id3(invoice) !== input.expectedInvoiceId || input.expectedAmountCents !== void 0 && invoice.amount_paid !== input.expectedAmountCents) fail7();
9274
+ let chargeId = id3(invoice.charge);
9275
+ let paymentIntentId = id3(invoice.payment_intent);
8936
9276
  if (!chargeId && !paymentIntentId) {
8937
- const payments = await call("GET", "/v1/invoice_payments", { invoice: id2(invoice), status: "paid", limit: 100 });
8938
- if (payments.has_more !== false || !Array.isArray(payments.data) || payments.data.length !== 1) fail6("refund_payment_ambiguous");
9277
+ const payments = await call("GET", "/v1/invoice_payments", { invoice: id3(invoice), status: "paid", limit: 100 });
9278
+ if (payments.has_more !== false || !Array.isArray(payments.data) || payments.data.length !== 1) fail7("refund_payment_ambiguous");
8939
9279
  const payment = row(payments.data[0]);
8940
- if (id2(payment.invoice) !== id2(invoice) || payment.status !== "paid" || row(payment.payment).type !== "payment_intent") fail6();
8941
- paymentIntentId = id2(row(payment.payment).payment_intent);
9280
+ if (id3(payment.invoice) !== id3(invoice) || payment.status !== "paid" || row(payment.payment).type !== "payment_intent") fail7();
9281
+ paymentIntentId = id3(row(payment.payment).payment_intent);
8942
9282
  }
8943
9283
  if (!chargeId) {
8944
- if (!providerId2(paymentIntentId, "pi")) fail6();
9284
+ if (!providerId2(paymentIntentId, "pi")) fail7();
8945
9285
  const payment = await call("GET", `/v1/payment_intents/${paymentIntentId}`);
8946
- if (id2(payment) !== paymentIntentId || id2(payment.customer) !== input.customerId || payment.status !== "succeeded") fail6();
8947
- chargeId = id2(payment.latest_charge);
9286
+ if (id3(payment) !== paymentIntentId || id3(payment.customer) !== input.customerId || payment.status !== "succeeded") fail7();
9287
+ chargeId = id3(payment.latest_charge);
8948
9288
  }
8949
- if (!providerId2(chargeId, "ch")) fail6();
9289
+ if (!providerId2(chargeId, "ch")) fail7();
8950
9290
  const charge = await call("GET", `/v1/charges/${chargeId}`);
8951
- if (id2(charge) !== chargeId || id2(charge.customer) !== input.customerId || charge.status !== "succeeded" || charge.invoice != null && id2(charge.invoice) !== id2(invoice) || paymentIntentId && id2(charge.payment_intent) !== paymentIntentId || !Number.isSafeInteger(charge.amount) || Number(charge.amount) <= 0 || !Number.isSafeInteger(charge.amount_refunded) || Number(charge.amount_refunded) < 0 || Number(charge.amount_refunded) > Number(charge.amount) || charge.currency !== invoice.currency || Number(charge.amount) !== Number(invoice.amount_paid)) fail6();
8952
- if (input.alreadyRefundedChargeId !== void 0 && (chargeId !== input.alreadyRefundedChargeId || charge.amount_refunded !== charge.amount || charge.refunded !== true)) fail6();
9291
+ if (id3(charge) !== chargeId || id3(charge.customer) !== input.customerId || charge.status !== "succeeded" || charge.invoice != null && id3(charge.invoice) !== id3(invoice) || paymentIntentId && id3(charge.payment_intent) !== paymentIntentId || !Number.isSafeInteger(charge.amount) || Number(charge.amount) <= 0 || !Number.isSafeInteger(charge.amount_refunded) || Number(charge.amount_refunded) < 0 || Number(charge.amount_refunded) > Number(charge.amount) || charge.currency !== invoice.currency || Number(charge.amount) !== Number(invoice.amount_paid)) fail7();
9292
+ if (input.alreadyRefundedChargeId !== void 0 && (chargeId !== input.alreadyRefundedChargeId || charge.amount_refunded !== charge.amount || charge.refunded !== true)) fail7();
8953
9293
  if (charge.amount_refunded !== charge.amount) {
8954
9294
  const refund = await call("POST", "/v1/refunds", {
8955
9295
  charge: chargeId,
8956
9296
  ...input.metadata ? { metadata: input.metadata } : {}
8957
9297
  }, `chapter-initial-refund:${input.subscriptionId}:${chargeId}`);
8958
- if (id2(refund.charge) !== chargeId || !["succeeded", "pending"].includes(String(refund.status)) || !Number.isSafeInteger(refund.amount) || Number(refund.amount) !== Number(charge.amount) - Number(charge.amount_refunded)) fail6("refund_not_confirmed");
9298
+ if (id3(refund.charge) !== chargeId || !["succeeded", "pending"].includes(String(refund.status)) || !Number.isSafeInteger(refund.amount) || Number(refund.amount) !== Number(charge.amount) - Number(charge.amount_refunded)) fail7("refund_not_confirmed");
8959
9299
  }
8960
9300
  let subscriptionCanceled = sub.status === "canceled";
8961
9301
  if (input.cancelSubscription !== false && !subscriptionCanceled) {
8962
9302
  const canceled = await call("DELETE", `/v1/subscriptions/${input.subscriptionId}`);
8963
- if (id2(canceled) !== input.subscriptionId || canceled.status !== "canceled") fail6("refund_cancel_incomplete");
9303
+ if (id3(canceled) !== input.subscriptionId || canceled.status !== "canceled") fail7("refund_cancel_incomplete");
8964
9304
  subscriptionCanceled = true;
8965
9305
  }
8966
9306
  const confirmed = await call("GET", `/v1/charges/${chargeId}`);
8967
- if (id2(confirmed) !== chargeId || id2(confirmed.customer) !== input.customerId || confirmed.amount !== charge.amount || confirmed.currency !== charge.currency || confirmed.amount_refunded !== charge.amount || confirmed.refunded !== true) fail6("refund_not_confirmed");
9307
+ if (id3(confirmed) !== chargeId || id3(confirmed.customer) !== input.customerId || confirmed.amount !== charge.amount || confirmed.currency !== charge.currency || confirmed.amount_refunded !== charge.amount || confirmed.refunded !== true) fail7("refund_not_confirmed");
8968
9308
  return { refundedCents: Number(charge.amount), subscriptionCanceled };
8969
9309
  }
8970
9310
 
8971
9311
  // src/subscription-payment-identity.ts
8972
- var import_stripe10 = require("@odla-ai/stripe");
8973
- var id3 = (value) => typeof value === "string" ? value : String(value?.id ?? "");
9312
+ var import_stripe11 = require("@odla-ai/stripe");
9313
+ var id4 = (value) => typeof value === "string" ? value : String(value?.id ?? "");
8974
9314
  async function tagSubscriptionPayment(secretKey, invoice, customerId, metadata, clientSecret2, applicationId) {
8975
- let paymentId = id3(invoice.payment_intent);
9315
+ let paymentId = id4(invoice.payment_intent);
8976
9316
  if (!paymentId) {
8977
- if (!/^in_[A-Za-z0-9]+$/.test(id3(invoice))) subscriptionContractMismatch();
8978
- const result2 = await (0, import_stripe10.stripeRequest)({ secretKey }, "GET", "/v1/invoice_payments", { invoice: id3(invoice), limit: 100 });
9317
+ if (!/^in_[A-Za-z0-9]+$/.test(id4(invoice))) subscriptionContractMismatch();
9318
+ const result2 = await (0, import_stripe11.stripeRequest)({ secretKey }, "GET", "/v1/invoice_payments", { invoice: id4(invoice), limit: 100 });
8979
9319
  const list = result2.body;
8980
9320
  if (!result2.ok || list?.has_more !== false || !Array.isArray(list.data) || list.data.length !== 1) subscriptionContractMismatch();
8981
9321
  const link = list.data[0];
8982
- if (id3(link.invoice) !== id3(invoice) || !["open", "paid"].includes(link.status) || link.payment?.type !== "payment_intent") subscriptionContractMismatch();
8983
- paymentId = id3(link.payment.payment_intent);
9322
+ if (id4(link.invoice) !== id4(invoice) || !["open", "paid"].includes(link.status) || link.payment?.type !== "payment_intent") subscriptionContractMismatch();
9323
+ paymentId = id4(link.payment.payment_intent);
8984
9324
  }
8985
9325
  if (!/^pi_[A-Za-z0-9]+$/.test(paymentId)) subscriptionContractMismatch();
8986
9326
  const path = `/v1/payment_intents/${paymentId}`;
8987
- const result = await (0, import_stripe10.stripeRequest)({ secretKey }, "GET", path);
9327
+ const result = await (0, import_stripe11.stripeRequest)({ secretKey }, "GET", path);
8988
9328
  const payment = result.body;
8989
- if (!result.ok || payment?.id !== paymentId || id3(payment.customer) !== customerId || payment.amount !== invoice.total || payment.currency !== invoice.currency || payment.client_secret !== clientSecret2 || payment.livemode !== (metadata.odlaEnvironment === "prod") || !Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === void 0 || payment.metadata[key3] === value)) subscriptionContractMismatch();
9329
+ if (!result.ok || payment?.id !== paymentId || id4(payment.customer) !== customerId || payment.amount !== invoice.total || payment.currency !== invoice.currency || payment.client_secret !== clientSecret2 || payment.livemode !== (metadata.odlaEnvironment === "prod") || !Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === void 0 || payment.metadata[key3] === value)) subscriptionContractMismatch();
8990
9330
  if (Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === value)) return;
8991
- const tagged = await (0, import_stripe10.stripeRequest)(
9331
+ const tagged = await (0, import_stripe11.stripeRequest)(
8992
9332
  { secretKey },
8993
9333
  "POST",
8994
9334
  path,
@@ -9000,7 +9340,7 @@ async function tagSubscriptionPayment(secretKey, invoice, customerId, metadata,
9000
9340
 
9001
9341
  // src/payments-stripe.ts
9002
9342
  async function stripeCall(sk, method, path, params, idempotencyKey) {
9003
- return (0, import_stripe11.stripeRequest)(
9343
+ return (0, import_stripe12.stripeRequest)(
9004
9344
  { secretKey: sk },
9005
9345
  method,
9006
9346
  path,
@@ -9008,7 +9348,7 @@ async function stripeCall(sk, method, path, params, idempotencyKey) {
9008
9348
  idempotencyKey ? { idempotencyKey } : {}
9009
9349
  );
9010
9350
  }
9011
- function fail7(op, r) {
9351
+ function fail8(op, r) {
9012
9352
  const err = new Error(`stripe ${op} failed: ${r.status}`);
9013
9353
  err.code = "stripe_error";
9014
9354
  throw err;
@@ -9047,6 +9387,15 @@ function createStripeProvider(config) {
9047
9387
  let customerId = input.existingCustomerId;
9048
9388
  if (customerId && !/^cus_[A-Za-z0-9]+$/.test(customerId) || input.existingSubscriptionId && (!customerId || !/^sub_[A-Za-z0-9]+$/.test(input.existingSubscriptionId))) subscriptionContractMismatch();
9049
9389
  const productId = await verifySubscriptionPrice(sk, input, Boolean(input.existingSubscriptionId));
9390
+ const seat = input.namedSeat?.amountCents ? input.namedSeat : void 0;
9391
+ const seatProductId = seat ? await verifySubscriptionPrice(sk, {
9392
+ ...input,
9393
+ priceId: seat.priceId,
9394
+ priceCents: seat.amountCents,
9395
+ billingTerms: void 0,
9396
+ namedSeat: void 0
9397
+ }, Boolean(input.existingSubscriptionId)) : void 0;
9398
+ if (seat && (!seatProductId || !productId || seatProductId === productId)) subscriptionContractMismatch();
9050
9399
  if (input.billingTerms?.discount && !productId) subscriptionContractMismatch();
9051
9400
  await prepareSubscriptionBillingTerms(sk, { ...input, discountProductId: productId }, Boolean(input.existingSubscriptionId));
9052
9401
  const billingParams = subscriptionBillingParams(input, productId);
@@ -9058,7 +9407,7 @@ function createStripeProvider(config) {
9058
9407
  { email: input.email, name: input.name, metadata: meta },
9059
9408
  `cus:${input.applicationId}`
9060
9409
  );
9061
- if (!cust.ok) fail7("customer create", cust);
9410
+ if (!cust.ok) fail8("customer create", cust);
9062
9411
  if (typeof cust.body.id !== "string" || !/^cus_[A-Za-z0-9]+$/.test(cust.body.id)) subscriptionContractMismatch();
9063
9412
  customerId = cust.body.id;
9064
9413
  }
@@ -9078,19 +9427,32 @@ function createStripeProvider(config) {
9078
9427
  {
9079
9428
  customer: customerId,
9080
9429
  "items[0][price]": input.priceId,
9430
+ ...seat ? {
9431
+ "items[1][price]": seat.priceId,
9432
+ "items[1][quantity]": 1,
9433
+ "items[1][metadata][seatId]": seat.seatId,
9434
+ "items[1][metadata][membershipId]": seat.membershipId
9435
+ } : {},
9081
9436
  payment_behavior: "default_incomplete",
9082
9437
  "payment_settings[save_default_payment_method]": "on_subscription",
9083
9438
  "payment_settings[payment_method_types][0]": "card",
9084
9439
  "expand[0]": "latest_invoice.confirmation_secret",
9085
9440
  "expand[1]": "latest_invoice.payment_intent",
9086
9441
  metadata: meta,
9087
- ...billingParams
9442
+ ...billingParams,
9443
+ ...seat && input.billingTerms?.initialTermEndsAt ? {
9444
+ "add_invoice_items[1][price_data][product]": seatProductId,
9445
+ "add_invoice_items[1][price_data][currency]": input.currency,
9446
+ "add_invoice_items[1][price_data][unit_amount]": seat.amountCents,
9447
+ "add_invoice_items[1][quantity]": 1,
9448
+ "add_invoice_items[1][discountable]": false
9449
+ } : {}
9088
9450
  },
9089
9451
  // Hardening: one subscription per application, so a client
9090
9452
  // retry between create and the db write can't orphan a second one.
9091
9453
  `sub:${input.applicationId}${input.purchaseAttemptId ? `:${input.purchaseAttemptId}` : ""}`
9092
9454
  );
9093
- if (!sub.ok) fail7("subscription create", sub);
9455
+ if (!sub.ok) fail8("subscription create", sub);
9094
9456
  if (input.billingTerms?.discount?.duration === "once" && Array.isArray(sub.body.discounts) && sub.body.discounts.length === 0) {
9095
9457
  const subscriptionId = sub.body.id;
9096
9458
  if (typeof subscriptionId !== "string" || !/^sub_[A-Za-z0-9]+$/.test(subscriptionId)) subscriptionContractMismatch();
@@ -9100,14 +9462,14 @@ function createStripeProvider(config) {
9100
9462
  "expand[2]": "discounts",
9101
9463
  "expand[3]": "latest_invoice.discounts"
9102
9464
  });
9103
- if (!expanded.ok) fail7("subscription invoice evidence", expanded);
9465
+ if (!expanded.ok) fail8("subscription invoice evidence", expanded);
9104
9466
  if (expanded.body.id !== subscriptionId) subscriptionContractMismatch();
9105
9467
  sub = expanded;
9106
9468
  }
9107
9469
  verifySubscriptionResult(sub.body, input, customerId, meta);
9108
9470
  const invoice = sub.body.latest_invoice;
9109
9471
  const clientSecret2 = clientSecretOf(sub.body);
9110
- if (!clientSecret2) fail7("subscription confirmation-secret missing", sub);
9472
+ if (!clientSecret2) fail8("subscription confirmation-secret missing", sub);
9111
9473
  await tagSubscriptionPayment(
9112
9474
  sk,
9113
9475
  invoice,
@@ -9119,7 +9481,7 @@ function createStripeProvider(config) {
9119
9481
  return { customerId, subscriptionId: String(sub.body.id), clientSecret: clientSecret2 };
9120
9482
  },
9121
9483
  async ingestWebhook(rawBody, sigHeader) {
9122
- if (!webhookSecret || !await (0, import_stripe11.verifyStripeSignature)(rawBody, sigHeader, webhookSecret)) {
9484
+ if (!webhookSecret || !await (0, import_stripe12.verifyStripeSignature)(rawBody, sigHeader, webhookSecret)) {
9123
9485
  return { ok: false, reason: "bad_signature" };
9124
9486
  }
9125
9487
  const event2 = JSON.parse(rawBody);
@@ -9319,9 +9681,9 @@ function matchesIntent(stored, expected) {
9319
9681
  "purchaseAttemptId"
9320
9682
  ].every((key3) => stored[key3] === expected[key3]) && customerMatches && JSON.stringify(storedTerms) === JSON.stringify(expected.billingTerms) && stored.membershipOffer?.digest === expected.membershipOffer?.digest && stored.paymentQuote?.digest === expected.paymentQuote?.digest;
9321
9683
  }
9322
- async function loadIntent(db, id6) {
9684
+ async function loadIntent(db, id7) {
9323
9685
  return first3((await db.query({
9324
- subscriptionCheckoutIntents: { $: { where: { id: id6 }, limit: 1 } }
9686
+ subscriptionCheckoutIntents: { $: { where: { id: id7 }, limit: 1 } }
9325
9687
  })).subscriptionCheckoutIntents);
9326
9688
  }
9327
9689
  async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCheckout) {
@@ -9342,16 +9704,22 @@ async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCh
9342
9704
  if (input.purchaseAttemptId !== application?.membershipRestartId) mismatch();
9343
9705
  if (application?.namedSeatId || application?.namedSeatClaimPending || application?.giftClaimId) mismatch();
9344
9706
  if (input.membershipOffer && (!application || application.canceled === true || application.status !== "submitted")) mismatch();
9707
+ if (input.paymentQuote?.namedSeat && application?.email !== input.email) mismatch();
9345
9708
  const applicationGuard = {
9346
9709
  ns: "applications",
9347
9710
  id: input.applicationId,
9348
9711
  exists: true,
9349
- equals: { status: "submitted", ...input.purchaseAttemptId ? { membershipRestartId: input.purchaseAttemptId } : {}, ...input.membershipOffer ? {
9350
- groupId: input.groupId,
9351
- tierId: input.tierId,
9352
- stripeRuntime: stripeRuntimeIdentity(input.runtime),
9353
- ...application?.canceled !== void 0 ? { canceled: application.canceled } : {}
9354
- } : {} },
9712
+ equals: {
9713
+ status: "submitted",
9714
+ ...input.paymentQuote?.namedSeat ? { email: input.email } : {},
9715
+ ...input.purchaseAttemptId ? { membershipRestartId: input.purchaseAttemptId } : {},
9716
+ ...input.membershipOffer ? {
9717
+ groupId: input.groupId,
9718
+ tierId: input.tierId,
9719
+ stripeRuntime: stripeRuntimeIdentity(input.runtime),
9720
+ ...application?.canceled !== void 0 ? { canceled: application.canceled } : {}
9721
+ } : {}
9722
+ },
9355
9723
  absent: ["namedSeatId", "namedSeatClaimPending", "giftClaimId", ...!input.purchaseAttemptId ? ["membershipRestartId"] : [], ...input.membershipOffer && application?.canceled === void 0 ? ["canceled"] : []]
9356
9724
  };
9357
9725
  let stored = await loadIntent(db, input.applicationId);
@@ -9375,7 +9743,11 @@ async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCh
9375
9743
  ns: MEMBERSHIP_QUOTE_NAMESPACE,
9376
9744
  id: input.applicationId,
9377
9745
  exists: true,
9378
- equals: { status: "ready", offerDigest: input.membershipOffer.digest }
9746
+ equals: {
9747
+ status: "ready",
9748
+ offerDigest: input.membershipOffer.digest,
9749
+ ...input.projectionRevision !== void 0 ? { revision: input.projectionRevision } : {}
9750
+ }
9379
9751
  }
9380
9752
  ] : []
9381
9753
  ]
@@ -9390,7 +9762,8 @@ async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCh
9390
9762
  if (!stored.providerSubscriptionId && input.now - Number(stored.createdAt) >= 23 * 60 * 6e4) {
9391
9763
  throw Object.assign(new Error("checkout requires provider reconciliation"), { code: "checkout_recovery_required" });
9392
9764
  }
9393
- if (input.membershipOffer) await authorizeNetworkCheckout();
9765
+ const binding = input.membershipOffer ? await authorizeNetworkCheckout() : void 0;
9766
+ if (input.paymentQuote?.namedSeat && (!binding?.namedSeat || binding.namedSeat.amountCents !== input.paymentQuote.lineItems.namedSeatCents || binding.namedSeat.recipientEmail !== input.paymentQuote.namedSeat.recipientEmail || binding.namedSeat.recipientName !== input.paymentQuote.namedSeat.recipientName || binding.namedSeat.purchaserEmail !== input.emailNormalized)) mismatch();
9394
9767
  const result = await provider.createSubscription({
9395
9768
  appId: input.appId,
9396
9769
  chapterId: input.chapterId,
@@ -9405,6 +9778,7 @@ async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCh
9405
9778
  priceCents: input.priceCents,
9406
9779
  currency: input.currency,
9407
9780
  interval: input.interval,
9781
+ ...binding?.namedSeat ? { namedSeat: binding.namedSeat } : {},
9408
9782
  ...stored.billingTerms ? { billingTerms: stored.billingTerms } : {},
9409
9783
  ...typeof stored.providerSubscriptionId === "string" ? { existingSubscriptionId: stored.providerSubscriptionId } : {},
9410
9784
  existingCustomerId: typeof stored.providerCustomerId === "string" ? stored.providerCustomerId : input.existingCustomerId
@@ -9521,6 +9895,7 @@ async function startSubscription(req, env, ctx) {
9521
9895
  return json({ error: "payment quote unavailable", ...expired ? { code: "payment_quote_changed" } : {} }, expired ? 409 : 503);
9522
9896
  }
9523
9897
  const { tier, quote, billingTerms } = checkout2;
9898
+ if (quote.namedSeat && body.seatTermsAck !== true) return json({ error: "seatTermsAck required" }, 400);
9524
9899
  const membershipOffer = "membershipOffer" in checkout2 ? checkout2.membershipOffer : void 0;
9525
9900
  if (ctx.membershipAuthority && !membershipOffer) return json({ error: "checkout requires reconciliation", code: "checkout_recovery_required" }, 409);
9526
9901
  if (!tier) return json({ error: "tier not found", code: "tier_unresolved" }, 400);
@@ -9545,6 +9920,7 @@ async function startSubscription(req, env, ctx) {
9545
9920
  priceCents: billingTerms ? tier.priceCents : quote.lineItems.dueTodayCents,
9546
9921
  ...billingTerms ? { billingTerms } : {},
9547
9922
  ...membershipOffer ? { membershipOffer } : {},
9923
+ ..."projectionRevision" in checkout2 ? { projectionRevision: checkout2.projectionRevision } : {},
9548
9924
  ...ctx.requirePaymentQuote || ctx.membershipAuthority || body.quoteDigest ? { paymentQuote: quote } : {},
9549
9925
  currency: tier.currency ?? String(group.currency ?? "usd"),
9550
9926
  interval: tier.interval ?? (group.interval === "month" ? "month" : "year"),
@@ -9566,9 +9942,10 @@ async function startSubscription(req, env, ctx) {
9566
9942
  offerId: membershipOffer.id,
9567
9943
  offerDigest: membershipOffer.digest,
9568
9944
  quoteDigest: quote.digest,
9569
- priceId: String(priceId)
9945
+ priceId: String(priceId),
9946
+ ...quote.namedSeat ? { namedSeat: { ...quote.namedSeat, purchaserEmail: normalizeChapterEmail(String(app.email ?? "")) } } : {}
9570
9947
  };
9571
- await verifyMembershipCheckoutBinding(await ctx.membershipAuthority.bindCheckout(binding, env), binding);
9948
+ return verifyMembershipCheckoutBinding(await ctx.membershipAuthority.bindCheckout(binding, env), binding);
9572
9949
  } : void 0);
9573
9950
  } catch (err) {
9574
9951
  const code = paymentErrorCode(err);
@@ -9590,7 +9967,7 @@ async function ingestWebhook(req, env, ctx) {
9590
9967
  var handlePayments = async (req, url, env, ctx) => {
9591
9968
  if (ctx.chapter.mode !== "chapter") return null;
9592
9969
  if (req.method === "GET" && url.pathname === "/api/payments/quote") return paymentQuoteRoute(url, env, ctx);
9593
- if (req.method === "POST" && url.pathname === "/api/payments/quote") return paymentQuoteRoute(url, env, ctx, true);
9970
+ if (req.method === "POST" && url.pathname === "/api/payments/quote") return paymentQuoteRoute(url, env, ctx, true, req);
9594
9971
  if (req.method === "POST" && url.pathname === "/api/payments/subscription") return startSubscription(req, env, ctx);
9595
9972
  if (req.method === "POST" && url.pathname === "/api/webhooks/stripe") return ingestWebhook(req, env, ctx);
9596
9973
  return null;
@@ -9604,16 +9981,16 @@ var userFor = async (req, env, ctx) => {
9604
9981
  const user = await ctx.verifyUser(req, env);
9605
9982
  return user?.email ? user : null;
9606
9983
  };
9607
- var loadClaim = async (db, id6) => first4((await db.query({ giftClaims: { $: { where: { id: id6 }, limit: 1 } } })).giftClaims);
9984
+ var loadClaim = async (db, id7) => first4((await db.query({ giftClaims: { $: { where: { id: id7 }, limit: 1 } } })).giftClaims);
9608
9985
  async function consumeRateLimit(db, chapterId, requester, now) {
9609
9986
  const bucket = await giftRequesterBucket(chapterId, requester);
9610
- const id6 = `gift-rate:${chapterId}:${bucket}`;
9611
- const current = first4((await db.query({ giftRedemptionAttempts: { $: { where: { id: id6 }, limit: 1 } } })).giftRedemptionAttempts);
9987
+ const id7 = `gift-rate:${chapterId}:${bucket}`;
9988
+ const current = first4((await db.query({ giftRedemptionAttempts: { $: { where: { id: id7 }, limit: 1 } } })).giftRedemptionAttempts);
9612
9989
  const within = Boolean(current && now - Number(current.windowStartedAt) < 15 * 6e4);
9613
9990
  const attemptCount = within ? Number(current?.attemptCount ?? 0) + 1 : 1;
9614
9991
  if (attemptCount > 10) return false;
9615
- await db.transact([{ t: "update", ns: "giftRedemptionAttempts", id: id6, attrs: {
9616
- id: id6,
9992
+ await db.transact([{ t: "update", ns: "giftRedemptionAttempts", id: id7, attrs: {
9993
+ id: id7,
9617
9994
  chapterId,
9618
9995
  bucket,
9619
9996
  attemptCount,
@@ -9787,14 +10164,14 @@ var at = (value) => Number.isSafeInteger(value) ? Number(value) : void 0;
9787
10164
  async function readNamedSeatOffer(db, input, now) {
9788
10165
  membershipClock(now);
9789
10166
  await loadMembershipAuthorityState(db, input);
9790
- const id6 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id6);
10167
+ const id7 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id7);
9791
10168
  requireMembershipScope(member, input);
9792
10169
  const offering = membershipOfferingVersion(member.offeringSnapshot);
9793
10170
  const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
9794
10171
  if (seat) requireMembershipScope(seat, input);
9795
10172
  const purchaseEndsAt = seatPurchaseDeadline(offering, member);
9796
10173
  const offer = {
9797
- membershipId: id6,
10174
+ membershipId: id7,
9798
10175
  amountCents: offering.seatPriceCents,
9799
10176
  currency: offering.currency,
9800
10177
  interval: offering.interval,
@@ -10163,18 +10540,18 @@ var handleEmailDeliveryResolution = async (req, url, env, ctx) => {
10163
10540
  if (!user) return json({ error: "unauthorized" }, 401);
10164
10541
  if (!await ctx.isAdmin(db, user)) return json({ error: "forbidden" }, 403);
10165
10542
  try {
10166
- const id6 = decodeURIComponent(match[1]);
10543
+ const id7 = decodeURIComponent(match[1]);
10167
10544
  const body = await namedSeatRequest(req);
10168
10545
  if (Object.keys(body).some((key3) => !["expectedAttempt", "expectedState", "decision", "reason", "messageId"].includes(key3)) || body.messageId !== void 0 && (typeof body.messageId !== "string" || body.messageId.length > 255 || /[\r\n]/.test(body.messageId))) {
10169
10546
  return json({ error: "invalid delivery evidence" }, 400);
10170
10547
  }
10171
- const chapterDb = db, row2 = await membershipLoad(chapterDb, "emailLog", id6);
10548
+ const chapterDb = db, row2 = await membershipLoad(chapterDb, "emailLog", id7);
10172
10549
  if (!row2 || row2.groupId !== ctx.chapter.id) return json({ error: "not found" }, 404);
10173
10550
  if (row2.applicationId) {
10174
10551
  const app = await membershipLoad(chapterDb, "applications", String(row2.applicationId));
10175
10552
  if (!app || !chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME)) return json({ error: "not found" }, 404);
10176
10553
  }
10177
- await reconcileLifecycleDelivery(chapterDb, { ...body, id: id6, groupId: ctx.chapter.id, actorId: user.userId }, Date.now());
10554
+ await reconcileLifecycleDelivery(chapterDb, { ...body, id: id7, groupId: ctx.chapter.id, actorId: user.userId }, Date.now());
10178
10555
  return json({ ok: true, sent: false });
10179
10556
  } catch {
10180
10557
  return json({ error: "delivery evidence is invalid, stale, or still in progress; reload the exact attempt" }, 409);
@@ -10277,13 +10654,13 @@ var handlePeopleProfile = async (req, url, env, ctx) => {
10277
10654
  const user = await ctx.verifyUser(req, env);
10278
10655
  if (!user) return json({ error: "unauthorized" }, 401);
10279
10656
  if (!await ctx.isAdmin(db, user)) return json({ error: "forbidden" }, 403);
10280
- let id6;
10657
+ let id7;
10281
10658
  try {
10282
- id6 = dedicated ? url.searchParams.get("recordId") ?? "" : decodeURIComponent(match[1]);
10659
+ id7 = dedicated ? url.searchParams.get("recordId") ?? "" : decodeURIComponent(match[1]);
10283
10660
  } catch {
10284
10661
  return json({ error: "invalid record id" }, 422);
10285
10662
  }
10286
- const record4 = await (0, import_crm13.getRecord)({ crm: ctx.chapter.crm, db }, id6);
10663
+ const record4 = await (0, import_crm13.getRecord)({ crm: ctx.chapter.crm, db }, id7);
10287
10664
  if (!record4) return json({ error: "not found" }, 404);
10288
10665
  if (record4.type !== "person") return dedicated ? json({ error: "person required" }, 422) : null;
10289
10666
  if (match?.[2]) {
@@ -10298,9 +10675,9 @@ var handlePeopleProfile = async (req, url, env, ctx) => {
10298
10675
  }
10299
10676
  if (req.method === "GET" && dedicated) {
10300
10677
  const excluded = profileReadOnlyFields(record4, ctx);
10301
- const fields = Object.entries(ctx.chapter.crm.type(record4.type).fields).filter(([id7]) => !excluded.includes(id7)).map(([id7, field]) => ({
10302
- id: id7,
10303
- label: field.label ?? id7,
10678
+ const fields = Object.entries(ctx.chapter.crm.type(record4.type).fields).filter(([id8]) => !excluded.includes(id8)).map(([id8, field]) => ({
10679
+ id: id8,
10680
+ label: field.label ?? id8,
10304
10681
  type: field.type,
10305
10682
  required: field.required ?? false,
10306
10683
  ...field.options ? { options: field.options } : {}
@@ -11071,12 +11448,12 @@ var readJson = async (req) => {
11071
11448
  return null;
11072
11449
  }
11073
11450
  };
11074
- async function loadApp(db, id6) {
11075
- const { applications } = await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } });
11451
+ async function loadApp(db, id7) {
11452
+ const { applications } = await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } });
11076
11453
  return applications?.[0] ?? null;
11077
11454
  }
11078
- async function loadGroup(db, id6) {
11079
- const { groups } = await db.query({ groups: { $: { where: { id: id6 }, limit: 1 } } });
11455
+ async function loadGroup(db, id7) {
11456
+ const { groups } = await db.query({ groups: { $: { where: { id: id7 }, limit: 1 } } });
11080
11457
  return groups?.[0] ?? null;
11081
11458
  }
11082
11459
  var handleAdminApplicationActions = async (req, url, env, ctx) => {
@@ -11162,8 +11539,8 @@ var handleAdminApprove = async (req, url, env, ctx) => {
11162
11539
  const got = await gate4(req, env, ctx);
11163
11540
  if (got instanceof Response) return got;
11164
11541
  const db = got;
11165
- const id6 = m[1];
11166
- const app = await loadApp(db, id6);
11542
+ const id7 = m[1];
11543
+ const app = await loadApp(db, id7);
11167
11544
  if (!app) return json({ error: "not found" }, 404);
11168
11545
  const result = await approveApplication({ db, env, ctx, app, origin: url.origin, reason: "manual" });
11169
11546
  if (!result.ok) return json({ error: result.error }, result.status);
@@ -11192,10 +11569,10 @@ var handleAdminApplicationPatch = async (req, url, env, ctx) => {
11192
11569
  const got = await gate4(req, env, ctx);
11193
11570
  if (got instanceof Response) return got;
11194
11571
  const db = got;
11195
- const id6 = m[1];
11572
+ const id7 = m[1];
11196
11573
  const body = await readJson(req);
11197
11574
  if (!body) return json({ error: "invalid JSON body" }, 400);
11198
- const app = await loadApp(db, id6);
11575
+ const app = await loadApp(db, id7);
11199
11576
  if (!app) return json({ error: "not found" }, 404);
11200
11577
  return patchApplication(db, env, ctx, app, body);
11201
11578
  };
@@ -11305,10 +11682,10 @@ async function updateEmailSettings(db, ctx, env, body) {
11305
11682
  const missing = Object.fromEntries(Object.keys(events).filter((key3) => !group?.emailTemplates?.[key3]).map((key3) => [key3, { subject: "", text: "", enabled: false }]));
11306
11683
  await (0, import_crm16.saveConfig)({ db, crm: ctx.chapter.crm }, { emailTemplates: { ...missing, ...clean }, notificationEmail, replyTo, debugEmail });
11307
11684
  } else {
11308
- const id6 = { ns: "groups", attr: "id", value: ctx.chapter.id };
11685
+ const id7 = { ns: "groups", attr: "id", value: ctx.chapter.id };
11309
11686
  await db.transact([
11310
- { t: "merge", ns: "groups", id: id6, attrs: { emailTemplates: clean } },
11311
- { t: "update", ns: "groups", id: id6, attrs: { commitmentText, normsText, notificationEmail, replyTo, debugEmail, fromName } }
11687
+ { t: "merge", ns: "groups", id: id7, attrs: { emailTemplates: clean } },
11688
+ { t: "update", ns: "groups", id: id7, attrs: { commitmentText, normsText, notificationEmail, replyTo, debugEmail, fromName } }
11312
11689
  ]);
11313
11690
  }
11314
11691
  return json({ ok: true });
@@ -11323,8 +11700,8 @@ async function gate5(req, env, ctx) {
11323
11700
  if (!await ctx.isAdmin(rawDb, u)) return json({ error: "forbidden" }, 403);
11324
11701
  return rawDb;
11325
11702
  }
11326
- async function loadGroup2(db, id6) {
11327
- const { groups } = await db.query({ groups: { $: { where: { id: id6 }, limit: 1 } } });
11703
+ async function loadGroup2(db, id7) {
11704
+ const { groups } = await db.query({ groups: { $: { where: { id: id7 }, limit: 1 } } });
11328
11705
  return groups?.[0] ?? null;
11329
11706
  }
11330
11707
  var str3 = (v, d = "") => typeof v === "string" ? v : d;
@@ -11578,7 +11955,7 @@ var handleAdminNetworkTargets = async (req, url, env, ctx) => {
11578
11955
  if ("response" in got) return got.response;
11579
11956
  return json({
11580
11957
  targets: ctx.chapter.network.targets.map(({
11581
- id: id6,
11958
+ id: id7,
11582
11959
  name,
11583
11960
  url: targetUrl,
11584
11961
  fields,
@@ -11587,7 +11964,7 @@ var handleAdminNetworkTargets = async (req, url, env, ctx) => {
11587
11964
  stageTransitions,
11588
11965
  commercialParity
11589
11966
  }) => ({
11590
- id: id6,
11967
+ id: id7,
11591
11968
  name,
11592
11969
  url: targetUrl,
11593
11970
  types: Object.keys(fields ?? DEFAULT_SHARE_FIELDS),
@@ -11749,7 +12126,7 @@ var handleAdminNetworkPush = async (req, url, env, ctx) => {
11749
12126
  if (typeof body.recordId !== "string" || !Array.isArray(body.targetIds) || body.targetIds.length === 0) {
11750
12127
  return json({ error: "recordId and a non-empty targetIds array are required" }, 400);
11751
12128
  }
11752
- const requested = new Set(body.targetIds.filter((id6) => typeof id6 === "string"));
12129
+ const requested = new Set(body.targetIds.filter((id7) => typeof id7 === "string"));
11753
12130
  if (requested.size !== body.targetIds.length) return json({ error: "targetIds must contain unique strings" }, 400);
11754
12131
  const targets = ctx.chapter.network.targets.filter((target) => requested.has(target.id));
11755
12132
  if (targets.length !== requested.size) return json({ error: "one or more targetIds are not configured" }, 400);
@@ -11877,7 +12254,7 @@ async function operationalReaderRecord(record4, reader, db, env, ctx) {
11877
12254
  const appId = record4.fields.applicationId;
11878
12255
  const app = typeof appId === "string" ? (await db.query({ applications: { $: { where: { id: appId }, limit: 1 } } })).applications?.[0] : void 0;
11879
12256
  if (reader.stageTransitions.includes(record4.type)) {
11880
- result.edit.allowedStages = app ? applicationActionStages(app, env, ctx).map((id6) => ({ id: id6, label: id6.replaceAll("_", " ") })) : result.edit.allowedStages.filter((stage) => !isApplicationStage(ctx, stage.id));
12257
+ result.edit.allowedStages = app ? applicationActionStages(app, env, ctx).map((id7) => ({ id: id7, label: id7.replaceAll("_", " ") })) : result.edit.allowedStages.filter((stage) => !isApplicationStage(ctx, stage.id));
11881
12258
  const lock = (await db.query({ crm_activity: { $: { where: { id: `network-action-lock:${record4.id}` }, limit: 1 } } })).crm_activity?.[0];
11882
12259
  const meta = lock?.meta;
11883
12260
  if (meta?.state === "pending" && typeof meta.activityId === "string") {
@@ -11982,9 +12359,9 @@ async function listReaderRecords(db, type, allowed2, emailField, window) {
11982
12359
  // src/network-mutation-receipt.ts
11983
12360
  var import_crm22 = require("@odla-ai/crm");
11984
12361
  var NETWORK_MUTATION_META = "chapterNetworkMutation";
11985
- async function priorMutation(db, id6) {
12362
+ async function priorMutation(db, id7) {
11986
12363
  const result = await db.query({
11987
- [import_crm22.CRM_NS.activity]: { $: { where: { id: id6 }, limit: 1 } }
12364
+ [import_crm22.CRM_NS.activity]: { $: { where: { id: id7 }, limit: 1 } }
11988
12365
  });
11989
12366
  const row2 = (result[import_crm22.CRM_NS.activity] ?? [])[0];
11990
12367
  const meta = row2?.meta;
@@ -12244,8 +12621,8 @@ async function gate7(req, env, ctx) {
12244
12621
  if (!await networkWriteAllowed(req, db, user, ctx)) return { response: json({ error: "super-admin required", code: "super_admin_required" }, 403) };
12245
12622
  return { db, user };
12246
12623
  }
12247
- function targetFor2(ctx, id6) {
12248
- return ctx.chapter.network.targets.find((target) => target.id === id6);
12624
+ function targetFor2(ctx, id7) {
12625
+ return ctx.chapter.network.targets.find((target) => target.id === id7);
12249
12626
  }
12250
12627
  async function readTargetRecords(db, env, ctx, target, query) {
12251
12628
  const secret = await getVaultSecret(db, target.secretName);
@@ -12380,7 +12757,7 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
12380
12757
  if (ctx.chapter.network.requireSuperAdminWrites && !await ctx.isSuperAdminEmail(got.db, got.user.email, got.user.userId)) {
12381
12758
  return json({ ...result.page, records: result.page.records.map(({ edit: _edit, ...record4 }) => ({
12382
12759
  ...record4,
12383
- fields: Object.fromEntries(Object.entries(record4.fields).filter(([id6]) => id6 === "name" || id6 === "email"))
12760
+ fields: Object.fromEntries(Object.entries(record4.fields).filter(([id7]) => id7 === "name" || id7 === "email"))
12384
12761
  })) });
12385
12762
  }
12386
12763
  return json(result.page);
@@ -12450,9 +12827,9 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
12450
12827
  }
12451
12828
  const body = typeof input.body === "string" ? input.body.trim() : "";
12452
12829
  if (!body || body.length > MAX_NOTE3) return json({ error: `note must be 1\u2013${MAX_NOTE3} characters` }, 400);
12453
- const id6 = crypto.randomUUID();
12830
+ const id7 = crypto.randomUUID();
12454
12831
  const note = {
12455
- id: id6,
12832
+ id: id7,
12456
12833
  targetId: identity2.targetId,
12457
12834
  recordType: identity2.recordType,
12458
12835
  recordId: identity2.recordId,
@@ -12464,7 +12841,7 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
12464
12841
  await got.db.transact([{
12465
12842
  t: "update",
12466
12843
  ns: "networkNotes",
12467
- id: id6,
12844
+ id: id7,
12468
12845
  attrs: { ...note, recordKey: identity2.recordKey }
12469
12846
  }]);
12470
12847
  return json({ note }, 201);
@@ -12564,13 +12941,13 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
12564
12941
  return json({ error: `body must be 1\u2013${MAX_NOTE4} characters and mutationId must be valid` }, 400);
12565
12942
  }
12566
12943
  const createdAt = Date.now();
12567
- const id6 = `network:${verified.sender}:${mutationId}`;
12944
+ const id7 = `network:${verified.sender}:${mutationId}`;
12568
12945
  const result = await (0, import_crm24.addActivity)(
12569
12946
  {
12570
12947
  crm: ctx.chapter.crm,
12571
12948
  db,
12572
12949
  now: () => createdAt,
12573
- newId: () => id6
12950
+ newId: () => id7
12574
12951
  },
12575
12952
  {
12576
12953
  recordId,
@@ -12585,11 +12962,11 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
12585
12962
  body
12586
12963
  }
12587
12964
  },
12588
- mutationId: id6
12965
+ mutationId: id7
12589
12966
  }
12590
12967
  );
12591
12968
  const note = {
12592
- id: id6,
12969
+ id: id7,
12593
12970
  sourceId: verified.sender,
12594
12971
  recordType,
12595
12972
  recordId,
@@ -12598,8 +12975,8 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
12598
12975
  };
12599
12976
  return json({ version: 1, site: { id: ctx.chapter.id, name: ctx.chapter.name }, note, duplicate: result.duplicate }, 201);
12600
12977
  };
12601
- function targetFor3(ctx, id6) {
12602
- return ctx.chapter.network.targets.find((target) => target.id === id6);
12978
+ function targetFor3(ctx, id7) {
12979
+ return ctx.chapter.network.targets.find((target) => target.id === id7);
12603
12980
  }
12604
12981
  var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
12605
12982
  if (url.pathname !== "/api/admin/network/shared-notes" || req.method !== "GET" && req.method !== "POST") return null;
@@ -12868,10 +13245,10 @@ async function linkNetworkChapterPerson(db, input, mutationId, actor, now = Date
12868
13245
  if (!state.claims.some((claim) => claim.id === expectedClaim.id && claim.personId === person.id && claim.kind === "email")) {
12869
13246
  throw new NetworkPersonRegistryError("identity_mismatch", "source email is not an exact verified identity on this Person");
12870
13247
  }
12871
- const id6 = await networkChapterLinkId(person.id, input.chapterId, input.chapterPersonId);
12872
- const previous = state.links.find((link2) => link2.id === id6);
13248
+ const id7 = await networkChapterLinkId(person.id, input.chapterId, input.chapterPersonId);
13249
+ const previous = state.links.find((link2) => link2.id === id7);
12873
13250
  const link = {
12874
- id: id6,
13251
+ id: id7,
12875
13252
  personId: person.id,
12876
13253
  chapterId: input.chapterId,
12877
13254
  chapterPersonId: input.chapterPersonId,
@@ -12882,11 +13259,11 @@ async function linkNetworkChapterPerson(db, input, mutationId, actor, now = Date
12882
13259
  const nextPerson = { ...person, revision: person.revision + 1, updatedAt: now };
12883
13260
  await db.transact([
12884
13261
  { t: "update", ns: "networkPersons", id: person.id, attrs: { ...nextPerson } },
12885
- { t: "update", ns: "networkChapterPersonLinks", id: id6, attrs: { ...link } },
13262
+ { t: "update", ns: "networkChapterPersonLinks", id: id7, attrs: { ...link } },
12886
13263
  networkPersonEventOp("chapter_source_linked", mutationId, actor, now, { chapterId: input.chapterId }, { personId: person.id })
12887
13264
  ], { mutationId, guards: [
12888
13265
  { ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision, state: "active" } },
12889
- { ns: "networkChapterPersonLinks", id: id6, exists: Boolean(previous), ...previous ? { equals: { state: "revoked" } } : {} }
13266
+ { ns: "networkChapterPersonLinks", id: id7, exists: Boolean(previous), ...previous ? { equals: { state: "revoked" } } : {} }
12890
13267
  ] });
12891
13268
  return { person: nextPerson, link };
12892
13269
  }
@@ -12923,9 +13300,9 @@ var defaultPolicy = (now, consent) => ({
12923
13300
  var isGuardConflict = (error) => error instanceof Error && error.code === "transact_guard_failed";
12924
13301
  async function linkOps(personId, links, now) {
12925
13302
  return Promise.all(links.map(async (link) => {
12926
- const id6 = await networkChapterLinkId(personId, link.chapterId, link.chapterPersonId);
13303
+ const id7 = await networkChapterLinkId(personId, link.chapterId, link.chapterPersonId);
12927
13304
  const sourceKey = await networkChapterPersonSourceKey(link.chapterId, link.chapterPersonId);
12928
- return { t: "update", ns: "networkChapterPersonLinks", id: id6, attrs: { id: id6, personId, chapterId: link.chapterId, chapterPersonId: link.chapterPersonId, sourceKey, state: "shadow", createdAt: now } };
13305
+ return { t: "update", ns: "networkChapterPersonLinks", id: id7, attrs: { id: id7, personId, chapterId: link.chapterId, chapterPersonId: link.chapterPersonId, sourceKey, state: "shadow", createdAt: now } };
12929
13306
  }));
12930
13307
  }
12931
13308
  async function ambiguousAlias(db, input, aliasId, claims, candidates, mutationId, actor, now) {
@@ -13144,10 +13521,10 @@ async function networkPersonBulkBatchDigest(rows5) {
13144
13521
  }
13145
13522
  async function networkPersonRegistryStateDigest(state) {
13146
13523
  const snapshot = {
13147
- persons: state.persons.map(({ id: id6, revision, state: personState, mergedIntoId }) => ({ id: id6, revision, state: personState, mergedIntoId })).sort((a, b) => a.id.localeCompare(b.id)),
13148
- claims: state.claims.map(({ id: id6, personId }) => ({ id: id6, personId })).sort((a, b) => a.id.localeCompare(b.id)),
13149
- aliases: state.aliases.map(({ id: id6, revision, status: status2, personId }) => ({ id: id6, revision, status: status2, personId })).sort((a, b) => a.id.localeCompare(b.id)),
13150
- links: state.links.map(({ id: id6, personId, state: linkState }) => ({ id: id6, personId, state: linkState })).sort((a, b) => a.id.localeCompare(b.id))
13524
+ persons: state.persons.map(({ id: id7, revision, state: personState, mergedIntoId }) => ({ id: id7, revision, state: personState, mergedIntoId })).sort((a, b) => a.id.localeCompare(b.id)),
13525
+ claims: state.claims.map(({ id: id7, personId }) => ({ id: id7, personId })).sort((a, b) => a.id.localeCompare(b.id)),
13526
+ aliases: state.aliases.map(({ id: id7, revision, status: status2, personId }) => ({ id: id7, revision, status: status2, personId })).sort((a, b) => a.id.localeCompare(b.id)),
13527
+ links: state.links.map(({ id: id7, personId, state: linkState }) => ({ id: id7, personId, state: linkState })).sort((a, b) => a.id.localeCompare(b.id))
13151
13528
  };
13152
13529
  return networkPersonBulkSha256(JSON.stringify(stableValue(snapshot)));
13153
13530
  }
@@ -13372,7 +13749,7 @@ var safeNetworkPersonAlias = async (alias) => ({
13372
13749
  recordedAt: alias.consent.recordedAt,
13373
13750
  hasBasis: Boolean(alias.consent.basis)
13374
13751
  },
13375
- identityClaims: alias.identityClaims.map(({ id: id6, kind, hint, verifiedAt }) => ({ id: id6, kind, hint: hint ?? null, verifiedAt })),
13752
+ identityClaims: alias.identityClaims.map(({ id: id7, kind, hint, verifiedAt }) => ({ id: id7, kind, hint: hint ?? null, verifiedAt })),
13376
13753
  chapterIds: [...new Set(alias.chapterLinks.map((link) => link.chapterId))],
13377
13754
  chapterSources: await Promise.all(alias.chapterLinks.map(async (link) => ({
13378
13755
  chapterId: link.chapterId,
@@ -13401,7 +13778,7 @@ async function projectNetworkPersonRegistryState(state) {
13401
13778
  return {
13402
13779
  ...safeNetworkPerson(person, claims),
13403
13780
  canonicalPersonId: canonicalId,
13404
- identityClaims: claims.map(({ id: id6, kind, hint, verifiedAt }) => ({ id: id6, kind, hint: hint ?? null, verifiedAt })),
13781
+ identityClaims: claims.map(({ id: id7, kind, hint, verifiedAt }) => ({ id: id7, kind, hint: hint ?? null, verifiedAt })),
13405
13782
  chapterLinks: safeLinks,
13406
13783
  chapterProjections: projectNetworkContactPolicy(canonical3.contactPolicy, safeLinks).map(({ contactPolicy, ...projection }) => ({ ...projection, contactPolicy: safeNetworkContactPolicy(contactPolicy) }))
13407
13784
  };
@@ -13656,7 +14033,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
13656
14033
  };
13657
14034
 
13658
14035
  // src/worker-routes-network-commercial.ts
13659
- var import_stripe12 = require("@odla-ai/stripe");
14036
+ var import_stripe13 = require("@odla-ai/stripe");
13660
14037
 
13661
14038
  // src/worker-routes-network-commercial-parity.ts
13662
14039
  var import_db13 = require("@odla-ai/db");
@@ -14032,7 +14409,7 @@ var handleNetworkCommercialStripeWebhook = async (req, url, env, ctx) => {
14032
14409
  return json({ error: "commercial webhook is not configured" }, 503);
14033
14410
  }
14034
14411
  try {
14035
- const event2 = await (0, import_stripe12.constructStripeEvent)(body, signature, secret);
14412
+ const event2 = await (0, import_stripe13.constructStripeEvent)(body, signature, secret);
14036
14413
  const fact = normalizeNetworkCommercialStripeEvent(event2, await stripeEventDigest(body));
14037
14414
  if (!fact.eventId || !fact.eventType || !fact.objectId) return json({ error: "Stripe event identity is invalid" }, 400);
14038
14415
  const result = await ingestNetworkCommercialFact(
@@ -14053,7 +14430,7 @@ var handleNetworkCommercialStripeWebhook = async (req, url, env, ctx) => {
14053
14430
  ...result.status === "replayed" && result.resolvedCollision ? { resolvedCollision: true } : {}
14054
14431
  });
14055
14432
  } catch (error) {
14056
- if (error instanceof import_stripe12.StripeError) return json({ error: error.message, code: error.code }, 400);
14433
+ if (error instanceof import_stripe13.StripeError) return json({ error: error.message, code: error.code }, 400);
14057
14434
  throw error;
14058
14435
  }
14059
14436
  };
@@ -14116,7 +14493,7 @@ var handleAdminNetworkCommercialReadModel = async (req, url, env, ctx) => {
14116
14493
  const secretKey = await getVaultSecret(db, "stripe_secret_key");
14117
14494
  if (!secretKey) return json({ error: "Stripe reconciliation is not configured" }, 503);
14118
14495
  try {
14119
- const stripe = (0, import_stripe12.initStripe)({ secretKey });
14496
+ const stripe = (0, import_stripe13.initStripe)({ secretKey });
14120
14497
  const [customers, subscriptions, invoices, refunds, payments] = await Promise.all([
14121
14498
  listStripeObjects(stripe, "/v1/customers"),
14122
14499
  listStripeObjects(stripe, "/v1/subscriptions", { status: "all" }),
@@ -14130,7 +14507,7 @@ var handleAdminNetworkCommercialReadModel = async (req, url, env, ctx) => {
14130
14507
  ...reconcileStripeSnapshot(projection2, { customers, subscriptions, invoices, refunds, payments }, ctx.chapter.networkCommercialReadModel.sources)
14131
14508
  });
14132
14509
  } catch (error) {
14133
- if (error instanceof import_stripe12.StripeError) return json({ error: "Stripe reconciliation failed", code: error.code }, 502);
14510
+ if (error instanceof import_stripe13.StripeError) return json({ error: "Stripe reconciliation failed", code: error.code }, 502);
14134
14511
  return json({ error: "Stripe reconciliation failed" }, 502);
14135
14512
  }
14136
14513
  }
@@ -14194,14 +14571,14 @@ var handleFormation = async (req, url, env, ctx) => {
14194
14571
  }
14195
14572
  }
14196
14573
  const db = ctx.makeDb(env);
14197
- const id6 = submissionId ? await applicationIdForSubmission(`formation:${ctx.chapter.id}:${submissionId}`) : crypto.randomUUID();
14574
+ const id7 = submissionId ? await applicationIdForSubmission(`formation:${ctx.chapter.id}:${submissionId}`) : crypto.randomUUID();
14198
14575
  try {
14199
14576
  const created = await (0, import_crm25.createRecord)(
14200
14577
  {
14201
14578
  crm: ctx.chapter.crm,
14202
14579
  db,
14203
14580
  now: () => Date.now(),
14204
- newId: () => id6
14581
+ newId: () => id7
14205
14582
  },
14206
14583
  {
14207
14584
  type: formation.type,
@@ -14210,7 +14587,7 @@ var handleFormation = async (req, url, env, ctx) => {
14210
14587
  }
14211
14588
  );
14212
14589
  return json({
14213
- id: id6,
14590
+ id: id7,
14214
14591
  duplicate: created.duplicate,
14215
14592
  status: created.record.stage ?? null
14216
14593
  }, created.duplicate ? 200 : 201);
@@ -14287,8 +14664,8 @@ function resourceValue(value, path, errors) {
14287
14664
  return null;
14288
14665
  }
14289
14666
  unknownKeys3(raw2, RESOURCE_KEYS, path, errors);
14290
- const id6 = text7(raw2.id, `${path}.id`, errors, 80) ?? "";
14291
- if (id6 && !ID5.test(id6)) errors.push(`${path}.id must be lowercase kebab-case`);
14667
+ const id7 = text7(raw2.id, `${path}.id`, errors, 80) ?? "";
14668
+ if (id7 && !ID5.test(id7)) errors.push(`${path}.id must be lowercase kebab-case`);
14292
14669
  const kind = text7(raw2.kind, `${path}.kind`, errors) ?? "";
14293
14670
  if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
14294
14671
  const title = text7(raw2.title, `${path}.title`, errors, 240) ?? "";
@@ -14345,7 +14722,7 @@ function resourceValue(value, path, errors) {
14345
14722
  updatedAt: integer4(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
14346
14723
  ...promotedFrom ? { promotedFrom } : {}
14347
14724
  };
14348
- return { id: id6, kind, title, ...summary ? { summary } : {}, tags, scope: scope2, status: status2, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance: provenance2 };
14725
+ return { id: id7, kind, title, ...summary ? { summary } : {}, tags, scope: scope2, status: status2, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance: provenance2 };
14349
14726
  }
14350
14727
  function validateBuilderLibraryDocument(value, options = {}) {
14351
14728
  const errors = [];
@@ -14376,7 +14753,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
14376
14753
  const inputs = Array.isArray(raw2.resources) ? raw2.resources : [];
14377
14754
  if (inputs.length > MAX_RESOURCES) errors.push(`document.resources may contain at most ${MAX_RESOURCES} items`);
14378
14755
  const resources = inputs.map((item, index2) => resourceValue(item, `document.resources[${index2}]`, errors)).filter(Boolean);
14379
- const ids2 = resources.map(({ id: id6 }) => id6);
14756
+ const ids2 = resources.map(({ id: id7 }) => id7);
14380
14757
  if (new Set(ids2).size !== ids2.length) errors.push("document.resources ids must be unique");
14381
14758
  for (const resource of resources) {
14382
14759
  if (sourceType === "chapter" && (resource.scope.kind !== "chapter" || resource.scope.chapterId !== sourceAppId)) {
@@ -14465,18 +14842,18 @@ async function loadLatestBuilderLibraryControl(db, sourceId, targetId, environme
14465
14842
  return control2(Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
14466
14843
  }
14467
14844
  async function loadLocalBuilderLibrary(db, appId, environment) {
14468
- const id6 = `builder-library-local:${environment}`;
14469
- const rows5 = (await db.query({ builderLibraryLocal: { $: { where: { id: id6 }, limit: 1 } } })).builderLibraryLocal;
14845
+ const id7 = `builder-library-local:${environment}`;
14846
+ const rows5 = (await db.query({ builderLibraryLocal: { $: { where: { id: id7 }, limit: 1 } } })).builderLibraryLocal;
14470
14847
  return local3(Array.isArray(rows5) ? rows5[0] : void 0);
14471
14848
  }
14472
14849
  async function materializeBuilderLibraryControl(db, envelope, previous, receivedAt) {
14473
14850
  const { document, digest } = envelope;
14474
14851
  const target = document.target;
14475
- const id6 = `builder-library-control:${document.source.appId}:${target.environment}:${document.revision}`;
14852
+ const id7 = `builder-library-control:${document.source.appId}:${target.environment}:${document.revision}`;
14476
14853
  const ops = [];
14477
14854
  if (previous?.active) ops.push({ t: "update", ns: "builderLibraryControls", id: previous.id, attrs: { active: false } });
14478
- ops.push({ t: "update", ns: "builderLibraryControls", id: id6, attrs: {
14479
- id: id6,
14855
+ ops.push({ t: "update", ns: "builderLibraryControls", id: id7, attrs: {
14856
+ id: id7,
14480
14857
  sourceId: document.source.appId,
14481
14858
  targetId: target.appId,
14482
14859
  environment: target.environment,
@@ -14489,13 +14866,13 @@ async function materializeBuilderLibraryControl(db, envelope, previous, received
14489
14866
  await db.transact(ops, { mutationId: `builder-library-control:${document.source.appId}:${target.environment}:${document.revision}:${digest}` });
14490
14867
  }
14491
14868
  async function saveLocalBuilderLibrary(db, document, environment, expectedRevision, updatedAt, updatedBy) {
14492
- const id6 = `builder-library-local:${environment}`;
14869
+ const id7 = `builder-library-local:${environment}`;
14493
14870
  const current = await loadLocalBuilderLibrary(db, document.source.appId, environment);
14494
14871
  if ((current?.revision ?? 0) !== expectedRevision) throw new Error("builder_library_revision_conflict");
14495
- const row2 = { id: id6, appId: document.source.appId, environment, revision: document.revision, document, updatedAt, ...updatedBy ? { updatedBy } : {} };
14496
- await db.transact([{ t: "update", ns: "builderLibraryLocal", id: id6, attrs: row2 }], {
14872
+ const row2 = { id: id7, appId: document.source.appId, environment, revision: document.revision, document, updatedAt, ...updatedBy ? { updatedBy } : {} };
14873
+ await db.transact([{ t: "update", ns: "builderLibraryLocal", id: id7, attrs: row2 }], {
14497
14874
  mutationId: `builder-library-local:${environment}:${document.revision}`,
14498
- guards: current ? [{ ns: "builderLibraryLocal", id: id6, exists: true, equals: { revision: expectedRevision } }] : [{ ns: "builderLibraryLocal", id: id6, exists: false }]
14875
+ guards: current ? [{ ns: "builderLibraryLocal", id: id7, exists: true, equals: { revision: expectedRevision } }] : [{ ns: "builderLibraryLocal", id: id7, exists: false }]
14499
14876
  });
14500
14877
  return row2;
14501
14878
  }
@@ -14710,30 +15087,30 @@ var handleAdminBuilderLibrary = async (req, url, env, ctx) => {
14710
15087
  };
14711
15088
 
14712
15089
  // src/membership-cancel-authority.ts
14713
- var import_stripe13 = require("@odla-ai/stripe");
15090
+ var import_stripe14 = require("@odla-ai/stripe");
14714
15091
  async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
14715
15092
  membershipClock(now);
14716
- const id6 = await networkMembershipId(target);
15093
+ const id7 = await networkMembershipId(target);
14717
15094
  const state = await loadMembershipAuthorityState(db, target);
14718
- const member = await membershipLoad(db, "networkMembership", id6);
15095
+ const member = await membershipLoad(db, "networkMembership", id7);
14719
15096
  requireMembershipScope(member, target);
14720
15097
  if (!["active", "cancel_at_term_end", "payment_grace", "paid_pending_approval", "lapsed"].includes(String(member.status))) {
14721
15098
  membershipFail("membership_cancellation_unavailable");
14722
15099
  }
14723
15100
  const path = `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`;
14724
- const read2 = await (0, import_stripe13.stripeRequest)({ secretKey: stripeSecret }, "GET", path);
15101
+ const read2 = await (0, import_stripe14.stripeRequest)({ secretKey: stripeSecret }, "GET", path);
14725
15102
  const sub = read2.body;
14726
15103
  if (!read2.ok || sub.id !== member.providerSubscriptionId || sub.customer !== member.providerCustomerId || sub.livemode !== (target.environment === "prod") || !Object.entries({
14727
15104
  ...stripePartitionMetadata(target),
14728
15105
  applicationId: target.applicationId
14729
15106
  }).every(([key3, value]) => sub.metadata?.[key3] === value)) membershipFail("membership_cancellation_provider_mismatch");
14730
15107
  if (sub.status !== "canceled" && sub.cancel_at_period_end !== true) {
14731
- const write = await (0, import_stripe13.stripeRequest)(
15108
+ const write = await (0, import_stripe14.stripeRequest)(
14732
15109
  { secretKey: stripeSecret },
14733
15110
  "POST",
14734
15111
  path,
14735
15112
  { cancel_at_period_end: true, proration_behavior: "none" },
14736
- { idempotencyKey: `membership-cancel-at-term:${id6}:${member.providerSubscriptionId}` }
15113
+ { idempotencyKey: `membership-cancel-at-term:${id7}:${member.providerSubscriptionId}` }
14737
15114
  );
14738
15115
  if (!write.ok || write.body.id !== member.providerSubscriptionId || write.body.cancel_at_period_end !== true) {
14739
15116
  membershipFail("membership_cancellation_pending");
@@ -14741,7 +15118,7 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
14741
15118
  }
14742
15119
  const status2 = Number(member.termEndsAt) <= now ? "lapsed" : member.journeyApprovedAt ? "cancel_at_term_end" : "paid_pending_approval";
14743
15120
  if (member.status !== status2 || member.autoRenew !== false || Number(member.graceEndsAt ?? 0) !== 0) {
14744
- await db.transact([membershipUpdate("networkMembership", id6, {
15121
+ await db.transact([membershipUpdate("networkMembership", id7, {
14745
15122
  status: status2,
14746
15123
  autoRenew: false,
14747
15124
  graceEndsAt: 0,
@@ -14750,12 +15127,12 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
14750
15127
  updatedAt: now
14751
15128
  })], {
14752
15129
  guards: [
14753
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
15130
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
14754
15131
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
14755
15132
  ]
14756
15133
  });
14757
15134
  }
14758
- return { membershipId: id6, status: status2, termEndsAt: Number(member.termEndsAt), graceEndsAt: 0, autoRenew: false };
15135
+ return { membershipId: id7, status: status2, termEndsAt: Number(member.termEndsAt), graceEndsAt: 0, autoRenew: false };
14759
15136
  }
14760
15137
 
14761
15138
  // src/membership-authority-handler.ts
@@ -14794,17 +15171,17 @@ async function namedSeatPrimaryActivation(db, member, now) {
14794
15171
  // src/membership-approval-authority.ts
14795
15172
  async function approveNetworkMembership(db, input, now) {
14796
15173
  membershipClock(now);
14797
- const id6 = await networkMembershipId(input), entitlementId = `${id6}:primary`;
15174
+ const id7 = await networkMembershipId(input), entitlementId = `${id7}:primary`;
14798
15175
  for (let attempt = 0; attempt < 8; attempt++) {
14799
15176
  const state = await loadMembershipAuthorityState(db, input);
14800
- const member = await membershipLoad(db, "networkMembership", id6);
15177
+ const member = await membershipLoad(db, "networkMembership", id7);
14801
15178
  requireMembershipScope(member, input);
14802
15179
  const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
14803
15180
  requireMembershipScope(entitlement, input);
14804
- if (entitlement.membershipId !== id6 || entitlement.kind !== "primary") membershipFail("membership_entitlement_conflict");
15181
+ if (entitlement.membershipId !== id7 || entitlement.kind !== "primary") membershipFail("membership_entitlement_conflict");
14805
15182
  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) {
14806
15183
  return {
14807
- membershipId: id6,
15184
+ membershipId: id7,
14808
15185
  status: String(member.status),
14809
15186
  cohort: String(member.cohort),
14810
15187
  memberNumber: Number(member.memberNumber),
@@ -14824,7 +15201,7 @@ async function approveNetworkMembership(db, input, now) {
14824
15201
  const seatActivation = await namedSeatPrimaryActivation(db, member, now);
14825
15202
  try {
14826
15203
  await db.transact([
14827
- membershipUpdate("networkMembership", id6, {
15204
+ membershipUpdate("networkMembership", id7, {
14828
15205
  status: member.autoRenew ? "active" : "cancel_at_term_end",
14829
15206
  memberNumber,
14830
15207
  journeyApprovedAt: now,
@@ -14839,8 +15216,8 @@ async function approveNetworkMembership(db, input, now) {
14839
15216
  updatedAt: now
14840
15217
  }),
14841
15218
  ...seatActivation.ops
14842
- ], { mutationId: `membership-approve:${id6}`, guards: [
14843
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision, status: "paid_pending_approval" }, absent: ["memberNumber"] },
15219
+ ], { mutationId: `membership-approve:${id7}`, guards: [
15220
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision, status: "paid_pending_approval" }, absent: ["memberNumber"] },
14844
15221
  { ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision, status: "pending_approval" } },
14845
15222
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
14846
15223
  ...seatActivation.guards
@@ -14855,41 +15232,41 @@ async function approveNetworkMembership(db, input, now) {
14855
15232
  // src/membership-refund-authority.ts
14856
15233
  async function refundNetworkMembership(db, input, secretKey, now) {
14857
15234
  membershipClock(now);
14858
- const id6 = await networkMembershipId(input), entitlementId = `${id6}:primary`;
14859
- let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id6);
15235
+ const id7 = await networkMembershipId(input), entitlementId = `${id7}:primary`;
15236
+ let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id7);
14860
15237
  requireMembershipScope(member, input);
14861
- if (member.status === "refunded") return { membershipId: id6, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
15238
+ if (member.status === "refunded") return { membershipId: id7, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
14862
15239
  if (!["paid_pending_approval", "active", "cancel_at_term_end", "payment_grace", "lapsed", "refund_pending"].includes(String(member.status))) membershipFail("membership_refund_not_eligible");
14863
15240
  if (member.status !== "refund_pending") {
14864
15241
  const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
14865
15242
  requireMembershipScope(entitlement, input);
14866
15243
  await db.transact([
14867
- membershipUpdate("networkMembership", id6, {
15244
+ membershipUpdate("networkMembership", id7, {
14868
15245
  status: "refund_pending",
14869
15246
  refundRequestedAt: now,
14870
15247
  revision: Number(member.revision) + 1,
14871
15248
  updatedAt: now
14872
15249
  }),
14873
15250
  membershipUpdate("networkEntitlement", entitlementId, { status: "closed", revision: Number(entitlement.revision) + 1, updatedAt: now }),
14874
- membershipUpdate("membershipEffect", `${id6}:refund`, {
14875
- id: `${id6}:refund`,
15251
+ membershipUpdate("membershipEffect", `${id7}:refund`, {
15252
+ id: `${id7}:refund`,
14876
15253
  chapterId: input.chapterId,
14877
15254
  environment: input.environment,
14878
15255
  runtime: input.runtime,
14879
- membershipId: id6,
15256
+ membershipId: id7,
14880
15257
  kind: "refund_membership",
14881
15258
  status: "pending",
14882
15259
  attempts: 0,
14883
15260
  createdAt: now,
14884
15261
  updatedAt: now
14885
15262
  })
14886
- ], { mutationId: `membership-refund-request:${id6}`, guards: [
14887
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
15263
+ ], { mutationId: `membership-refund-request:${id7}`, guards: [
15264
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
14888
15265
  { ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision } },
14889
15266
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
14890
15267
  ] });
14891
15268
  }
14892
- member = await membershipLoad(db, "networkMembership", id6);
15269
+ member = await membershipLoad(db, "networkMembership", id7);
14893
15270
  requireMembershipScope(member, input);
14894
15271
  if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
14895
15272
  const result = await refundSubscriptionPayment(secretKey, {
@@ -14903,23 +15280,23 @@ async function refundNetworkMembership(db, input, secretKey, now) {
14903
15280
  if (!result.subscriptionCanceled || result.refundedCents !== member.amountCents) membershipFail("membership_refund_unconfirmed");
14904
15281
  for (let attempt = 0; attempt < 8; attempt++) {
14905
15282
  state = await loadMembershipAuthorityState(db, input);
14906
- member = await membershipLoad(db, "networkMembership", id6);
15283
+ member = await membershipLoad(db, "networkMembership", id7);
14907
15284
  requireMembershipScope(member, input);
14908
- if (member.status === "refunded") return { membershipId: id6, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
15285
+ if (member.status === "refunded") return { membershipId: id7, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
14909
15286
  if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
14910
15287
  const counted = member.founding === true && member.memberNumber !== void 0;
14911
15288
  const counts = state.cohortCounts;
14912
15289
  if (!counts || counted && (!Number.isSafeInteger(counts[String(member.cohort)]) || counts[String(member.cohort)] < 1)) membershipFail("invalid_cohort_count");
14913
15290
  try {
14914
15291
  await db.transact([
14915
- membershipUpdate("networkMembership", id6, {
15292
+ membershipUpdate("networkMembership", id7, {
14916
15293
  status: "refunded",
14917
15294
  refundedCents: result.refundedCents,
14918
15295
  autoRenew: false,
14919
15296
  revision: Number(member.revision) + 1,
14920
15297
  updatedAt: now
14921
15298
  }),
14922
- membershipUpdate("membershipEffect", `${id6}:refund`, { status: "complete", updatedAt: now }),
15299
+ membershipUpdate("membershipEffect", `${id7}:refund`, { status: "complete", updatedAt: now }),
14923
15300
  ...counted ? [membershipUpdate("membershipAuthorityState", String(state.id), {
14924
15301
  cohortCounts: { ...counts, [String(member.cohort)]: counts[String(member.cohort)] - 1 },
14925
15302
  revision: Number(state.revision) + 1,
@@ -14930,7 +15307,7 @@ async function refundNetworkMembership(db, input, secretKey, now) {
14930
15307
  chapterId: input.chapterId,
14931
15308
  environment: input.environment,
14932
15309
  runtime: input.runtime,
14933
- membershipId: id6,
15310
+ membershipId: id7,
14934
15311
  seatId: member.namedSeatId,
14935
15312
  kind: "refund_seat_with_primary",
14936
15313
  status: "pending",
@@ -14938,8 +15315,8 @@ async function refundNetworkMembership(db, input, secretKey, now) {
14938
15315
  createdAt: now,
14939
15316
  updatedAt: now
14940
15317
  })] : []
14941
- ], { mutationId: `membership-refunded:${id6}`, guards: [
14942
- { ns: "networkMembership", id: id6, exists: true, equals: { status: "refund_pending", revision: member.revision } },
15318
+ ], { mutationId: `membership-refunded:${id7}`, guards: [
15319
+ { ns: "networkMembership", id: id7, exists: true, equals: { status: "refund_pending", revision: member.revision } },
14943
15320
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
14944
15321
  ] });
14945
15322
  } catch (error) {
@@ -14950,12 +15327,12 @@ async function refundNetworkMembership(db, input, secretKey, now) {
14950
15327
  }
14951
15328
 
14952
15329
  // src/membership-provider-lifecycle.ts
14953
- var import_stripe14 = require("@odla-ai/stripe");
15330
+ var import_stripe15 = require("@odla-ai/stripe");
14954
15331
 
14955
15332
  // src/membership-renewal-authority.ts
14956
15333
  async function reconcileMembershipRenewal(db, input, now) {
14957
15334
  membershipClock(now);
14958
- const id6 = await networkMembershipId(input), eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
15335
+ const id7 = await networkMembershipId(input), eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
14959
15336
  const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow?.digest, "membership_renewal_evidence_pending");
14960
15337
  const fact = eventRow?.fact;
14961
15338
  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({
@@ -14968,10 +15345,10 @@ async function reconcileMembershipRenewal(db, input, now) {
14968
15345
  return membershipFail("membership_renewal_evidence_pending");
14969
15346
  }
14970
15347
  for (let attempt = 0; attempt < 8; attempt++) {
14971
- const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id6);
15348
+ const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id7);
14972
15349
  requireMembershipScope(member, input);
14973
15350
  const receipt = () => ({
14974
- membershipId: id6,
15351
+ membershipId: id7,
14975
15352
  status: String(member.status),
14976
15353
  termEndsAt: Number(member.termEndsAt),
14977
15354
  graceEndsAt: Number(member.graceEndsAt ?? 0),
@@ -14996,13 +15373,13 @@ async function reconcileMembershipRenewal(db, input, now) {
14996
15373
  const paid = fact.eventType === "invoice.payment_succeeded";
14997
15374
  if (!paid && (fact.status !== "open" || !Number.isSafeInteger(fact.amountPaidCents) || Number(fact.amountPaidCents) < 0 || Number(fact.amountPaidCents) >= amount)) membershipFail("membership_renewal_failure_mismatch");
14998
15375
  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");
14999
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id6}:primary`);
15376
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id7}:primary`);
15000
15377
  requireMembershipScope(entitlement, input);
15001
15378
  const graceEndsAt = paid ? 0 : start + 14 * 864e5;
15002
15379
  const status2 = paid ? member.journeyApprovedAt ? member.autoRenew ? "active" : "cancel_at_term_end" : "paid_pending_approval" : now < graceEndsAt ? "payment_grace" : "lapsed";
15003
15380
  const entitlementStatus = member.journeyApprovedAt && (paid || now < graceEndsAt) ? "active" : member.journeyApprovedAt ? "closed" : "pending_approval";
15004
15381
  const ops = [
15005
- membershipUpdate("networkMembership", id6, {
15382
+ membershipUpdate("networkMembership", id7, {
15006
15383
  status: status2,
15007
15384
  graceEndsAt,
15008
15385
  ...paid ? {
@@ -15013,7 +15390,7 @@ async function reconcileMembershipRenewal(db, input, now) {
15013
15390
  revision: Number(member.revision) + 1,
15014
15391
  updatedAt: now
15015
15392
  }),
15016
- membershipUpdate("networkEntitlement", `${id6}:primary`, {
15393
+ membershipUpdate("networkEntitlement", `${id7}:primary`, {
15017
15394
  status: entitlementStatus,
15018
15395
  ...paid ? { termStartedAt: start, termEndsAt: end } : {},
15019
15396
  sourceEventId: input.eventId,
@@ -15022,8 +15399,8 @@ async function reconcileMembershipRenewal(db, input, now) {
15022
15399
  })
15023
15400
  ];
15024
15401
  const guards = [
15025
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
15026
- { ns: "networkEntitlement", id: `${id6}:primary`, exists: true, equals: { revision: entitlement.revision } },
15402
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
15403
+ { ns: "networkEntitlement", id: `${id7}:primary`, exists: true, equals: { revision: entitlement.revision } },
15027
15404
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
15028
15405
  { ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
15029
15406
  evidenceGuard
@@ -15048,15 +15425,15 @@ async function reconcileMembershipRenewal(db, input, now) {
15048
15425
  );
15049
15426
  }
15050
15427
  try {
15051
- await db.transact(ops, { mutationId: `membership-renewal:${id6}:${input.eventId}`, guards });
15428
+ await db.transact(ops, { mutationId: `membership-renewal:${id7}:${input.eventId}`, guards });
15052
15429
  } catch (error) {
15053
15430
  if (error.code !== "transact_guard_failed") throw error;
15054
15431
  continue;
15055
15432
  }
15056
- const saved = await membershipLoad(db, "networkMembership", id6);
15433
+ const saved = await membershipLoad(db, "networkMembership", id7);
15057
15434
  requireMembershipScope(saved, input);
15058
15435
  return {
15059
- membershipId: id6,
15436
+ membershipId: id7,
15060
15437
  status: String(saved.status),
15061
15438
  termEndsAt: Number(saved.termEndsAt),
15062
15439
  graceEndsAt: Number(saved.graceEndsAt ?? 0),
@@ -15082,12 +15459,12 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15082
15459
  }).some(([key3, value]) => fact.identity[key3] !== value)) membershipFail("membership_billing_evidence_pending");
15083
15460
  const sk = await getVaultSecret(db, stripeSecretName);
15084
15461
  if (!sk) membershipFail("membership_billing_provider_unavailable");
15085
- const id6 = await networkMembershipId(input);
15462
+ const id7 = await networkMembershipId(input);
15086
15463
  for (let attempt = 0; attempt < 8; attempt++) {
15087
- const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id6);
15464
+ const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id7);
15088
15465
  requireMembershipScope(member, input);
15089
15466
  const receipt = (value = member) => ({
15090
- membershipId: id6,
15467
+ membershipId: id7,
15091
15468
  status: String(value.status),
15092
15469
  termEndsAt: Number(value.termEndsAt),
15093
15470
  graceEndsAt: Number(value.graceEndsAt ?? 0),
@@ -15108,13 +15485,13 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15108
15485
  });
15109
15486
  await commercialEvidenceGuard(db, input.eventId, row2?.digest, "membership_billing_evidence_pending");
15110
15487
  await refundNetworkMembership(db, input, sk, now);
15111
- const saved2 = await membershipLoad(db, "networkMembership", id6);
15488
+ const saved2 = await membershipLoad(db, "networkMembership", id7);
15112
15489
  requireMembershipScope(saved2, input);
15113
15490
  return receipt(saved2);
15114
15491
  }
15115
15492
  if (fact.subscriptionId !== member.providerSubscriptionId || fact.objectKind !== "subscription") membershipFail("membership_billing_scope_mismatch");
15116
15493
  if (["refund_pending", "refunded", "declined", "canceled", "restarting"].includes(String(member.status))) return receipt();
15117
- const result = await (0, import_stripe14.stripeRequest)({ secretKey: sk }, "GET", `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`);
15494
+ const result = await (0, import_stripe15.stripeRequest)({ secretKey: sk }, "GET", `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`);
15118
15495
  const sub = result.body;
15119
15496
  if (!result.ok || sub.id !== member.providerSubscriptionId || sub.customer !== member.providerCustomerId || sub.livemode !== (input.environment === "prod") || typeof sub.cancel_at_period_end !== "boolean" || !["active", "trialing", "past_due", "unpaid", "canceled"].includes(sub.status) || !Object.entries({ ...stripePartitionMetadata(input), applicationId: input.applicationId }).every(([key3, value]) => sub.metadata?.[key3] === value)) {
15120
15497
  membershipFail("membership_billing_provider_mismatch");
@@ -15123,18 +15500,18 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15123
15500
  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";
15124
15501
  const graceEndsAt = autoRenew ? Number(member.graceEndsAt ?? 0) : 0;
15125
15502
  if (member.autoRenew === autoRenew && member.status === status2 && Number(member.graceEndsAt ?? 0) === graceEndsAt) return receipt();
15126
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id6}:primary`);
15503
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id7}:primary`);
15127
15504
  requireMembershipScope(entitlement, input);
15128
15505
  try {
15129
15506
  await db.transact([
15130
- membershipUpdate("networkMembership", id6, { status: status2, autoRenew, graceEndsAt, revision: Number(member.revision) + 1, updatedAt: now }),
15507
+ membershipUpdate("networkMembership", id7, { status: status2, autoRenew, graceEndsAt, revision: Number(member.revision) + 1, updatedAt: now }),
15131
15508
  membershipUpdate("networkEntitlement", String(entitlement.id), {
15132
15509
  status: status2 === "lapsed" ? "closed" : entitlement.status,
15133
15510
  revision: Number(entitlement.revision) + 1,
15134
15511
  updatedAt: now
15135
15512
  })
15136
15513
  ], { guards: [
15137
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
15514
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
15138
15515
  { ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision } },
15139
15516
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
15140
15517
  { ns: "networkCommercialEvents", id: String(row2.id), exists: true, equals: { digest: fact.digest } },
@@ -15144,7 +15521,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15144
15521
  if (error.code !== "transact_guard_failed") throw error;
15145
15522
  continue;
15146
15523
  }
15147
- const saved = await membershipLoad(db, "networkMembership", id6);
15524
+ const saved = await membershipLoad(db, "networkMembership", id7);
15148
15525
  requireMembershipScope(saved, input);
15149
15526
  return receipt(saved);
15150
15527
  }
@@ -15152,7 +15529,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15152
15529
  }
15153
15530
 
15154
15531
  // src/membership-seat-stripe.ts
15155
- var import_stripe15 = require("@odla-ai/stripe");
15532
+ var import_stripe16 = require("@odla-ai/stripe");
15156
15533
  var reject7 = () => {
15157
15534
  throw Object.assign(new Error("named seat provider contract mismatch"), { code: "named_seat_provider_mismatch" });
15158
15535
  };
@@ -15164,7 +15541,7 @@ var metadataFor = (seat) => ({
15164
15541
  });
15165
15542
  var matches = (value, expected) => value && Object.entries(expected).every(([key3, item]) => value[key3] === item);
15166
15543
  async function read(sk, path, params) {
15167
- const response = await (0, import_stripe15.stripeRequest)({ secretKey: sk }, "GET", path, params);
15544
+ const response = await (0, import_stripe16.stripeRequest)({ secretKey: sk }, "GET", path, params);
15168
15545
  if (!response.ok) return reject7();
15169
15546
  return response.body;
15170
15547
  }
@@ -15175,7 +15552,7 @@ async function subscriptionForSeat(sk, seat) {
15175
15552
  currency: seat.currency,
15176
15553
  interval: seat.interval,
15177
15554
  environment: seat.environment
15178
- }, Boolean(seat.providerPaymentIntentId));
15555
+ }, Boolean(seat.providerPaymentIntentId || seat.combinedInvoice));
15179
15556
  if (!productId) reject7();
15180
15557
  const sub = await read(sk, `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}?expand[]=discounts`);
15181
15558
  const items = sub.items?.data;
@@ -15201,11 +15578,11 @@ async function createNamedSeatStripePayment(sk, seat, now) {
15201
15578
  }
15202
15579
  await subscriptionForSeat(sk, seat);
15203
15580
  const metadata = metadataFor(seat);
15204
- const result = seat.providerPaymentIntentId ? await (0, import_stripe15.stripeRequest)(
15581
+ const result = seat.providerPaymentIntentId ? await (0, import_stripe16.stripeRequest)(
15205
15582
  { secretKey: sk },
15206
15583
  "GET",
15207
15584
  `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`
15208
- ) : await (0, import_stripe15.stripeRequest)({ secretKey: sk }, "POST", "/v1/payment_intents", {
15585
+ ) : await (0, import_stripe16.stripeRequest)({ secretKey: sk }, "POST", "/v1/payment_intents", {
15209
15586
  amount: seat.amountCents,
15210
15587
  currency: seat.currency,
15211
15588
  customer: seat.providerCustomerId,
@@ -15219,11 +15596,15 @@ async function createNamedSeatStripePayment(sk, seat, now) {
15219
15596
  return { paymentIntentId: pi.id, clientSecret: pi.client_secret, status: pi.status };
15220
15597
  }
15221
15598
  async function attachNamedSeatStripeRenewal(sk, seat) {
15222
- if (!seat.providerPaymentIntentId || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject7();
15599
+ if (!seat.providerPaymentIntentId && !seat.combinedInvoice || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject7();
15223
15600
  const sub = await subscriptionForSeat(sk, seat);
15224
15601
  const existing = sub.items.data.find((item2) => item2.metadata?.seatId === seat.id);
15225
- if (existing) return String(existing.id);
15226
- const result = await (0, import_stripe15.stripeRequest)({ secretKey: sk }, "POST", "/v1/subscription_items", {
15602
+ if (existing) {
15603
+ if (!/^si_[A-Za-z0-9_]+$/.test(existing.id)) reject7();
15604
+ return String(existing.id);
15605
+ }
15606
+ if (seat.combinedInvoice) reject7();
15607
+ const result = await (0, import_stripe16.stripeRequest)({ secretKey: sk }, "POST", "/v1/subscription_items", {
15227
15608
  subscription: seat.providerSubscriptionId,
15228
15609
  price: seat.priceId,
15229
15610
  quantity: 1,
@@ -15440,7 +15821,7 @@ async function recordRecipient(db, input, now, approve) {
15440
15821
  const state = await loadMembershipAuthorityState(db, input);
15441
15822
  const seat = await membershipLoad(db, "networkSeat", input.seatId);
15442
15823
  requireMembershipScope(seat, input);
15443
- if (!seat.purchaserUserId || seat.purchaserUserId === input.recipientUserId) membershipFail("named_seat_recipient_must_differ_from_purchaser");
15824
+ if (!seat.purchaserUserId && !seat.purchaserEmailNormalized || seat.purchaserUserId === input.recipientUserId || seat.purchaserEmailNormalized === normalizeChapterEmail(input.verifiedRecipientEmail)) membershipFail("named_seat_recipient_must_differ_from_purchaser");
15444
15825
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
15445
15826
  requireMembershipScope(member, input);
15446
15827
  const entitlement = await membershipLoad(db, "networkEntitlement", String(seat.entitlementId));
@@ -15760,9 +16141,14 @@ function createMembershipAuthorityHandler(options) {
15760
16141
  try {
15761
16142
  input = JSON.parse(verified.body);
15762
16143
  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] ?? []];
15763
- 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]) : SAFE2.test(input[key3])))) {
16144
+ 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]) : SAFE2.test(input[key3])))) {
15764
16145
  throw new TypeError("invalid request");
15765
16146
  }
16147
+ if (action === "checkouts" && input.namedSeat !== void 0) {
16148
+ const seat = input.namedSeat;
16149
+ 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");
16150
+ checkoutNamedSeatSelection(seat, seat.purchaserEmail);
16151
+ }
15766
16152
  } catch {
15767
16153
  return respond({ error: "invalid_offer_request" }, 400);
15768
16154
  }
@@ -15835,8 +16221,9 @@ function createMembershipAuthorityHandler(options) {
15835
16221
  offerId: String(input.offerId),
15836
16222
  offerDigest: String(input.offerDigest),
15837
16223
  quoteDigest: String(input.quoteDigest),
15838
- priceId: String(input.priceId)
15839
- }, options.now?.() ?? Date.now());
16224
+ priceId: String(input.priceId),
16225
+ ...input.namedSeat ? { namedSeat: input.namedSeat } : {}
16226
+ }, options.now?.() ?? Date.now(), peer.seatPriceId);
15840
16227
  return respond({ ok: true, binding });
15841
16228
  }
15842
16229
  const offerId = await membershipOfferIdForRequest({ ...peer, applicationId: String(input.applicationId), requestId: String(input.requestId) });
@@ -15864,13 +16251,13 @@ function createMembershipAuthorityHandler(options) {
15864
16251
 
15865
16252
  // src/schema-membership-authority.ts
15866
16253
  var attr = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
15867
- var id4 = () => attr("string", false, true, true);
16254
+ var id5 = () => attr("string", false, true, true);
15868
16255
  var key = (optional = false) => attr("string", optional, false, true);
15869
16256
  var scope = () => ({ appId: key(), chapterId: key(), environment: key(), runtime: key(), applicationId: key(), skuId: key() });
15870
16257
  var times = () => ({ revision: attr("number"), createdAt: attr("number"), updatedAt: attr("number") });
15871
16258
  var membershipAuthorityEntities = {
15872
16259
  networkMembership: { attrs: {
15873
- id: id4(),
16260
+ id: id5(),
15874
16261
  membershipId: key(),
15875
16262
  ...scope(),
15876
16263
  personId: key(true),
@@ -15910,7 +16297,7 @@ var membershipAuthorityEntities = {
15910
16297
  restartId: key(true)
15911
16298
  } },
15912
16299
  membershipRestart: { attrs: {
15913
- id: id4(),
16300
+ id: id5(),
15914
16301
  ...scope(),
15915
16302
  membershipId: key(),
15916
16303
  restartId: key(),
@@ -15920,7 +16307,7 @@ var membershipAuthorityEntities = {
15920
16307
  createdAt: attr("number")
15921
16308
  } },
15922
16309
  membershipPurchaseHistory: { attrs: {
15923
- id: id4(),
16310
+ id: id5(),
15924
16311
  ...scope(),
15925
16312
  membershipId: key(),
15926
16313
  providerSubscriptionId: key(),
@@ -15929,7 +16316,7 @@ var membershipAuthorityEntities = {
15929
16316
  archivedAt: attr("number")
15930
16317
  } },
15931
16318
  networkEntitlement: { attrs: {
15932
- id: id4(),
16319
+ id: id5(),
15933
16320
  ...scope(),
15934
16321
  membershipId: key(),
15935
16322
  seatId: key(true),
@@ -15946,10 +16333,12 @@ var membershipAuthorityEntities = {
15946
16333
  journeyApprovedAt: attr("number", true)
15947
16334
  } },
15948
16335
  networkSeat: { attrs: {
15949
- id: id4(),
16336
+ id: id5(),
15950
16337
  ...scope(),
15951
16338
  primaryMembershipId: key(),
15952
- purchaserUserId: key(),
16339
+ purchaserUserId: key(true),
16340
+ purchaserEmailNormalized: key(true),
16341
+ combinedInvoice: attr("json", true),
15953
16342
  recipientName: attr("string"),
15954
16343
  recipientEmailNormalized: key(),
15955
16344
  recipientClaimId: key(),
@@ -15981,9 +16370,9 @@ var membershipAuthorityEntities = {
15981
16370
  refundedCents: attr("number", true),
15982
16371
  providerRefundIds: attr("json", true)
15983
16372
  } },
15984
- networkSeatRecipient: { attrs: { id: id4(), appId: key(), chapterId: key(), environment: key(), runtime: key(), seatId: key(), recipientEmailNormalized: key(), createdAt: attr("number") } },
16373
+ networkSeatRecipient: { attrs: { id: id5(), appId: key(), chapterId: key(), environment: key(), runtime: key(), seatId: key(), recipientEmailNormalized: key(), createdAt: attr("number") } },
15985
16374
  membershipEffect: { attrs: {
15986
- id: id4(),
16375
+ id: id5(),
15987
16376
  chapterId: key(),
15988
16377
  environment: key(),
15989
16378
  runtime: key(),
@@ -16002,11 +16391,11 @@ var membershipAuthorityRules = Object.fromEntries(Object.keys(membershipAuthorit
16002
16391
 
16003
16392
  // src/schema-membership-offers.ts
16004
16393
  var attr2 = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
16005
- var id5 = () => attr2("string", false, true, true);
16394
+ var id6 = () => attr2("string", false, true, true);
16006
16395
  var key2 = () => attr2("string", false, false, true);
16007
16396
  var membershipOfferEntities = {
16008
16397
  membershipAuthorityState: { attrs: {
16009
- id: id5(),
16398
+ id: id6(),
16010
16399
  chapterId: key2(),
16011
16400
  environment: key2(),
16012
16401
  mode: key2(),
@@ -16016,7 +16405,7 @@ var membershipOfferEntities = {
16016
16405
  updatedAt: attr2("number")
16017
16406
  } },
16018
16407
  membershipCheckoutOffer: { attrs: {
16019
- id: id5(),
16408
+ id: id6(),
16020
16409
  applicationId: key2(),
16021
16410
  appId: key2(),
16022
16411
  chapterId: key2(),
@@ -16031,7 +16420,7 @@ var membershipOfferEntities = {
16031
16420
  authorityRevision: attr2("number")
16032
16421
  } },
16033
16422
  membershipOfferCheckout: { attrs: {
16034
- id: id5(),
16423
+ id: id6(),
16035
16424
  appId: key2(),
16036
16425
  chapterId: key2(),
16037
16426
  environment: key2(),
@@ -16042,7 +16431,8 @@ var membershipOfferEntities = {
16042
16431
  offerDigest: attr2("string"),
16043
16432
  quoteDigest: attr2("string"),
16044
16433
  priceId: attr2("string"),
16045
- boundAt: attr2("number")
16434
+ boundAt: attr2("number"),
16435
+ namedSeat: attr2("json", true)
16046
16436
  } }
16047
16437
  };
16048
16438
  var membershipOfferRules = Object.fromEntries(Object.keys(membershipOfferEntities).map((ns) => [ns, { view: "false", create: "false", update: "false", delete: "false" }]));
@@ -16097,7 +16487,7 @@ function createMembershipAuthorityStorage(db, secretNames) {
16097
16487
  }
16098
16488
 
16099
16489
  // src/membership-seat-cancel.ts
16100
- var import_stripe16 = require("@odla-ai/stripe");
16490
+ var import_stripe17 = require("@odla-ai/stripe");
16101
16491
  async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
16102
16492
  membershipClock(now);
16103
16493
  const state = await loadMembershipAuthorityState(db, target);
@@ -16125,7 +16515,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
16125
16515
  }
16126
16516
  const metadata = { ...stripePartitionMetadata(target), applicationId: target.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
16127
16517
  const request = async (method, path, params) => {
16128
- const result = await (0, import_stripe16.stripeRequest)(
16518
+ const result = await (0, import_stripe17.stripeRequest)(
16129
16519
  { secretKey },
16130
16520
  method,
16131
16521
  path,
@@ -16138,7 +16528,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
16138
16528
  let payment;
16139
16529
  if (seat.providerPaymentIntentId) payment = await request("GET", `/v1/payment_intents/${encodeURIComponent(String(seat.providerPaymentIntentId))}`);
16140
16530
  else {
16141
- const snapshot = await listStripeObjects((0, import_stripe16.initStripe)({ secretKey }), "/v1/payment_intents", { customer: String(seat.providerCustomerId) });
16531
+ const snapshot = await listStripeObjects((0, import_stripe17.initStripe)({ secretKey }), "/v1/payment_intents", { customer: String(seat.providerCustomerId) });
16142
16532
  const matches2 = snapshot.objects.filter((row2) => row2.metadata?.seatId === target.seatId);
16143
16533
  if (!snapshot.complete || matches2.length !== 1) throw new Error("seat payment creation outcome unresolved");
16144
16534
  payment = matches2[0];
@@ -16151,9 +16541,9 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
16151
16541
  if (payment.status === "succeeded" || payment.amount_received > 0) throw new Error("paid seat requires payment evidence and full refund");
16152
16542
  if (payment.status !== "canceled") {
16153
16543
  if (!["requires_payment_method", "requires_confirmation", "requires_action", "processing"].includes(payment.status)) throw new Error("seat payment outcome unresolved");
16154
- const id6 = payment.id;
16155
- payment = await request("POST", `/v1/payment_intents/${encodeURIComponent(id6)}/cancel`, { cancellation_reason: "abandoned" });
16156
- if (payment.id !== id6) throw new Error("seat cancellation receipt mismatch");
16544
+ const id7 = payment.id;
16545
+ payment = await request("POST", `/v1/payment_intents/${encodeURIComponent(id7)}/cancel`, { cancellation_reason: "abandoned" });
16546
+ if (payment.id !== id7) throw new Error("seat cancellation receipt mismatch");
16157
16547
  }
16158
16548
  if (!valid(payment) || payment.status !== "canceled" || payment.amount_received !== 0) throw new Error("seat cancellation unconfirmed");
16159
16549
  await db.transact([membershipUpdate("networkSeat", target.seatId, { status: "canceled", revision: Number(seat.revision) + 1, updatedAt: now })], {
@@ -16485,7 +16875,8 @@ async function requestMembershipCheckoutBinding(options) {
16485
16875
  offerId: options.offerId,
16486
16876
  offerDigest: options.offerDigest,
16487
16877
  quoteDigest: options.quoteDigest,
16488
- priceId: options.priceId
16878
+ priceId: options.priceId,
16879
+ ...options.namedSeat ? { namedSeat: options.namedSeat } : {}
16489
16880
  });
16490
16881
  return verifyMembershipCheckoutBinding(result.binding, options);
16491
16882
  }