@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.
@@ -79,10 +79,10 @@ async function clerkGetUserByEmail(secretKey, email, fetchImpl = fetch, source =
79
79
  const user = Array.isArray(data) ? data[0] : void 0;
80
80
  return user ? toRecord(user, source) : null;
81
81
  }
82
- async function clerkGetUser(secretKey, id6, fetchImpl = fetch, source = "public") {
83
- const data = await clerkGet(`/v1/users/${encodeURIComponent(id6)}`, secretKey, fetchImpl).catch(() => null);
82
+ async function clerkGetUser(secretKey, id7, fetchImpl = fetch, source = "public") {
83
+ const data = await clerkGet(`/v1/users/${encodeURIComponent(id7)}`, secretKey, fetchImpl).catch(() => null);
84
84
  const record4 = data ? toRecord(data, source) : null;
85
- return record4?.id === id6 ? record4 : null;
85
+ return record4?.id === id7 ? record4 : null;
86
86
  }
87
87
  async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
88
88
  const out = [];
@@ -98,8 +98,8 @@ async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
98
98
  }
99
99
  return out;
100
100
  }
101
- async function clerkSetRole(secretKey, id6, role, fetchImpl = fetch, source = "public") {
102
- const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id6)}/metadata`, {
101
+ async function clerkSetRole(secretKey, id7, role, fetchImpl = fetch, source = "public") {
102
+ const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id7)}/metadata`, {
103
103
  method: "PATCH",
104
104
  headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
105
105
  redirect: "manual",
@@ -158,10 +158,10 @@ async function ensureProjectedPerson(deps, input, options = {}) {
158
158
  const existing = await find();
159
159
  if (existing) return { record: existing, created: false };
160
160
  const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`chapter:person:v1:${email}`)));
161
- const id6 = `person-${Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
161
+ const id7 = `person-${Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
162
162
  try {
163
163
  const created = await createRecord({ ...deps, db: deps.db }, {
164
- id: id6,
164
+ id: id7,
165
165
  type: "person",
166
166
  input: { ...input, email },
167
167
  ...options
@@ -614,7 +614,10 @@ function verifyInitialMembershipInvoice(intent, evidence) {
614
614
  if (!intent.providerSubscriptionId || !intent.providerCustomerId) return { status: "retry" };
615
615
  try {
616
616
  const terms2 = intent.billingTerms ? subscriptionBillingTerms(intent.billingTerms) : void 0;
617
- const due = subscriptionAmountDue({ priceCents: Number(intent.priceCents), billingTerms: terms2 });
617
+ const quote = intent.paymentQuote;
618
+ const seatCents = quote?.namedSeat ? quote.lineItems.namedSeatCents : 0;
619
+ if (!Number.isSafeInteger(seatCents) || Number(seatCents) < 0 || quote?.namedSeat && quote.seatOffer?.amountCents !== seatCents) return { status: "mismatch" };
620
+ const due = subscriptionAmountDue({ priceCents: Number(intent.priceCents), billingTerms: terms2 }) + Number(seatCents);
618
621
  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" };
619
622
  return { status: "verified", ...terms2?.initialTermEndsAt ? { renewalAt: terms2.initialTermEndsAt } : {} };
620
623
  } catch {
@@ -965,8 +968,8 @@ async function rebuildNetworkCommercialProjection(db, sources, mutationId, now =
965
968
  return projection;
966
969
  }
967
970
  async function collide(db, sources, stored, fact, now, legacyDigest, alerts) {
968
- const id6 = exceptionId(fact.eventId, "collision");
969
- const exception = (await db.query({ networkCommercialExceptions: { $: { where: { id: id6 }, limit: 1 } } })).networkCommercialExceptions?.[0];
971
+ const id7 = exceptionId(fact.eventId, "collision");
972
+ const exception = (await db.query({ networkCommercialExceptions: { $: { where: { id: id7 }, limit: 1 } } })).networkCommercialExceptions?.[0];
970
973
  const exact = stored.digest === fact.digest || stored.digest === legacyDigest;
971
974
  const canonical3 = !exact && sameNetworkCommercialFact(stored.fact, fact);
972
975
  if (exact || canonical3) {
@@ -987,7 +990,7 @@ async function collide(db, sources, stored, fact, now, legacyDigest, alerts) {
987
990
  action: "canonical_replay",
988
991
  reason: "a resend matched the retained canonical fact"
989
992
  } },
990
- { t: "update", ns: "networkCommercialExceptions", id: id6, attrs: {
993
+ { t: "update", ns: "networkCommercialExceptions", id: id7, attrs: {
991
994
  status: "resolved",
992
995
  resolvedDigest: stored.digest,
993
996
  reviewedDigest: fact.digest,
@@ -996,7 +999,7 @@ async function collide(db, sources, stored, fact, now, legacyDigest, alerts) {
996
999
  updatedAt: now
997
1000
  } }
998
1001
  ], { mutationId: `network-commercial-resolve:${fact.eventId}:${fact.digest}`, guards: [
999
- { ns: "networkCommercialExceptions", id: id6, exists: true, equals: { status: "active", updatedAt: exception.updatedAt } }
1002
+ { ns: "networkCommercialExceptions", id: id7, exists: true, equals: { status: "active", updatedAt: exception.updatedAt } }
1000
1003
  ] });
1001
1004
  const projection2 = await rebuildNetworkCommercialProjection(db, sources, `network-commercial-resolve-rebuild:${fact.eventId}:${fact.digest}`, now, alerts);
1002
1005
  return { status: "replayed", resolvedCollision: true, projection: projection2 };
@@ -1006,9 +1009,9 @@ async function collide(db, sources, stored, fact, now, legacyDigest, alerts) {
1006
1009
  }
1007
1010
  const stale = RESOLUTION_ATTRS.filter((attr3) => exception !== void 0 && Object.hasOwn(exception, attr3));
1008
1011
  await db.transact([
1009
- ...stale.length ? [{ t: "retract", ns: "networkCommercialExceptions", id: id6, attrs: stale }] : [],
1010
- { t: "update", ns: "networkCommercialExceptions", id: id6, attrs: {
1011
- id: id6,
1012
+ ...stale.length ? [{ t: "retract", ns: "networkCommercialExceptions", id: id7, attrs: stale }] : [],
1013
+ { t: "update", ns: "networkCommercialExceptions", id: id7, attrs: {
1014
+ id: id7,
1012
1015
  eventId: fact.eventId,
1013
1016
  kind: "collision",
1014
1017
  status: "active",
@@ -1028,12 +1031,12 @@ async function ingestNetworkCommercialFact(db, sources, fact, now = Date.now(),
1028
1031
  const found = await db.query({ networkCommercialEvents: { $: { where: { eventId: fact.eventId }, limit: 1 } } });
1029
1032
  const existing = found.networkCommercialEvents?.[0];
1030
1033
  if (existing) return collide(db, sources, existing, fact, now, legacyDigest, alerts);
1031
- const id6 = eventRowId(fact.eventId);
1034
+ const id7 = eventRowId(fact.eventId);
1032
1035
  let lostRace = false;
1033
1036
  let duplicate = false;
1034
1037
  try {
1035
- const write = await db.transact([{ t: "update", ns: "networkCommercialEvents", id: id6, attrs: {
1036
- id: id6,
1038
+ const write = await db.transact([{ t: "update", ns: "networkCommercialEvents", id: id7, attrs: {
1039
+ id: id7,
1037
1040
  eventId: fact.eventId,
1038
1041
  digest: fact.digest,
1039
1042
  eventType: fact.eventType,
@@ -1043,7 +1046,7 @@ async function ingestNetworkCommercialFact(db, sources, fact, now = Date.now(),
1043
1046
  receivedAt: now
1044
1047
  } }], {
1045
1048
  mutationId: `network-commercial-ingest:${fact.eventId}:${fact.digest}`,
1046
- guards: [{ ns: "networkCommercialEvents", id: id6, exists: false }]
1049
+ guards: [{ ns: "networkCommercialEvents", id: id7, exists: false }]
1047
1050
  });
1048
1051
  duplicate = write.duplicate;
1049
1052
  } catch (error) {
@@ -1087,10 +1090,10 @@ async function recoverNetworkCommercialEvent(db, sources, eventId, actorUserId,
1087
1090
  const provider = await readStripeRecoveryEvent(db, eventId);
1088
1091
  const fact = normalizeNetworkCommercialStripeEvent(provider.event, provider.digest);
1089
1092
  if (!sameNetworkCommercialFact(stored.fact, fact)) throw new Error("Stripe facts differ; collision requires investigation");
1090
- const id6 = crypto.randomUUID();
1093
+ const id7 = crypto.randomUUID();
1091
1094
  await db.transact([
1092
- { t: "update", ns: "paymentRecoveryReviews", id: id6, attrs: {
1093
- id: id6,
1095
+ { t: "update", ns: "paymentRecoveryReviews", id: id7, attrs: {
1096
+ id: id7,
1094
1097
  eventId,
1095
1098
  actorUserId,
1096
1099
  reviewedAt: now,
@@ -1105,9 +1108,9 @@ async function recoverNetworkCommercialEvent(db, sources, eventId, actorUserId,
1105
1108
  t: "update",
1106
1109
  ns: "networkCommercialExceptions",
1107
1110
  id: exceptionId2,
1108
- attrs: { status: "resolved", resolvedDigest: provider.digest, resolvedBy: actorUserId, reviewId: id6, updatedAt: now }
1111
+ attrs: { status: "resolved", resolvedDigest: provider.digest, resolvedBy: actorUserId, reviewId: id7, updatedAt: now }
1109
1112
  }] : []
1110
- ], { mutationId: `payment-recovery:${id6}`, guards: [
1113
+ ], { mutationId: `payment-recovery:${id7}`, guards: [
1111
1114
  { ns: "networkCommercialEvents", id: eventRowId2, exists: true, equals: { digest: stored.digest } },
1112
1115
  {
1113
1116
  ns: "networkCommercialExceptions",
@@ -1116,8 +1119,8 @@ async function recoverNetworkCommercialEvent(db, sources, eventId, actorUserId,
1116
1119
  ...exception ? { equals: { status: exception.status, updatedAt: exception.updatedAt } } : {}
1117
1120
  }
1118
1121
  ] });
1119
- await rebuildNetworkCommercialProjection(db, sources, `payment-recovery-rebuild:${id6}`, now, alerts);
1120
- return { recovered: true, eventId, reviewId: id6 };
1122
+ await rebuildNetworkCommercialProjection(db, sources, `payment-recovery-rebuild:${id7}`, now, alerts);
1123
+ return { recovered: true, eventId, reviewId: id7 };
1121
1124
  }
1122
1125
 
1123
1126
  // src/payments.ts
@@ -1901,11 +1904,11 @@ async function sendTemplated(deps, input) {
1901
1904
  const previous = prior[0];
1902
1905
  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" };
1903
1906
  const hash2 = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input.dedupeKey));
1904
- const id6 = previous ? String(previous.id) : `email_${Array.from(new Uint8Array(hash2), (b) => b.toString(16).padStart(2, "0")).join("")}`;
1907
+ const id7 = previous ? String(previous.id) : `email_${Array.from(new Uint8Array(hash2), (b) => b.toString(16).padStart(2, "0")).join("")}`;
1905
1908
  const attempt = Number(previous?.deliveryAttempt ?? 0) + 1;
1906
1909
  if (!Number.isSafeInteger(attempt) || attempt < 1) return { sent: false, reason: "delivery-state-invalid" };
1907
1910
  const row2 = {
1908
- id: id6,
1911
+ id: id7,
1909
1912
  groupId: input.group.id,
1910
1913
  to: decision.to,
1911
1914
  template: input.template,
@@ -1923,15 +1926,15 @@ async function sendTemplated(deps, input) {
1923
1926
  if (isRetiredEmailCopy(String(row2.subject), String(row2.body))) return { sent: false, reason: "template-retired" };
1924
1927
  if (validateEmailCopy2({ subject: row2.subject, text: row2.body }, { allowedVariables: [] }).length) return { sent: false, reason: "template-invalid" };
1925
1928
  try {
1926
- const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id: id6, attrs: row2 }], {
1927
- mutationId: `email-claim:${id6}:${attempt}`,
1929
+ const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id: id7, attrs: row2 }], {
1930
+ mutationId: `email-claim:${id7}:${attempt}`,
1928
1931
  guards: [previous ? {
1929
1932
  ns: "emailLog",
1930
- id: id6,
1933
+ id: id7,
1931
1934
  exists: true,
1932
1935
  equals: { deliveryState: "retry_ready", ...previous.deliveryAttempt !== void 0 ? { deliveryAttempt: previous.deliveryAttempt } : {} },
1933
1936
  ...previous.deliveryAttempt === void 0 ? { absent: ["deliveryAttempt"] } : {}
1934
- } : { ns: "emailLog", id: id6, exists: false }]
1937
+ } : { ns: "emailLog", id: id7, exists: false }]
1935
1938
  });
1936
1939
  if (claim.duplicate) return { sent: false, reason: "delivery-uncertain" };
1937
1940
  } catch (error) {
@@ -1951,16 +1954,16 @@ async function sendTemplated(deps, input) {
1951
1954
  if (!response || typeof response.messageId !== "string" || !response.messageId) throw new Error("missing delivery receipt");
1952
1955
  messageId = response.messageId;
1953
1956
  } catch {
1954
- await deps.db.transact([{ t: "update", ns: "emailLog", id: id6, attrs: { deliveryState: "uncertain", error: "delivery-uncertain" } }], {
1955
- guards: [{ ns: "emailLog", id: id6, exists: true, equals: { deliveryState: "sending", deliveryAttempt: attempt } }]
1957
+ await deps.db.transact([{ t: "update", ns: "emailLog", id: id7, attrs: { deliveryState: "uncertain", error: "delivery-uncertain" } }], {
1958
+ guards: [{ ns: "emailLog", id: id7, exists: true, equals: { deliveryState: "sending", deliveryAttempt: attempt } }]
1956
1959
  });
1957
1960
  return { sent: false, reason: "delivery-uncertain" };
1958
1961
  }
1959
- await deps.db.transact([{ t: "update", ns: "emailLog", id: id6, attrs: { deliveryState: "sent", error: "", messageId, sentAt: deps.now() } }], {
1960
- mutationId: `email-confirmed:${id6}:${attempt}`,
1962
+ await deps.db.transact([{ t: "update", ns: "emailLog", id: id7, attrs: { deliveryState: "sent", error: "", messageId, sentAt: deps.now() } }], {
1963
+ mutationId: `email-confirmed:${id7}:${attempt}`,
1961
1964
  guards: [{
1962
1965
  ns: "emailLog",
1963
- id: id6,
1966
+ id: id7,
1964
1967
  exists: true,
1965
1968
  equals: { deliveryState: "sending", deliveryAttempt: attempt }
1966
1969
  }]
@@ -2145,8 +2148,8 @@ async function hash(value) {
2145
2148
  async function membershipDiscountCouponId(input, policyId) {
2146
2149
  return policyId ? `odla_m_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.skuId, policyId])}` : void 0;
2147
2150
  }
2148
- async function load(db, ns, id6) {
2149
- return (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
2151
+ async function load(db, ns, id7) {
2152
+ return (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
2150
2153
  }
2151
2154
  async function membershipOfferIdForRequest(input) {
2152
2155
  return `offer_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.applicationId, input.requestId])}`;
@@ -2280,20 +2283,20 @@ async function issueMembershipCommercialOffer(db, input) {
2280
2283
 
2281
2284
  // src/commercial-evidence-guard.ts
2282
2285
  async function commercialEvidenceGuard(db, eventId, digest, code = "membership_payment_evidence_quarantined") {
2283
- const ns = "networkCommercialExceptions", id6 = `network-commercial-exception:${eventId}:collision`;
2284
- const row2 = (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
2285
- if (!row2) return { ns, id: id6, exists: false };
2286
+ const ns = "networkCommercialExceptions", id7 = `network-commercial-exception:${eventId}:collision`;
2287
+ const row2 = (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
2288
+ if (!row2) return { ns, id: id7, exists: false };
2286
2289
  if (row2.status !== "resolved" || row2.resolvedDigest !== digest || typeof digest !== "string") {
2287
2290
  throw Object.assign(new Error(code), { code });
2288
2291
  }
2289
- return { ns, id: id6, exists: true, equals: { status: "resolved", resolvedDigest: digest, updatedAt: row2.updatedAt } };
2292
+ return { ns, id: id7, exists: true, equals: { status: "resolved", resolvedDigest: digest, updatedAt: row2.updatedAt } };
2290
2293
  }
2291
2294
 
2292
2295
  // src/membership-authority-state.ts
2293
2296
  var membershipFail = (code) => {
2294
2297
  throw Object.assign(new Error(code), { code });
2295
2298
  };
2296
- var membershipLoad = async (db, ns, id6) => (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
2299
+ var membershipLoad = async (db, ns, id7) => (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
2297
2300
  var membershipScopeKeys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId"];
2298
2301
  var membershipScope = (input) => Object.fromEntries(membershipScopeKeys.map((key3) => [key3, input[key3]]));
2299
2302
  function requireMembershipScope(row2, input) {
@@ -2304,18 +2307,18 @@ async function loadMembershipAuthorityState(db, input) {
2304
2307
  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");
2305
2308
  return row2;
2306
2309
  }
2307
- var membershipUpdate = (ns, id6, attrs) => ({ t: "update", ns, id: id6, attrs });
2310
+ var membershipUpdate = (ns, id7, attrs) => ({ t: "update", ns, id: id7, attrs });
2308
2311
  var membershipClock = (now) => {
2309
2312
  if (!Number.isSafeInteger(now) || now < 0) membershipFail("invalid_membership_clock");
2310
2313
  };
2311
2314
 
2312
2315
  // src/membership-restart-payment.ts
2313
2316
  async function membershipRestartPayment(db, target, member, now) {
2314
- const id6 = String(member.id), restart = await membershipLoad(db, "membershipRestart", id6);
2317
+ const id7 = String(member.id), restart = await membershipLoad(db, "membershipRestart", id7);
2315
2318
  requireMembershipScope(restart, target);
2316
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id6}:primary`);
2319
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id7}:primary`);
2317
2320
  requireMembershipScope(entitlement, target);
2318
- if (entitlement.membershipId !== id6 || entitlement.kind !== "primary") membershipFail("membership_restart_conflict");
2321
+ if (entitlement.membershipId !== id7 || entitlement.kind !== "primary") membershipFail("membership_restart_conflict");
2319
2322
  if (restart.status !== "ready" || member.status !== "restarting" || member.restartId !== restart.restartId || member.memberNumber !== restart.memberNumber || !Number.isSafeInteger(member.journeyApprovedAt)) membershipFail("membership_restart_conflict");
2320
2323
  return {
2321
2324
  restartId: String(restart.restartId),
@@ -2332,49 +2335,117 @@ async function membershipRestartPayment(db, target, member, now) {
2332
2335
  graceEndsAt: 0
2333
2336
  },
2334
2337
  ops: [
2335
- membershipUpdate("membershipRestart", id6, { status: "complete" }),
2336
- { t: "retract", ns: "networkMembership", id: id6, attrs: ["refundRequestedAt", "cancellationRequestedAt", "discountPolicyVersionId", "discountSnapshot"] }
2338
+ membershipUpdate("membershipRestart", id7, { status: "complete" }),
2339
+ { t: "retract", ns: "networkMembership", id: id7, attrs: ["refundRequestedAt", "cancellationRequestedAt", "discountPolicyVersionId", "discountSnapshot"] }
2337
2340
  ],
2338
2341
  guards: [
2339
- { ns: "networkMembership", id: id6, exists: true, equals: { status: "restarting", revision: member.revision, restartId: restart.restartId } },
2340
- { ns: "membershipRestart", id: id6, exists: true, equals: { status: "ready", restartId: restart.restartId } },
2342
+ { ns: "networkMembership", id: id7, exists: true, equals: { status: "restarting", revision: member.revision, restartId: restart.restartId } },
2343
+ { ns: "membershipRestart", id: id7, exists: true, equals: { status: "ready", restartId: restart.restartId } },
2341
2344
  { ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision, status: entitlement.status } }
2342
2345
  ]
2343
2346
  };
2344
2347
  }
2345
2348
 
2349
+ // src/checkout-named-seat.ts
2350
+ function checkoutNamedSeatSelection(value, purchaserEmail) {
2351
+ const row2 = value;
2352
+ const name = typeof row2?.recipientName === "string" ? row2.recipientName.trim().replace(/\s+/g, " ") : "";
2353
+ const email = normalizeChapterEmail(typeof row2?.recipientEmail === "string" ? row2.recipientEmail : "");
2354
+ const purchaser = normalizeChapterEmail(purchaserEmail);
2355
+ 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) {
2356
+ throw Object.assign(new Error("Enter a different recipient's name and email."), { code: "invalid_named_seat_recipient" });
2357
+ }
2358
+ return { recipientName: name, recipientEmail: email };
2359
+ }
2360
+ async function checkoutNamedSeatIdentity(target, email) {
2361
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([
2362
+ target.appId,
2363
+ target.chapterId,
2364
+ target.environment,
2365
+ target.runtime,
2366
+ email
2367
+ ])));
2368
+ return {
2369
+ seatId: (await membershipOfferIdForRequest({ ...target, requestId: "named_seat" })).replace(/^offer_/, "seat_"),
2370
+ membershipId: (await membershipOfferIdForRequest({ ...target, requestId: "checkout_binding" })).replace(/^offer_/, "membership_"),
2371
+ recipientClaimId: `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`
2372
+ };
2373
+ }
2374
+
2346
2375
  // src/membership-checkout-binding.ts
2347
2376
  var keys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId", "offerId", "offerDigest", "quoteDigest", "priceId"];
2348
2377
  var reject2 = (code) => {
2349
2378
  throw Object.assign(new Error(code), { code });
2350
2379
  };
2351
- var load2 = async (db, ns, id6) => (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
2380
+ var load2 = async (db, ns, id7) => (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
2352
2381
  async function membershipCheckoutBindingId(input) {
2353
2382
  return (await membershipOfferIdForRequest({ ...input, requestId: "checkout_binding" })).replace(/^offer_/, "checkout_");
2354
2383
  }
2355
2384
  async function verifyMembershipCheckoutBinding(value, input) {
2356
2385
  const row2 = value;
2357
2386
  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");
2358
- return { id: row2.id, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: row2.boundAt };
2387
+ if (Boolean(row2.namedSeat) !== Boolean(input.namedSeat)) return reject2("membership_checkout_conflict");
2388
+ if (input.namedSeat) {
2389
+ const expected = {
2390
+ ...checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail),
2391
+ purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail)
2392
+ };
2393
+ const identity2 = await checkoutNamedSeatIdentity(input, expected.recipientEmail);
2394
+ 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");
2395
+ }
2396
+ return {
2397
+ id: row2.id,
2398
+ ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])),
2399
+ boundAt: row2.boundAt,
2400
+ ...row2.namedSeat ? { namedSeat: row2.namedSeat } : {}
2401
+ };
2359
2402
  }
2360
- async function bindMembershipCheckout(db, input, now) {
2403
+ async function bindMembershipCheckout(db, input, now, seatPriceId) {
2361
2404
  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");
2362
2405
  const stateId = `${input.chapterId}:${input.environment}`;
2363
2406
  const state = await load2(db, "membershipAuthorityState", stateId);
2364
2407
  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");
2365
- const id6 = await membershipCheckoutBindingId(input);
2366
- const existing = await load2(db, "membershipOfferCheckout", id6);
2408
+ const id7 = await membershipCheckoutBindingId(input);
2409
+ const existing = await load2(db, "membershipOfferCheckout", id7);
2367
2410
  if (existing) return verifyMembershipCheckoutBinding(existing, input);
2368
2411
  const row2 = await load2(db, "membershipCheckoutOffer", input.offerId);
2369
2412
  const offer = await verifyIssuedMembershipOffer(row2, { ...input, now, lifetimeMs: 6e4 });
2370
2413
  if (offer.digest !== input.offerDigest || offer.expiresAt <= now || offer.issuedAt > now) return reject2("membership_offer_expired");
2371
- const restart = await load2(db, "membershipRestart", id6.replace(/^checkout_/, "membership_"));
2372
- const receipt = { id: id6, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: now };
2414
+ const restart = await load2(db, "membershipRestart", id7.replace(/^checkout_/, "membership_"));
2415
+ let namedSeat;
2416
+ if (input.namedSeat) {
2417
+ if (!seatPriceId || !/^price_[A-Za-z0-9_]+$/.test(seatPriceId) || seatPriceId === input.priceId || restart) return reject2("named_seat_purchase_unavailable");
2418
+ const selection = checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail);
2419
+ namedSeat = {
2420
+ ...selection,
2421
+ purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail),
2422
+ ...await checkoutNamedSeatIdentity(input, selection.recipientEmail),
2423
+ priceId: seatPriceId,
2424
+ amountCents: offer.contract.offering.seatPriceCents
2425
+ };
2426
+ }
2427
+ const receipt = { id: id7, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: now, ...namedSeat ? { namedSeat } : {} };
2373
2428
  try {
2374
- await db.transact([{ t: "update", ns: "membershipOfferCheckout", id: id6, attrs: receipt }], {
2375
- mutationId: `membership-checkout:${id6}${restart?.restartId ? `:${restart.restartId}` : ""}`,
2429
+ await db.transact([
2430
+ { t: "update", ns: "membershipOfferCheckout", id: id7, attrs: receipt },
2431
+ ...namedSeat ? [{ t: "update", ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, attrs: {
2432
+ id: namedSeat.recipientClaimId,
2433
+ appId: input.appId,
2434
+ chapterId: input.chapterId,
2435
+ environment: input.environment,
2436
+ runtime: input.runtime,
2437
+ seatId: namedSeat.seatId,
2438
+ recipientEmailNormalized: namedSeat.recipientEmail,
2439
+ createdAt: now
2440
+ } }] : []
2441
+ ], {
2442
+ mutationId: `membership-checkout:${id7}${restart?.restartId ? `:${restart.restartId}` : ""}`,
2376
2443
  guards: [
2377
- { ns: "membershipOfferCheckout", id: id6, exists: false },
2444
+ { ns: "membershipOfferCheckout", id: id7, exists: false },
2445
+ ...namedSeat ? [
2446
+ { ns: "networkSeat", id: namedSeat.seatId, exists: false },
2447
+ { ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, exists: false }
2448
+ ] : [],
2378
2449
  { ns: "membershipCheckoutOffer", id: input.offerId, exists: true, equals: { digest: input.offerDigest } },
2379
2450
  { ns: "membershipAuthorityState", id: stateId, exists: true, equals: { mode: "authoritative", revision: state.revision } }
2380
2451
  ]
@@ -2382,11 +2453,78 @@ async function bindMembershipCheckout(db, input, now) {
2382
2453
  } catch (error) {
2383
2454
  if (error.code !== "transact_guard_failed") throw error;
2384
2455
  }
2385
- const saved = await load2(db, "membershipOfferCheckout", id6);
2456
+ const saved = await load2(db, "membershipOfferCheckout", id7);
2386
2457
  if (!saved) return reject2("membership_binding_unconfirmed");
2387
2458
  return verifyMembershipCheckoutBinding(saved, input);
2388
2459
  }
2389
2460
 
2461
+ // src/membership-checkout-seat-payment.ts
2462
+ function checkoutSeatPaymentOperations(target, seat, input) {
2463
+ const { now, paidAt, endsAt } = input;
2464
+ const included = seat.amountCents === 0, entitlementId = `${seat.seatId}:entitlement`;
2465
+ const effectId = `${seat.seatId}:${included ? "invite" : "attach-renewal"}`;
2466
+ return [
2467
+ membershipUpdate("networkSeat", seat.seatId, {
2468
+ id: seat.seatId,
2469
+ ...membershipScope(target),
2470
+ primaryMembershipId: seat.membershipId,
2471
+ purchaserEmailNormalized: seat.purchaserEmail,
2472
+ recipientName: seat.recipientName,
2473
+ recipientEmailNormalized: seat.recipientEmail,
2474
+ recipientClaimId: seat.recipientClaimId,
2475
+ priceId: seat.priceId,
2476
+ primaryPriceId: input.primaryPriceId,
2477
+ amountCents: seat.amountCents,
2478
+ currency: input.offering.currency,
2479
+ interval: input.offering.interval,
2480
+ providerCustomerId: input.customerId,
2481
+ providerSubscriptionId: input.subscriptionId,
2482
+ combinedInvoice: { invoiceId: input.invoiceId, totalCents: input.invoiceTotalCents },
2483
+ status: included ? "pending_acceptance" : "renewal_pending",
2484
+ preparedAt: input.boundAt,
2485
+ purchasedAt: paidAt,
2486
+ termStartedAt: paidAt,
2487
+ termEndsAt: endsAt,
2488
+ acceptanceEndsAt: endsAt,
2489
+ seatPurchaseEndsAt: seatPurchaseDeadline(input.offering, { initialTermStartedAt: paidAt, termEndsAt: endsAt }),
2490
+ offeringSnapshot: input.offering,
2491
+ paymentEventId: input.eventId,
2492
+ ...included ? { entitlementId } : {},
2493
+ revision: 1,
2494
+ createdAt: now,
2495
+ updatedAt: now
2496
+ }),
2497
+ ...included ? [membershipUpdate("networkEntitlement", entitlementId, {
2498
+ id: entitlementId,
2499
+ ...membershipScope(target),
2500
+ membershipId: seat.membershipId,
2501
+ seatId: seat.seatId,
2502
+ kind: "named_seat",
2503
+ status: "pending_acceptance",
2504
+ termStartedAt: paidAt,
2505
+ termEndsAt: endsAt,
2506
+ offeringSnapshot: input.offering,
2507
+ sourceEventId: input.eventId,
2508
+ revision: 1,
2509
+ createdAt: now,
2510
+ updatedAt: now
2511
+ })] : [],
2512
+ membershipUpdate("membershipEffect", effectId, {
2513
+ id: effectId,
2514
+ chapterId: target.chapterId,
2515
+ environment: target.environment,
2516
+ runtime: target.runtime,
2517
+ membershipId: seat.membershipId,
2518
+ seatId: seat.seatId,
2519
+ kind: included ? "invite_named_recipient" : "attach_seat_renewal",
2520
+ status: "pending",
2521
+ attempts: 0,
2522
+ createdAt: now,
2523
+ updatedAt: now
2524
+ })
2525
+ ];
2526
+ }
2527
+
2390
2528
  // src/membership-payment-authority.ts
2391
2529
  async function networkMembershipId(input) {
2392
2530
  return (await membershipCheckoutBindingId(input)).replace(/^checkout_/, "membership_");
@@ -2402,11 +2540,14 @@ async function confirmMembershipPayment(db, input, now) {
2402
2540
  { ...input, offerId: String(binding.offerId), now, lifetimeMs: 6e4 }
2403
2541
  );
2404
2542
  if (binding.offerDigest !== offer.digest) membershipFail("membership_checkout_conflict");
2543
+ const { namedSeat } = await verifyMembershipCheckoutBinding(binding, binding);
2544
+ if (namedSeat && namedSeat.amountCents !== offer.contract.offering.seatPriceCents) membershipFail("membership_checkout_conflict");
2545
+ const totalCents = offer.contract.dueTodayCents + (namedSeat?.amountCents ?? 0);
2405
2546
  const eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
2406
2547
  if (!eventRow) membershipFail("membership_payment_evidence_pending");
2407
2548
  const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow.digest);
2408
2549
  const fact = eventRow.fact;
2409
- 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({
2550
+ 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({
2410
2551
  appId: input.appId,
2411
2552
  chapterId: input.chapterId,
2412
2553
  environment: input.environment,
@@ -2418,14 +2559,14 @@ async function confirmMembershipPayment(db, input, now) {
2418
2559
  const paidAt = fact.invoicePaidAt;
2419
2560
  const endsAt = offer.contract.initialTermEndsAt ?? fact.invoicePeriodEndsAt;
2420
2561
  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");
2421
- const id6 = await networkMembershipId(input), entitlementId = `${id6}:primary`;
2422
- let member = await membershipLoad(db, "networkMembership", id6);
2562
+ const id7 = await networkMembershipId(input), entitlementId = `${id7}:primary`;
2563
+ let member = await membershipLoad(db, "networkMembership", id7);
2423
2564
  if (!member || member.status === "restarting") {
2424
2565
  const offering = offer.contract.offering;
2425
2566
  const restart = member ? await membershipRestartPayment(db, input, member, now) : null;
2426
2567
  const attrs = {
2427
- id: id6,
2428
- membershipId: id6,
2568
+ id: id7,
2569
+ membershipId: id7,
2429
2570
  ...membershipScope(input),
2430
2571
  baseSkuId: input.skuId,
2431
2572
  status: "paid_pending_approval",
@@ -2441,8 +2582,9 @@ async function confirmMembershipPayment(db, input, now) {
2441
2582
  founding: offer.contract.founding,
2442
2583
  cohort: offer.contract.founding ? offer.contract.discountPolicy.cohortLabel : "standard",
2443
2584
  baseAmountCents: offering.basePriceCents,
2444
- amountCents: offer.contract.dueTodayCents,
2585
+ amountCents: totalCents,
2445
2586
  renewalAmountCents: offer.contract.renewalCents,
2587
+ ...namedSeat ? { namedSeatId: namedSeat.seatId } : {},
2446
2588
  currency: offering.currency,
2447
2589
  interval: offering.interval,
2448
2590
  initialTermStartedAt: paidAt,
@@ -2459,11 +2601,11 @@ async function confirmMembershipPayment(db, input, now) {
2459
2601
  };
2460
2602
  try {
2461
2603
  await db.transact([
2462
- membershipUpdate("networkMembership", id6, attrs),
2604
+ membershipUpdate("networkMembership", id7, attrs),
2463
2605
  membershipUpdate("networkEntitlement", entitlementId, {
2464
2606
  id: entitlementId,
2465
2607
  ...membershipScope(input),
2466
- membershipId: id6,
2608
+ membershipId: id7,
2467
2609
  kind: "primary",
2468
2610
  status: restart?.attrs.status === "active" ? "active" : "pending_approval",
2469
2611
  termStartedAt: paidAt,
@@ -2475,23 +2617,45 @@ async function confirmMembershipPayment(db, input, now) {
2475
2617
  updatedAt: now,
2476
2618
  ...restart?.entitlementAttrs
2477
2619
  }),
2478
- ...restart?.ops ?? []
2479
- ], { mutationId: `membership-paid:${id6}${restart ? `:${restart.restartId}` : ""}`, guards: [
2480
- ...restart?.guards ?? [{ ns: "networkMembership", id: id6, exists: false }],
2620
+ ...restart?.ops ?? [],
2621
+ ...namedSeat ? checkoutSeatPaymentOperations(input, namedSeat, {
2622
+ offering,
2623
+ primaryPriceId: String(binding.priceId),
2624
+ invoiceId: fact.invoiceId,
2625
+ invoiceTotalCents: totalCents,
2626
+ subscriptionId: input.providerSubscriptionId,
2627
+ customerId: input.providerCustomerId,
2628
+ eventId: input.eventId,
2629
+ paidAt: Number(paidAt),
2630
+ endsAt: Number(endsAt),
2631
+ boundAt: Number(binding.boundAt),
2632
+ now
2633
+ }) : []
2634
+ ], { mutationId: `membership-paid:${id7}${restart ? `:${restart.restartId}` : ""}`, guards: [
2635
+ ...restart?.guards ?? [{ ns: "networkMembership", id: id7, exists: false }],
2636
+ ...namedSeat ? [
2637
+ { ns: "networkSeat", id: namedSeat.seatId, exists: false },
2638
+ {
2639
+ ns: "networkSeatRecipient",
2640
+ id: namedSeat.recipientClaimId,
2641
+ exists: true,
2642
+ equals: { seatId: namedSeat.seatId, recipientEmailNormalized: namedSeat.recipientEmail }
2643
+ }
2644
+ ] : [],
2481
2645
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
2482
- { ns: "membershipOfferCheckout", id: String(binding.id), exists: true, equals: { offerDigest: offer.digest } },
2646
+ { ns: "membershipOfferCheckout", id: String(binding.id), exists: true, equals: { offerDigest: offer.digest, quoteDigest: binding.quoteDigest } },
2483
2647
  { ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
2484
2648
  evidenceGuard
2485
2649
  ] });
2486
2650
  } catch (error) {
2487
2651
  if (error.code !== "transact_guard_failed") throw error;
2488
2652
  }
2489
- member = await membershipLoad(db, "networkMembership", id6);
2653
+ member = await membershipLoad(db, "networkMembership", id7);
2490
2654
  }
2491
2655
  requireMembershipScope(member, input);
2492
2656
  if (member.purchaseOfferDigest !== offer.digest || member.providerInitialInvoiceId !== fact.invoiceId || member.providerSubscriptionId !== fact.subscriptionId || member.providerCustomerId !== fact.customerId) membershipFail("membership_purchase_conflict");
2493
2657
  return {
2494
- membershipId: id6,
2658
+ membershipId: id7,
2495
2659
  status: String(member.status),
2496
2660
  cohort: String(member.cohort),
2497
2661
  initialTermStartedAt: Number(member.initialTermStartedAt),
@@ -2562,19 +2726,19 @@ async function stopPreviousSubscription(member, target, secret) {
2562
2726
  async function prepareMembershipRestart(db, input, now, stripeSecret) {
2563
2727
  membershipClock(now);
2564
2728
  if (!/^sub_[A-Za-z0-9_]+$/.test(input.previousSubscriptionId)) membershipFail("membership_restart_conflict");
2565
- const id6 = await networkMembershipId(input), bindingId = await membershipCheckoutBindingId(input);
2566
- const restartId = `restart_${await stripePayloadDigest(JSON.stringify([id6, input.previousSubscriptionId]))}`;
2729
+ const id7 = await networkMembershipId(input), bindingId = await membershipCheckoutBindingId(input);
2730
+ const restartId = `restart_${await stripePayloadDigest(JSON.stringify([id7, input.previousSubscriptionId]))}`;
2567
2731
  const state = await loadMembershipAuthorityState(db, input);
2568
- const previous = await membershipLoad(db, "networkMembership", id6);
2732
+ const previous = await membershipLoad(db, "networkMembership", id7);
2569
2733
  requireMembershipScope(previous, input);
2570
2734
  const receipt = {
2571
2735
  applicationId: input.applicationId,
2572
- membershipId: id6,
2736
+ membershipId: id7,
2573
2737
  restartId,
2574
2738
  memberNumber: Number(previous.memberNumber),
2575
2739
  status: "ready"
2576
2740
  };
2577
- const existing = await membershipLoad(db, "membershipRestart", id6);
2741
+ const existing = await membershipLoad(db, "membershipRestart", id7);
2578
2742
  if (existing?.restartId === restartId && existing.status === "ready") {
2579
2743
  requireMembershipScope(existing, input);
2580
2744
  return receipt;
@@ -2590,33 +2754,33 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
2590
2754
  membershipUpdate("membershipPurchaseHistory", restartId, {
2591
2755
  id: restartId,
2592
2756
  ...membershipScope(input),
2593
- membershipId: id6,
2757
+ membershipId: id7,
2594
2758
  providerSubscriptionId: input.previousSubscriptionId,
2595
2759
  memberSnapshot: previous,
2596
2760
  bindingSnapshot: binding2,
2597
2761
  archivedAt: now
2598
2762
  }),
2599
- membershipUpdate("networkMembership", id6, { status: "restarting", restartId, revision: Number(previous.revision) + 1, updatedAt: now })
2763
+ membershipUpdate("networkMembership", id7, { status: "restarting", restartId, revision: Number(previous.revision) + 1, updatedAt: now })
2600
2764
  ], { mutationId: `membership-restart-reserve:${restartId}`, guards: [
2601
2765
  ...seats.guards,
2602
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: previous.revision, status: previous.status } },
2766
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: previous.revision, status: previous.status } },
2603
2767
  { ns: "membershipPurchaseHistory", id: restartId, exists: false },
2604
2768
  { ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding2.offerDigest } },
2605
2769
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
2606
2770
  ] });
2607
2771
  } else if (previous.restartId !== restartId) membershipFail("membership_restart_conflict");
2608
- const reserved = await membershipLoad(db, "networkMembership", id6);
2772
+ const reserved = await membershipLoad(db, "networkMembership", id7);
2609
2773
  requireMembershipScope(reserved, input);
2610
2774
  if (reserved.status !== "restarting" || reserved.restartId !== restartId) membershipFail("membership_restart_conflict");
2611
2775
  await stopPreviousSubscription(reserved, input, stripeSecret);
2612
2776
  const binding = await membershipLoad(db, "membershipOfferCheckout", bindingId);
2613
2777
  requireMembershipScope(binding, input);
2614
2778
  await db.transact([
2615
- membershipUpdate("membershipRestart", id6, { id: id6, ...membershipScope(input), ...receipt, previousSubscriptionId: input.previousSubscriptionId, createdAt: now }),
2779
+ membershipUpdate("membershipRestart", id7, { id: id7, ...membershipScope(input), ...receipt, previousSubscriptionId: input.previousSubscriptionId, createdAt: now }),
2616
2780
  { t: "delete", ns: "membershipOfferCheckout", id: bindingId }
2617
2781
  ], { mutationId: `membership-restart-ready:${restartId}`, guards: [
2618
- { ns: "membershipRestart", id: id6, exists: Boolean(existing), ...existing ? { equals: { restartId: existing.restartId, status: existing.status } } : {} },
2619
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: reserved.revision, status: "restarting", restartId } },
2782
+ { ns: "membershipRestart", id: id7, exists: Boolean(existing), ...existing ? { equals: { restartId: existing.restartId, status: existing.status } } : {} },
2783
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: reserved.revision, status: "restarting", restartId } },
2620
2784
  { ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding.offerDigest } }
2621
2785
  ] });
2622
2786
  return receipt;
@@ -2626,15 +2790,15 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
2626
2790
  async function readMembershipAccess(db, input, now) {
2627
2791
  membershipClock(now);
2628
2792
  await loadMembershipAuthorityState(db, input);
2629
- const id6 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id6);
2630
- if (!member) return { membershipId: id6, allowed: false, status: "unpaid", accessEndsAt: 0, revision: 0 };
2793
+ const id7 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id7);
2794
+ if (!member) return { membershipId: id7, allowed: false, status: "unpaid", accessEndsAt: 0, revision: 0 };
2631
2795
  requireMembershipScope(member, input);
2632
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id6}:primary`);
2796
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id7}:primary`);
2633
2797
  requireMembershipScope(entitlement, input);
2634
2798
  const endsAt = member.status === "payment_grace" ? Number(member.graceEndsAt) : Number(member.termEndsAt);
2635
- 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;
2799
+ 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;
2636
2800
  return {
2637
- membershipId: id6,
2801
+ membershipId: id7,
2638
2802
  allowed: allowed2,
2639
2803
  status: String(member.status),
2640
2804
  accessEndsAt: Number.isSafeInteger(endsAt) ? endsAt : 0,
@@ -2752,7 +2916,7 @@ async function refundChapterNetworkMembership(db, env, ctx, app) {
2752
2916
  const target = await membershipTargetForIntent(intent, app, env, ctx);
2753
2917
  const receipt = await ctx.membershipAuthority.refundMembership(target, env);
2754
2918
  const offer = intent.membershipOffer;
2755
- if (receipt.membershipId !== await networkMembershipId(target) || receipt.status !== "refunded" || receipt.subscriptionCanceled !== true || receipt.refundedCents !== offer.contract?.dueTodayCents) throw new Error("membership refund unconfirmed");
2919
+ 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");
2756
2920
  return receipt;
2757
2921
  }
2758
2922
  async function renewChapterNetworkMembership(db, env, ctx, app, eventId) {
@@ -2803,14 +2967,14 @@ function facts(snapshots) {
2803
2967
  ["payment_intent.updated", snapshots.payments?.objects ?? []]
2804
2968
  ];
2805
2969
  return groups.flatMap(([eventType, objects]) => objects.map((object2) => {
2806
- const id6 = typeof object2.id === "string" ? object2.id : "invalid";
2970
+ const id7 = typeof object2.id === "string" ? object2.id : "invalid";
2807
2971
  return normalizeNetworkCommercialStripeEvent({
2808
- id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id6}`,
2972
+ id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id7}`,
2809
2973
  type: eventType,
2810
2974
  created: typeof object2.created === "number" ? object2.created : 0,
2811
2975
  livemode: object2.livemode === true,
2812
2976
  data: { object: object2 }
2813
- }, `stripe-snapshot:${eventType}:${id6}`);
2977
+ }, `stripe-snapshot:${eventType}:${id7}`);
2814
2978
  }));
2815
2979
  }
2816
2980
  var selected = (row2) => ({
@@ -2885,9 +3049,9 @@ async function chapterBillingSubscriptions(secretKey, chapterId, env) {
2885
3049
  // src/approve-application.ts
2886
3050
  async function approveApplication(input) {
2887
3051
  const { db, env, ctx } = input;
2888
- const id6 = String(input.app.id ?? "");
2889
- if (!id6) return { ok: false, status: 404, error: "not found" };
2890
- const app = (await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } })).applications?.[0];
3052
+ const id7 = String(input.app.id ?? "");
3053
+ if (!id7) return { ok: false, status: 404, error: "not found" };
3054
+ const app = (await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } })).applications?.[0];
2891
3055
  if (!app || !chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME)) {
2892
3056
  return { ok: false, status: 404, error: "not found" };
2893
3057
  }
@@ -2921,10 +3085,10 @@ async function approveApplication(input) {
2921
3085
  try {
2922
3086
  if (!duplicate) {
2923
3087
  const write = await db.transact(
2924
- [{ t: "update", ns: "applications", id: id6, attrs: { status: "approved", approvalEffectsPending: true, approvalEffectsOrigin: input.origin } }],
2925
- { mutationId: `approve:${id6}`, guards: [{
3088
+ [{ t: "update", ns: "applications", id: id7, attrs: { status: "approved", approvalEffectsPending: true, approvalEffectsOrigin: input.origin } }],
3089
+ { mutationId: `approve:${id7}`, guards: [{
2926
3090
  ns: "applications",
2927
- id: id6,
3091
+ id: id7,
2928
3092
  exists: true,
2929
3093
  equals: Object.fromEntries(guardFields.filter((key3) => app[key3] !== void 0).map((key3) => [key3, app[key3]])),
2930
3094
  absent: guardFields.filter((key3) => app[key3] === void 0)
@@ -2936,7 +3100,7 @@ async function approveApplication(input) {
2936
3100
  if (error.code !== "transact_guard_failed") throw error;
2937
3101
  return { ok: false, status: 409, error: "application changed; reload before approving" };
2938
3102
  }
2939
- const current = (await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } })).applications?.[0];
3103
+ const current = (await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } })).applications?.[0];
2940
3104
  if (!current || current.status !== "approved" || current.canceled === true || guardFields.filter((key3) => key3 !== "status").some((key3) => current[key3] !== app[key3])) {
2941
3105
  return { ok: false, status: 409, error: "application changed; approval effects were not applied" };
2942
3106
  }
@@ -2946,7 +3110,7 @@ async function approveApplication(input) {
2946
3110
  } catch {
2947
3111
  return { ok: false, status: 503, error: "network membership approval reconciliation pending" };
2948
3112
  }
2949
- const afterAuthority = (await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } })).applications?.[0];
3113
+ const afterAuthority = (await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } })).applications?.[0];
2950
3114
  if (!afterAuthority || afterAuthority.status !== "approved" || afterAuthority.canceled === true || guardFields.filter((key3) => key3 !== "status").some((key3) => afterAuthority[key3] !== app[key3])) {
2951
3115
  return { ok: false, status: 409, error: "application changed during network approval" };
2952
3116
  }
@@ -2959,7 +3123,7 @@ async function approveApplication(input) {
2959
3123
  if (!userId && typeof app.email === "string") {
2960
3124
  const found = await clerkGetUserByEmail(secret, app.email, fetch, clerkRoleMetadata(ctx.chapter.auth));
2961
3125
  userId = found?.id ?? null;
2962
- if (userId) await db.transact([{ t: "update", ns: "applications", id: id6, attrs: { clerkUserId: userId } }]);
3126
+ if (userId) await db.transact([{ t: "update", ns: "applications", id: id7, attrs: { clerkUserId: userId } }]);
2963
3127
  }
2964
3128
  if (userId) {
2965
3129
  const user = await clerkGetUser(secret, userId, fetch, clerkRoleMetadata(ctx.chapter.auth));
@@ -2981,8 +3145,8 @@ async function approveApplication(input) {
2981
3145
  template: send,
2982
3146
  to: app.email,
2983
3147
  vars: { firstName: String(app.firstName ?? ""), membersUrl: `${input.origin}/members/` },
2984
- applicationId: id6,
2985
- dedupeKey: `approve:${id6}`
3148
+ applicationId: id7,
3149
+ dedupeKey: `approve:${id7}`
2986
3150
  }
2987
3151
  );
2988
3152
  emailLogged = result.sent;
@@ -2990,10 +3154,10 @@ async function approveApplication(input) {
2990
3154
  return { ok: false, status: 503, error: "approval notice reconciliation pending", rolePromoted, emailLogged };
2991
3155
  }
2992
3156
  }
2993
- await db.transact([{ t: "update", ns: "applications", id: id6, attrs: { approvalEffectsPending: false } }], {
3157
+ await db.transact([{ t: "update", ns: "applications", id: id7, attrs: { approvalEffectsPending: false } }], {
2994
3158
  guards: [{
2995
3159
  ns: "applications",
2996
- id: id6,
3160
+ id: id7,
2997
3161
  exists: true,
2998
3162
  equals: { status: "approved", ...app.canceled !== void 0 ? { canceled: app.canceled } : {} },
2999
3163
  ...app.canceled === void 0 ? { absent: ["canceled"] } : {}
@@ -3215,10 +3379,10 @@ async function redeemGiftClaim(db, claim, beneficiary, now) {
3215
3379
  tierId: target.tierId,
3216
3380
  ...typeof existing?.clerkUserId === "string" ? { clerkUserId: existing.clerkUserId } : {}
3217
3381
  };
3218
- const id6 = existing ? String(existing.id) : `gift-application:${String(claim.id)}`;
3382
+ const id7 = existing ? String(existing.id) : `gift-application:${String(claim.id)}`;
3219
3383
  const status2 = existing && existing.status !== "submitted" ? String(existing.status) : "paid_pending_vetting";
3220
- ops.push({ t: "update", ns: "applications", id: id6, attrs: {
3221
- id: id6,
3384
+ ops.push({ t: "update", ns: "applications", id: id7, attrs: {
3385
+ id: id7,
3222
3386
  groupId: chapterId,
3223
3387
  email: beneficiary.email,
3224
3388
  emailNormalized,
@@ -3229,12 +3393,12 @@ async function redeemGiftClaim(db, claim, beneficiary, now) {
3229
3393
  clerkUserId: beneficiary.userId,
3230
3394
  ...existing ? {} : { createdAt: now }
3231
3395
  } });
3232
- result = { kind: "application", id: id6 };
3396
+ result = { kind: "application", id: id7 };
3233
3397
  } else {
3234
3398
  if (beneficiary.applicationId) return null;
3235
- const id6 = `gift-entitlement:${String(claim.id)}`;
3236
- ops.push({ t: "update", ns: "giftEntitlements", id: id6, attrs: {
3237
- id: id6,
3399
+ const id7 = `gift-entitlement:${String(claim.id)}`;
3400
+ ops.push({ t: "update", ns: "giftEntitlements", id: id7, attrs: {
3401
+ id: id7,
3238
3402
  claimId: String(claim.id),
3239
3403
  orderId: String(claim.orderId),
3240
3404
  chapterId,
@@ -3244,7 +3408,7 @@ async function redeemGiftClaim(db, claim, beneficiary, now) {
3244
3408
  active: true,
3245
3409
  createdAt: now
3246
3410
  } });
3247
- result = { kind: "entitlement", id: id6 };
3411
+ result = { kind: "entitlement", id: id7 };
3248
3412
  }
3249
3413
  ops.unshift({ t: "update", ns: "giftClaims", id: String(claim.id), attrs: {
3250
3414
  status: "redeemed",
@@ -3418,9 +3582,9 @@ async function storeStripeReceipt(db, input) {
3418
3582
  return "stored";
3419
3583
  }
3420
3584
  if (existing) return existing.digest === input.digest || existing.digest === input.legacyDigest ? "replayed" : "collision";
3421
- const id6 = `stripe-event:${input.eventId}`;
3422
- const write = await db.transact([{ t: "update", ns: "stripeEventReceipts", id: id6, attrs: {
3423
- id: id6,
3585
+ const id7 = `stripe-event:${input.eventId}`;
3586
+ const write = await db.transact([{ t: "update", ns: "stripeEventReceipts", id: id7, attrs: {
3587
+ id: id7,
3424
3588
  eventId: input.eventId,
3425
3589
  digest: input.digest,
3426
3590
  disposition: input.disposition,
@@ -3652,9 +3816,9 @@ var EVENT_ID = /^evt_[A-Za-z0-9_]{1,150}$/;
3652
3816
  var fail3 = (message, code) => Object.assign(new Error(message), { code });
3653
3817
  async function resolveNetworkCommercialCollision(db, sources, input, now = Date.now(), alerts) {
3654
3818
  if (!EVENT_ID.test(input.eventId)) throw fail3("invalid event reference", "invalid_event");
3655
- const id6 = exceptionId(input.eventId, "collision"), reviewId = `resolve:${input.mutationId}`;
3819
+ const id7 = exceptionId(input.eventId, "collision"), reviewId = `resolve:${input.mutationId}`;
3656
3820
  const found = await db.query({
3657
- networkCommercialExceptions: { $: { where: { id: id6 }, limit: 1 } },
3821
+ networkCommercialExceptions: { $: { where: { id: id7 }, limit: 1 } },
3658
3822
  networkCommercialEvents: { $: { where: { eventId: input.eventId }, limit: 1 } }
3659
3823
  });
3660
3824
  const exception = found.networkCommercialExceptions?.[0], stored = found.networkCommercialEvents?.[0];
@@ -3677,7 +3841,7 @@ async function resolveNetworkCommercialCollision(db, sources, input, now = Date.
3677
3841
  action: "resolve",
3678
3842
  reason: input.reason
3679
3843
  } },
3680
- { t: "update", ns: "networkCommercialExceptions", id: id6, attrs: {
3844
+ { t: "update", ns: "networkCommercialExceptions", id: id7, attrs: {
3681
3845
  status: "resolved",
3682
3846
  resolvedDigest: stored.digest,
3683
3847
  ...dismissed ? { reviewedDigest: dismissed } : {},
@@ -3686,7 +3850,7 @@ async function resolveNetworkCommercialCollision(db, sources, input, now = Date.
3686
3850
  updatedAt: now
3687
3851
  } }
3688
3852
  ], { mutationId: input.mutationId, guards: [
3689
- { ns: "networkCommercialExceptions", id: id6, exists: true, equals: { status: "active", updatedAt: exception.updatedAt } }
3853
+ { ns: "networkCommercialExceptions", id: id7, exists: true, equals: { status: "active", updatedAt: exception.updatedAt } }
3690
3854
  ] });
3691
3855
  if (write.duplicate) throw fail3("mutationId was already used for another change", "mutation_reused");
3692
3856
  const projection = await rebuildNetworkCommercialProjection(db, sources, `${input.mutationId}:rebuild`, now, alerts);
@@ -3801,10 +3965,10 @@ var handlePaymentRecovery = async (req, url, env, ctx) => {
3801
3965
  const provider = await readStripeRecoveryEvent(db, eventId);
3802
3966
  const event2 = normalizeWebhookEvent(provider.event, { requireProviderPayment: Boolean(ctx.membershipAuthority) });
3803
3967
  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);
3804
- const id6 = crypto.randomUUID();
3968
+ const id7 = crypto.randomUUID();
3805
3969
  await db.transact([
3806
- { t: "update", ns: "paymentRecoveryReviews", id: id6, attrs: {
3807
- id: id6,
3970
+ { t: "update", ns: "paymentRecoveryReviews", id: id7, attrs: {
3971
+ id: id7,
3808
3972
  eventId,
3809
3973
  actorUserId: user.userId,
3810
3974
  reviewedAt: Date.now(),
@@ -3813,8 +3977,8 @@ var handlePaymentRecovery = async (req, url, env, ctx) => {
3813
3977
  scope: `${target.appId}:${target.environment}:${target.runtime}`,
3814
3978
  previousState: row2
3815
3979
  } },
3816
- { t: "update", ns: "stripeEventReceipts", id: String(row2.id), attrs: { retryReady: true, reviewedDigest: provider.digest, reviewId: id6 } }
3817
- ], { mutationId: `payment-recovery:${id6}`, guards: [{
3980
+ { t: "update", ns: "stripeEventReceipts", id: String(row2.id), attrs: { retryReady: true, reviewedDigest: provider.digest, reviewId: id7 } }
3981
+ ], { mutationId: `payment-recovery:${id7}`, guards: [{
3818
3982
  ns: "stripeEventReceipts",
3819
3983
  id: String(row2.id),
3820
3984
  exists: true,
@@ -3883,43 +4047,121 @@ async function closeRecipientApplication(db, env, ctx, seatId) {
3883
4047
  }
3884
4048
 
3885
4049
  // src/membership-seat-refund-stripe.ts
4050
+ import { stripeRequest as stripeRequest5 } from "@odla-ai/stripe";
4051
+
4052
+ // src/membership-checkout-seat-refund.ts
3886
4053
  import { stripeRequest as stripeRequest4 } from "@odla-ai/stripe";
3887
4054
  var fail4 = (code = "named_seat_refund_mismatch") => {
3888
4055
  throw Object.assign(new Error(code), { code });
3889
4056
  };
4057
+ var id2 = (value) => typeof value === "string" ? value : value?.id ?? "";
4058
+ async function refundCheckoutNamedSeat(secretKey, seat) {
4059
+ const invoiceRef = seat.combinedInvoice;
4060
+ 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();
4061
+ const call = async (method, path, params, key3) => {
4062
+ const result = await stripeRequest4({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
4063
+ if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
4064
+ return result.body;
4065
+ };
4066
+ const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId };
4067
+ const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
4068
+ 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();
4069
+ const invoice = await call("GET", `/v1/invoices/${encodeURIComponent(invoiceRef.invoiceId)}`);
4070
+ 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();
4071
+ let chargeId = id2(invoice.charge), paymentId = id2(invoice.payment_intent);
4072
+ if (!chargeId && !paymentId) {
4073
+ const payments = await call("GET", "/v1/invoice_payments", { invoice: invoice.id, status: "paid", limit: 100 });
4074
+ if (!Array.isArray(payments.data) || payments.data.length !== 1 || payments.has_more !== false) fail4();
4075
+ const payment = payments.data[0];
4076
+ if (id2(payment.invoice) !== invoice.id || payment.status !== "paid" || payment.payment?.type !== "payment_intent") fail4();
4077
+ paymentId = id2(payment.payment.payment_intent);
4078
+ }
4079
+ if (!chargeId) {
4080
+ if (!/^pi_[A-Za-z0-9_]+$/.test(paymentId)) fail4();
4081
+ const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(paymentId)}`);
4082
+ 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();
4083
+ chargeId = id2(payment.latest_charge);
4084
+ }
4085
+ if (!/^ch_[A-Za-z0-9_]+$/.test(chargeId)) fail4();
4086
+ const charge = await call("GET", `/v1/charges/${encodeURIComponent(chargeId)}`);
4087
+ 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();
4088
+ const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
4089
+ if (items.length > 1) fail4();
4090
+ if (items.length && sub.status !== "canceled") {
4091
+ const item = items[0];
4092
+ if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.quantity !== 1 || item.price?.id !== seat.priceId) fail4();
4093
+ const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
4094
+ if (removed.id !== item.id || removed.deleted !== true) fail4("named_seat_renewal_removal_unconfirmed");
4095
+ }
4096
+ if (charge.amount_refunded === charge.amount && charge.refunded === true) return { refundedCents: seat.amountCents, refundIds: [] };
4097
+ const list = await call("GET", "/v1/refunds", { charge: chargeId, limit: 100 });
4098
+ if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail4("named_seat_refund_history_incomplete");
4099
+ let refundedCents = 0;
4100
+ const refundIds = [];
4101
+ for (const refund of list.data) {
4102
+ 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();
4103
+ if (["pending", "requires_action"].includes(refund.status)) fail4("named_seat_refund_pending");
4104
+ if (refund.status === "succeeded") {
4105
+ if (refund.metadata?.seatId !== seat.id || refund.metadata?.membershipId !== seat.primaryMembershipId) fail4("named_seat_refund_allocation_ambiguous");
4106
+ refundedCents += refund.amount;
4107
+ refundIds.push(refund.id);
4108
+ }
4109
+ }
4110
+ if (refundedCents !== charge.amount_refunded || refundedCents > seat.amountCents) fail4();
4111
+ if (refundedCents < seat.amountCents) {
4112
+ const remaining = seat.amountCents - refundedCents;
4113
+ const refund = await call("POST", "/v1/refunds", {
4114
+ charge: chargeId,
4115
+ amount: remaining,
4116
+ reason: "requested_by_customer",
4117
+ metadata: { ...metadata, membershipId: seat.primaryMembershipId, seatId: seat.id }
4118
+ }, `combined-seat-refund:${seat.id}:${remaining}`);
4119
+ if (id2(refund.charge) !== chargeId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail4();
4120
+ if (refund.status !== "succeeded") fail4("named_seat_refund_pending");
4121
+ refundedCents += remaining;
4122
+ refundIds.push(refund.id);
4123
+ }
4124
+ return { refundedCents, refundIds };
4125
+ }
4126
+
4127
+ // src/membership-seat-refund-stripe.ts
4128
+ var fail5 = (code = "named_seat_refund_mismatch") => {
4129
+ throw Object.assign(new Error(code), { code });
4130
+ };
3890
4131
  async function refundNamedSeatStripePayment(secretKey, seat) {
3891
- if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail4();
4132
+ if (seat.combinedInvoice) return refundCheckoutNamedSeat(secretKey, seat);
4133
+ if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail5();
3892
4134
  const call = async (method, path, params, idempotencyKey) => {
3893
- const result = await stripeRequest4({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
3894
- if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
4135
+ const result = await stripeRequest5({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
4136
+ if (!result.ok || !result.body) return fail5("named_seat_refund_provider_unavailable");
3895
4137
  return result.body;
3896
4138
  };
3897
4139
  const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
3898
4140
  const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`);
3899
- 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();
4141
+ 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();
3900
4142
  const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
3901
- 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();
4143
+ 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();
3902
4144
  const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
3903
- if (items.length > 1) fail4();
4145
+ if (items.length > 1) fail5();
3904
4146
  if (items.length && sub.status !== "canceled") {
3905
4147
  const item = items[0];
3906
- if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail4();
4148
+ if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail5();
3907
4149
  const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
3908
- if (removed.id !== item.id || removed.deleted !== true) fail4("named_seat_renewal_removal_unconfirmed");
4150
+ if (removed.id !== item.id || removed.deleted !== true) fail5("named_seat_renewal_removal_unconfirmed");
3909
4151
  }
3910
4152
  const list = await call("GET", "/v1/refunds", { payment_intent: seat.providerPaymentIntentId, limit: 100 });
3911
- if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail4("named_seat_refund_history_incomplete");
4153
+ if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail5("named_seat_refund_history_incomplete");
3912
4154
  let refundedCents = 0, pendingCents = 0;
3913
4155
  const refundIds = [];
3914
4156
  for (const refund of list.data) {
3915
- 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();
4157
+ 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();
3916
4158
  if (refund.status === "succeeded") {
3917
4159
  refundedCents += refund.amount;
3918
4160
  refundIds.push(refund.id);
3919
4161
  } else if (refund.status === "pending" || refund.status === "requires_action") pendingCents += refund.amount;
3920
4162
  }
3921
- if (refundedCents + pendingCents > seat.amountCents) fail4();
3922
- if (pendingCents) fail4("named_seat_refund_pending");
4163
+ if (refundedCents + pendingCents > seat.amountCents) fail5();
4164
+ if (pendingCents) fail5("named_seat_refund_pending");
3923
4165
  if (refundedCents < seat.amountCents) {
3924
4166
  const remaining = seat.amountCents - refundedCents;
3925
4167
  const refund = await call("POST", "/v1/refunds", {
@@ -3928,8 +4170,8 @@ async function refundNamedSeatStripePayment(secretKey, seat) {
3928
4170
  reason: "requested_by_customer",
3929
4171
  metadata
3930
4172
  }, `named-seat-refund:${seat.id}:${remaining}`);
3931
- if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail4();
3932
- if (refund.status !== "succeeded") fail4("named_seat_refund_pending");
4173
+ if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail5();
4174
+ if (refund.status !== "succeeded") fail5("named_seat_refund_pending");
3933
4175
  refundedCents += remaining;
3934
4176
  refundIds.push(refund.id);
3935
4177
  }
@@ -3951,8 +4193,8 @@ async function reserveNamedMembershipSeat(db, input, now) {
3951
4193
  const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
3952
4194
  requireMembershipScope(member, input);
3953
4195
  if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
3954
- const id6 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
3955
- let seat = await membershipLoad(db, "networkSeat", id6);
4196
+ const id7 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
4197
+ let seat = await membershipLoad(db, "networkSeat", id7);
3956
4198
  if (!seat) {
3957
4199
  const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
3958
4200
  const purchaseEndsAt = seatPurchaseDeadline(offering, member);
@@ -3960,7 +4202,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
3960
4202
  membershipFail("named_seat_purchase_unavailable");
3961
4203
  }
3962
4204
  const attrs = {
3963
- id: id6,
4205
+ id: id7,
3964
4206
  ...membershipScope(input),
3965
4207
  primaryMembershipId: memberId,
3966
4208
  recipientName: name,
@@ -3985,20 +4227,20 @@ async function reserveNamedMembershipSeat(db, input, now) {
3985
4227
  };
3986
4228
  try {
3987
4229
  await db.transact([
3988
- membershipUpdate("networkSeat", id6, attrs),
4230
+ membershipUpdate("networkSeat", id7, attrs),
3989
4231
  membershipUpdate("networkSeatRecipient", claimId, {
3990
4232
  id: claimId,
3991
4233
  appId: input.appId,
3992
4234
  chapterId: input.chapterId,
3993
4235
  environment: input.environment,
3994
4236
  runtime: input.runtime,
3995
- seatId: id6,
4237
+ seatId: id7,
3996
4238
  recipientEmailNormalized: email,
3997
4239
  createdAt: now
3998
4240
  }),
3999
- membershipUpdate("networkMembership", memberId, { namedSeatId: id6, revision: Number(member.revision) + 1, updatedAt: now })
4000
- ], { mutationId: `named-seat-reserve:${id6}`, guards: [
4001
- { ns: "networkSeat", id: id6, exists: false },
4241
+ membershipUpdate("networkMembership", memberId, { namedSeatId: id7, revision: Number(member.revision) + 1, updatedAt: now })
4242
+ ], { mutationId: `named-seat-reserve:${id7}`, guards: [
4243
+ { ns: "networkSeat", id: id7, exists: false },
4002
4244
  { ns: "networkSeatRecipient", id: claimId, exists: false },
4003
4245
  { ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
4004
4246
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
@@ -4006,7 +4248,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4006
4248
  } catch (error) {
4007
4249
  if (error.code !== "transact_guard_failed") throw error;
4008
4250
  }
4009
- seat = await membershipLoad(db, "networkSeat", id6);
4251
+ seat = await membershipLoad(db, "networkSeat", id7);
4010
4252
  if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
4011
4253
  }
4012
4254
  requireMembershipScope(seat, input);
@@ -4016,7 +4258,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4016
4258
 
4017
4259
  // src/membership-seat-included.ts
4018
4260
  var namedSeatIncluded = (seat) => Number(seat.amountCents) === 0;
4019
- var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean(seat.providerPaymentIntentId && seat.providerSubscriptionItemId);
4261
+ var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean((seat.providerPaymentIntentId || seat.combinedInvoice) && seat.providerSubscriptionItemId);
4020
4262
  function namedSeatRecipientState(seat) {
4021
4263
  switch (String(seat.status)) {
4022
4264
  case "renewal_pending":
@@ -4120,7 +4362,7 @@ async function requestNamedSeatRefund(db, input, now) {
4120
4362
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
4121
4363
  requireMembershipScope(member, input);
4122
4364
  if (seat.status === "refunded" || seat.status === "refund_pending") return;
4123
- 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");
4365
+ 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");
4124
4366
  const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
4125
4367
  await db.transact([
4126
4368
  membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
@@ -4233,8 +4475,8 @@ async function lowerSeatPrice(db, scope2, now, limit = 100) {
4233
4475
  const receipt = { seatPriceCents: target, repriced: 0, skipped: [], more: rows5.length > limit };
4234
4476
  for (const member of rows5.slice(0, limit)) {
4235
4477
  if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) throw new Error("membership reprice scope mismatch");
4236
- const id6 = String(member.id), skip = (reason) => {
4237
- receipt.skipped.push({ membershipId: id6, reason });
4478
+ const id7 = String(member.id), skip = (reason) => {
4479
+ receipt.skipped.push({ membershipId: id7, reason });
4238
4480
  };
4239
4481
  const offering = membershipOfferingVersion(member.offeringSnapshot);
4240
4482
  if (offering.seatPriceCents <= target) {
@@ -4250,16 +4492,16 @@ async function lowerSeatPrice(db, scope2, now, limit = 100) {
4250
4492
  skip(`seat is ${String(seat.status)}`);
4251
4493
  continue;
4252
4494
  }
4253
- const ops = [membershipUpdate("networkMembership", id6, { offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(member.revision) + 1, updatedAt: now })];
4495
+ const ops = [membershipUpdate("networkMembership", id7, { offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(member.revision) + 1, updatedAt: now })];
4254
4496
  const guards = [
4255
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
4497
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
4256
4498
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative" } }
4257
4499
  ];
4258
4500
  if (seat) {
4259
4501
  ops.push(membershipUpdate("networkSeat", String(seat.id), { amountCents: target, offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(seat.revision) + 1, updatedAt: now }));
4260
4502
  guards.push({ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision, status: "payment_pending" } });
4261
4503
  }
4262
- await db.transact(ops, { mutationId: `seat-reprice:${id6}:${member.revision}`, guards });
4504
+ await db.transact(ops, { mutationId: `seat-reprice:${id7}:${member.revision}`, guards });
4263
4505
  receipt.repriced++;
4264
4506
  }
4265
4507
  return receipt;
@@ -4460,25 +4702,28 @@ var handleAdminMembershipPolicyVersions = async (req, url, env, ctx) => {
4460
4702
  };
4461
4703
 
4462
4704
  // src/membership-discount-reterm.ts
4463
- import { stripeRequest as stripeRequest6 } from "@odla-ai/stripe";
4705
+ import { stripeRequest as stripeRequest7 } from "@odla-ai/stripe";
4464
4706
 
4465
4707
  // src/subscription-provider-contract.ts
4466
- import { stripeRequest as stripeRequest5 } from "@odla-ai/stripe";
4708
+ import { stripeRequest as stripeRequest6 } from "@odla-ai/stripe";
4467
4709
  function subscriptionContractMismatch() {
4468
4710
  throw Object.assign(new Error("Stripe subscription does not match the checkout contract"), { code: "stripe_contract_mismatch" });
4469
4711
  }
4470
4712
  async function verifySubscriptionPrice(secretKey, input, resume = false) {
4471
4713
  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();
4472
- const result = await stripeRequest5({ secretKey }, "GET", `/v1/prices/${encodeURIComponent(input.priceId)}`);
4714
+ const result = await stripeRequest6({ secretKey }, "GET", `/v1/prices/${encodeURIComponent(input.priceId)}`);
4473
4715
  const price = result.body;
4474
4716
  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();
4475
4717
  return typeof price.product === "string" ? price.product : void 0;
4476
4718
  }
4477
4719
  function verifySubscriptionResult(sub, input, customerId, metadata) {
4478
4720
  const items = sub?.items;
4479
- 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();
4721
+ const seat = input.namedSeat?.amountCents ? input.namedSeat : void 0;
4722
+ const primary = items?.data?.find((item) => item.price?.id === input.priceId);
4723
+ const seatItem = seat && items?.data?.find((item) => item.price?.id === seat.priceId);
4724
+ 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();
4480
4725
  const invoice = sub.latest_invoice;
4481
- if (!invoice || typeof invoice !== "object" || invoice.customer !== customerId || invoice.currency !== input.currency || invoice.total !== subscriptionAmountDue(input) || invoice.billing_reason !== "subscription_create") subscriptionContractMismatch();
4726
+ 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();
4482
4727
  verifySubscriptionBillingResult(sub, input);
4483
4728
  }
4484
4729
 
@@ -4490,8 +4735,8 @@ var couponOf = (discount) => {
4490
4735
  const ref = discount?.source?.coupon ?? discount?.coupon;
4491
4736
  return typeof ref === "string" ? ref : typeof ref?.id === "string" ? ref.id : void 0;
4492
4737
  };
4493
- async function retermPolicy(db, scope2, id6) {
4494
- const row2 = await membershipLoad(db, "discountPolicyVersion", id6);
4738
+ async function retermPolicy(db, scope2, id7) {
4739
+ const row2 = await membershipLoad(db, "discountPolicyVersion", id7);
4495
4740
  if (!row2) membershipFail("membership_reterm_policy_unknown");
4496
4741
  let policy2;
4497
4742
  try {
@@ -4499,12 +4744,12 @@ async function retermPolicy(db, scope2, id6) {
4499
4744
  } catch {
4500
4745
  return membershipFail("membership_reterm_policy_ineligible");
4501
4746
  }
4502
- 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");
4747
+ 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");
4503
4748
  return policy2;
4504
4749
  }
4505
4750
  async function attachProviderDiscount(sk, scope2, member, policy2, couponId, productId) {
4506
4751
  const subId = String(member.providerSubscriptionId), path = `/v1/subscriptions/${encodeURIComponent(subId)}`;
4507
- const read2 = await stripeRequest6({ secretKey: sk }, "GET", path, { "expand[0]": "discounts" });
4752
+ const read2 = await stripeRequest7({ secretKey: sk }, "GET", path, { "expand[0]": "discounts" });
4508
4753
  const sub = read2.body, items = sub?.items?.data;
4509
4754
  const metadata = { ...stripePartitionMetadata(scope2), applicationId: String(member.applicationId) };
4510
4755
  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) {
@@ -4515,7 +4760,7 @@ async function attachProviderDiscount(sk, scope2, member, policy2, couponId, pro
4515
4760
  const present = discounts.map(couponOf);
4516
4761
  if (present.length === 1 && present[0] === couponId) return false;
4517
4762
  if (present.length > 0) throw Object.assign(new Error("provider subscription carries an unrelated discount"), { code: "provider_mismatch" });
4518
- const written = await stripeRequest6({ secretKey: sk }, "POST", path, {
4763
+ const written = await stripeRequest7({ secretKey: sk }, "POST", path, {
4519
4764
  "discounts[0][coupon]": couponId,
4520
4765
  "expand[0]": "discounts",
4521
4766
  "metadata[discountPolicyVersionId]": policy2.id
@@ -4531,7 +4776,7 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4531
4776
  const limit = input.limit ?? 100;
4532
4777
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 200) throw new TypeError("invalid reterm limit");
4533
4778
  if (typeof input.policyVersionId !== "string" || !SAFE.test(input.policyVersionId)) throw new TypeError("invalid policy version id");
4534
- 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");
4779
+ 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");
4535
4780
  const state = await loadMembershipAuthorityState(db, scope2);
4536
4781
  const policy2 = await retermPolicy(db, scope2, input.policyVersionId);
4537
4782
  const receipt = { policyVersionId: policy2.id, retermed: [], unchanged: [], skipped: [], more: false };
@@ -4541,10 +4786,10 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4541
4786
  let rows5;
4542
4787
  if (input.membershipIds) {
4543
4788
  rows5 = [];
4544
- for (const id6 of input.membershipIds) {
4545
- const member = await membershipLoad(db, "networkMembership", id6);
4546
- if (!member) skip(id6, "not_found");
4547
- else if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) skip(id6, "scope_mismatch");
4789
+ for (const id7 of input.membershipIds) {
4790
+ const member = await membershipLoad(db, "networkMembership", id7);
4791
+ if (!member) skip(id7, "not_found");
4792
+ else if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) skip(id7, "scope_mismatch");
4548
4793
  else rows5.push(member);
4549
4794
  }
4550
4795
  } else {
@@ -4554,32 +4799,32 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4554
4799
  rows5 = found.slice(0, limit);
4555
4800
  }
4556
4801
  for (const member of rows5) {
4557
- const id6 = String(member.id);
4802
+ const id7 = String(member.id);
4558
4803
  if (member.discountPolicyVersionId === policy2.id) {
4559
- receipt.unchanged.push(id6);
4804
+ receipt.unchanged.push(id7);
4560
4805
  continue;
4561
4806
  }
4562
4807
  if (!RETERMABLE.has(String(member.status))) {
4563
- skip(id6, "status");
4808
+ skip(id7, "status");
4564
4809
  continue;
4565
4810
  }
4566
4811
  if (member.founding !== true) {
4567
- skip(id6, "not_founding");
4812
+ skip(id7, "not_founding");
4568
4813
  continue;
4569
4814
  }
4570
4815
  if (member.cohort !== policy2.cohortLabel) {
4571
- skip(id6, "cohort_mismatch");
4816
+ skip(id7, "cohort_mismatch");
4572
4817
  continue;
4573
4818
  }
4574
4819
  const skuId = String(member.baseSkuId);
4575
4820
  if (!policy2.discountEligibleSkuIds.includes(skuId)) {
4576
- skip(id6, "sku_not_discounted");
4821
+ skip(id7, "sku_not_discounted");
4577
4822
  continue;
4578
4823
  }
4579
4824
  const base = Number(member.baseAmountCents), current = Number(member.renewalAmountCents);
4580
4825
  const renewal = Math.round(base * (1e4 - policy2.percentOffBps) / 1e4);
4581
4826
  if (!Number.isSafeInteger(base) || base <= 0 || !Number.isSafeInteger(current) || renewal > current) {
4582
- skip(id6, "would_not_widen");
4827
+ skip(id7, "would_not_widen");
4583
4828
  continue;
4584
4829
  }
4585
4830
  const couponId = await membershipDiscountCouponId({ ...scope2, skuId }, policy2.id);
@@ -4607,22 +4852,22 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4607
4852
  } catch (error) {
4608
4853
  const code = error.code;
4609
4854
  if (code === "provider_mismatch" || code === "stripe_contract_mismatch") {
4610
- skip(id6, "provider_mismatch");
4855
+ skip(id7, "provider_mismatch");
4611
4856
  continue;
4612
4857
  }
4613
4858
  throw error;
4614
4859
  }
4615
4860
  try {
4616
- await db.transact([membershipUpdate("networkMembership", id6, {
4861
+ await db.transact([membershipUpdate("networkMembership", id7, {
4617
4862
  discountPolicyVersionId: policy2.id,
4618
4863
  discountSnapshot: policy2,
4619
4864
  renewalAmountCents: renewal,
4620
4865
  revision: Number(member.revision) + 1,
4621
4866
  updatedAt: now
4622
4867
  })], {
4623
- mutationId: `membership-reterm:${id6}:${policy2.id}:${member.revision}`,
4868
+ mutationId: `membership-reterm:${id7}:${policy2.id}:${member.revision}`,
4624
4869
  guards: [
4625
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision, status: member.status } },
4870
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision, status: member.status } },
4626
4871
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative" } },
4627
4872
  { ns: "discountPolicyVersion", id: policy2.id, exists: true }
4628
4873
  ]
@@ -4632,7 +4877,7 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4632
4877
  return membershipFail("membership_reterm_retry_required");
4633
4878
  }
4634
4879
  receipt.retermed.push({
4635
- membershipId: id6,
4880
+ membershipId: id7,
4636
4881
  ...typeof member.discountPolicyVersionId === "string" ? { previousPolicyVersionId: member.discountPolicyVersionId } : {},
4637
4882
  previousRenewalAmountCents: current,
4638
4883
  renewalAmountCents: renewal,
@@ -4668,7 +4913,7 @@ var handleAdminMembershipReterm = async (req, url, env, ctx) => {
4668
4913
  return json({ error: BAD }, 400);
4669
4914
  }
4670
4915
  const appId = body.appId ?? body.chapterId, runtime = body.runtime ?? env.ODLA_RUNTIME;
4671
- 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)) {
4916
+ 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)) {
4672
4917
  return json({ error: BAD }, 400);
4673
4918
  }
4674
4919
  const secret = await getVaultSecret(db, operator.stripeSecretName ?? "stripe_secret_key");
@@ -4943,12 +5188,12 @@ var handleMembershipRestart = async (req, url, env, ctx) => {
4943
5188
  if (!app || app.clerkUserId !== user.userId || app.namedSeatId || app.giftClaimId) return json({ error: "membership is not eligible to restart" }, 409);
4944
5189
  if (app.membershipRestartId && app.status === "submitted") return json({ applicationId: app.id });
4945
5190
  try {
4946
- const id6 = String(app.id), intent = await membershipLoad(db, "subscriptionCheckoutIntents", id6);
5191
+ const id7 = String(app.id), intent = await membershipLoad(db, "subscriptionCheckoutIntents", id7);
4947
5192
  if (!intent || !app.stripeSubscriptionId || intent.providerSubscriptionId !== app.stripeSubscriptionId) return json({ error: "membership history needs reconciliation" }, 409);
4948
5193
  const target = await membershipTargetForIntent(intent, app, env, ctx);
4949
5194
  const restart = await ctx.membershipAuthority.restart({ ...target, previousSubscriptionId: String(app.stripeSubscriptionId) }, env);
4950
- 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");
4951
- const now = Date.now(), quote = await membershipLoad(db, "membershipQuoteProjections", id6);
5195
+ 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");
5196
+ const now = Date.now(), quote = await membershipLoad(db, "membershipQuoteProjections", id7);
4952
5197
  const attrs = {
4953
5198
  status: "submitted",
4954
5199
  membershipRestartId: restart.restartId,
@@ -4968,8 +5213,8 @@ var handleMembershipRestart = async (req, url, env, ctx) => {
4968
5213
  quoteSnapshot: quote ?? {},
4969
5214
  archivedAt: now
4970
5215
  } },
4971
- { t: "delete", ns: "subscriptionCheckoutIntents", id: id6 },
4972
- { t: "retract", ns: "applications", id: id6, attrs: [
5216
+ { t: "delete", ns: "subscriptionCheckoutIntents", id: id7 },
5217
+ { t: "retract", ns: "applications", id: id7, attrs: [
4973
5218
  "stripeSubscriptionId",
4974
5219
  "canceled",
4975
5220
  "renewalAt",
@@ -4980,28 +5225,28 @@ var handleMembershipRestart = async (req, url, env, ctx) => {
4980
5225
  "approvalEffectsOrigin",
4981
5226
  "approvalEffectsLastAttemptAt"
4982
5227
  ] },
4983
- { t: "update", ns: "applications", id: id6, attrs },
4984
- ...quote ? [{ t: "update", ns: "membershipQuoteProjections", id: id6, attrs: { status: "restart", updatedAt: now } }] : []
5228
+ { t: "update", ns: "applications", id: id7, attrs },
5229
+ ...quote ? [{ t: "update", ns: "membershipQuoteProjections", id: id7, attrs: { status: "restart", updatedAt: now } }] : []
4985
5230
  ];
4986
5231
  try {
4987
5232
  await db.transact(ops, { mutationId: `membership-restart-application:${restart.restartId}`, guards: [
4988
- { ns: "applications", id: id6, exists: true, equals: {
5233
+ { ns: "applications", id: id7, exists: true, equals: {
4989
5234
  clerkUserId: user.userId,
4990
5235
  groupId: ctx.chapter.id,
4991
5236
  status: app.status,
4992
5237
  stripeSubscriptionId: app.stripeSubscriptionId
4993
5238
  } },
4994
- { ns: "subscriptionCheckoutIntents", id: id6, exists: true, equals: { providerSubscriptionId: app.stripeSubscriptionId } },
5239
+ { ns: "subscriptionCheckoutIntents", id: id7, exists: true, equals: { providerSubscriptionId: app.stripeSubscriptionId } },
4995
5240
  { ns: "membershipCheckoutHistory", id: restart.restartId, exists: false },
4996
- { ns: "membershipQuoteProjections", id: id6, exists: Boolean(quote), ...quote ? { equals: { revision: quote.revision, status: quote.status } } : {} }
5241
+ { ns: "membershipQuoteProjections", id: id7, exists: Boolean(quote), ...quote ? { equals: { revision: quote.revision, status: quote.status } } : {} }
4997
5242
  ] });
4998
5243
  } catch (error) {
4999
5244
  if (error.code !== "transact_guard_failed") throw error;
5000
5245
  }
5001
- const saved = await membershipLoad(db, "applications", id6);
5246
+ const saved = await membershipLoad(db, "applications", id7);
5002
5247
  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");
5003
5248
  await syncApplicationToCrm(crmDeps(db, ctx), { app: saved, stage: String(saved.status) }).catch(() => void 0);
5004
- return json({ applicationId: id6 });
5249
+ return json({ applicationId: id7 });
5005
5250
  } catch {
5006
5251
  return json({ error: "We could not prepare your new subscription. Your account is saved. Please retry." }, 503);
5007
5252
  }
@@ -5132,8 +5377,8 @@ async function submitApplication(db, chapter, fields, opts) {
5132
5377
  if (app.requireDisclaimerAck && !acked) {
5133
5378
  return { ok: false, error: "disclaimerAck is required" };
5134
5379
  }
5135
- const id6 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
5136
- const row2 = { id: id6, status: chapter.pipeline.initial, createdAt: opts.now };
5380
+ const id7 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
5381
+ const row2 = { id: id7, status: chapter.pipeline.initial, createdAt: opts.now };
5137
5382
  for (const f of [...app.required, ...app.optional]) {
5138
5383
  if (fieldStates[f]?.visible === false) continue;
5139
5384
  if (typeof fields[f] === "string") row2[f] = fields[f].trim();
@@ -5167,12 +5412,12 @@ async function submitApplication(db, chapter, fields, opts) {
5167
5412
  }
5168
5413
  if (acked) row2.disclaimerAckAt = opts.now;
5169
5414
  const ops = [
5170
- { t: "update", ns: "applications", id: id6, attrs: row2 }
5415
+ { t: "update", ns: "applications", id: id7, attrs: row2 }
5171
5416
  ];
5172
5417
  if (opts.admissionGrant?.decision === "waive_interview") {
5173
5418
  ops.push({ t: "update", ns: "admissionGrants", id: opts.admissionGrant.id, attrs: {
5174
5419
  consumedAt: opts.now,
5175
- consumedApplicationId: id6
5420
+ consumedApplicationId: id7
5176
5421
  } });
5177
5422
  }
5178
5423
  try {
@@ -5180,7 +5425,7 @@ async function submitApplication(db, chapter, fields, opts) {
5180
5425
  ops,
5181
5426
  opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
5182
5427
  );
5183
- return { ok: true, id: id6, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
5428
+ return { ok: true, id: id7, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
5184
5429
  } catch (error) {
5185
5430
  if (error.code === "unique_violation") return {
5186
5431
  ok: false,
@@ -5386,14 +5631,14 @@ function validateAdmissionGrantDocument(input) {
5386
5631
  const seenEmails = /* @__PURE__ */ new Set();
5387
5632
  for (const [index2, raw2] of rawGrants.slice(0, MAX_GRANTS).entries()) {
5388
5633
  const grant = raw2 && typeof raw2 === "object" ? raw2 : {};
5389
- const id6 = typeof grant.id === "string" ? grant.id : "";
5634
+ const id7 = typeof grant.id === "string" ? grant.id : "";
5390
5635
  const email = typeof grant.email === "string" ? grant.email.trim() : "";
5391
5636
  const normalized = normalizeAdmissionEmail(email);
5392
5637
  const decision = grant.decision;
5393
5638
  const note = typeof grant.note === "string" ? grant.note.trim() : void 0;
5394
5639
  const expiresAt = grant.expiresAt === void 0 ? void 0 : Number(grant.expiresAt);
5395
- if (!ID2.test(id6)) errors.push(`document.grants[${index2}].id must be an identifier`);
5396
- if (seenIds.has(id6)) errors.push(`document.grants repeats id "${id6}"`);
5640
+ if (!ID2.test(id7)) errors.push(`document.grants[${index2}].id must be an identifier`);
5641
+ if (seenIds.has(id7)) errors.push(`document.grants repeats id "${id7}"`);
5397
5642
  if (!isAdmissionEmail(normalized)) errors.push(`document.grants[${index2}].email must be valid`);
5398
5643
  if (seenEmails.has(normalized)) errors.push(`document.grants repeats email "${normalized}"`);
5399
5644
  if (decision !== "waive_interview" && decision !== "require_interview") {
@@ -5403,10 +5648,10 @@ function validateAdmissionGrantDocument(input) {
5403
5648
  if (expiresAt !== void 0 && (!Number.isFinite(expiresAt) || expiresAt <= 0)) {
5404
5649
  errors.push(`document.grants[${index2}].expiresAt must be epoch milliseconds`);
5405
5650
  }
5406
- seenIds.add(id6);
5651
+ seenIds.add(id7);
5407
5652
  seenEmails.add(normalized);
5408
5653
  grants.push({
5409
- id: id6,
5654
+ id: id7,
5410
5655
  email,
5411
5656
  decision,
5412
5657
  ...note ? { note } : {},
@@ -5478,8 +5723,8 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
5478
5723
  const incomingIds = new Set(document.grants.map((grant) => `admission:parent:${document.source.appId}:${grant.id}`));
5479
5724
  const ops = [];
5480
5725
  for (const row2 of current) {
5481
- const id6 = String(row2.id ?? "");
5482
- if (id6 && !incomingIds.has(id6)) ops.push({ t: "update", ns: "admissionGrants", id: id6, attrs: {
5726
+ const id7 = String(row2.id ?? "");
5727
+ if (id7 && !incomingIds.has(id7)) ops.push({ t: "update", ns: "admissionGrants", id: id7, attrs: {
5483
5728
  active: false,
5484
5729
  revokedAt: receivedAt,
5485
5730
  revokedBy: document.source.actor,
@@ -5488,10 +5733,10 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
5488
5733
  } });
5489
5734
  }
5490
5735
  for (const grant of document.grants) {
5491
- const id6 = `admission:parent:${document.source.appId}:${grant.id}`;
5492
- const existing = current.find((row2) => row2.id === id6);
5493
- ops.push({ t: "update", ns: "admissionGrants", id: id6, attrs: {
5494
- id: id6,
5736
+ const id7 = `admission:parent:${document.source.appId}:${grant.id}`;
5737
+ const existing = current.find((row2) => row2.id === id7);
5738
+ ops.push({ t: "update", ns: "admissionGrants", id: id7, attrs: {
5739
+ id: id7,
5495
5740
  sourceGrantId: grant.id,
5496
5741
  sourceScope: "parent",
5497
5742
  sourceId: document.source.appId,
@@ -5512,7 +5757,7 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
5512
5757
  ...!grant.note && existing?.note !== void 0 ? ["note"] : [],
5513
5758
  ...grant.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
5514
5759
  ];
5515
- if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id6, attrs: retract });
5760
+ if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id7, attrs: retract });
5516
5761
  }
5517
5762
  if (previous?.active) ops.push({ t: "update", ns: "admissionGrantRevisions", id: previous.id, attrs: { active: false } });
5518
5763
  const revisionId = `admission-revision:${document.source.appId}:${document.target.environment}:${document.revision}`;
@@ -5543,9 +5788,9 @@ var APPLICATION_LABELS = {
5543
5788
  phone: "Phone",
5544
5789
  state: "State"
5545
5790
  };
5546
- function humanize(id6) {
5547
- const text8 = id6.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
5548
- return text8 ? `${text8[0].toUpperCase()}${text8.slice(1)}` : id6;
5791
+ function humanize(id7) {
5792
+ const text8 = id7.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
5793
+ return text8 ? `${text8[0].toUpperCase()}${text8.slice(1)}` : id7;
5549
5794
  }
5550
5795
  function applicationDefs(chapter) {
5551
5796
  try {
@@ -5554,23 +5799,23 @@ function applicationDefs(chapter) {
5554
5799
  return {};
5555
5800
  }
5556
5801
  }
5557
- function applicationType(id6, def) {
5802
+ function applicationType(id7, def) {
5558
5803
  if (def && ["string", "email", "url", "enum"].includes(def.type)) return def.type;
5559
- return id6 === "email" ? "email" : id6 === "linkedin" ? "url" : "string";
5804
+ return id7 === "email" ? "email" : id7 === "linkedin" ? "url" : "string";
5560
5805
  }
5561
5806
  function applicationFlowFields(chapter, application = chapter.application) {
5562
5807
  const defs = applicationDefs(chapter);
5563
- return [...application.required, ...application.optional].map((id6, order2) => {
5564
- const def = defs[id6];
5565
- const conditions = application.conditions[id6];
5566
- const type = applicationType(id6, def);
5808
+ return [...application.required, ...application.optional].map((id7, order2) => {
5809
+ const def = defs[id7];
5810
+ const conditions = application.conditions[id7];
5811
+ const type = applicationType(id7, def);
5567
5812
  return {
5568
- id: id6,
5569
- label: def?.label ?? APPLICATION_LABELS[id6] ?? humanize(id6),
5813
+ id: id7,
5814
+ label: def?.label ?? APPLICATION_LABELS[id7] ?? humanize(id7),
5570
5815
  type,
5571
- required: application.required.includes(id6),
5816
+ required: application.required.includes(id7),
5572
5817
  order: order2,
5573
- maxLength: application.maxLen[id6] ?? application.defaultMaxLen,
5818
+ maxLength: application.maxLen[id7] ?? application.defaultMaxLen,
5574
5819
  ...type === "enum" && def?.options ? { options: [...def.options] } : {},
5575
5820
  ...conditions ? { conditions: { ...conditions } } : {},
5576
5821
  ...type === "email" ? { emailIdentity: "literal" } : {}
@@ -5580,17 +5825,17 @@ function applicationFlowFields(chapter, application = chapter.application) {
5580
5825
  function resolveFormationFlowFields(crm, formation) {
5581
5826
  if (!formation.enabled) return [];
5582
5827
  const def = crm.type(formation.type);
5583
- return [...formation.required, ...formation.optional].map((id6, order2) => {
5584
- const field = def.fields[id6];
5828
+ return [...formation.required, ...formation.optional].map((id7, order2) => {
5829
+ const field = def.fields[id7];
5585
5830
  return {
5586
- id: id6,
5587
- label: field.label ?? humanize(id6),
5831
+ id: id7,
5832
+ label: field.label ?? humanize(id7),
5588
5833
  type: field.type,
5589
- required: formation.required.includes(id6),
5834
+ required: formation.required.includes(id7),
5590
5835
  order: order2,
5591
- maxLength: formation.maxLen[id6] ?? formation.defaultMaxLen,
5836
+ maxLength: formation.maxLen[id7] ?? formation.defaultMaxLen,
5592
5837
  ...field.options ? { options: [...field.options] } : {},
5593
- ...id6 === def.emailField || field.type === "email" ? { emailIdentity: "literal" } : {}
5838
+ ...id7 === def.emailField || field.type === "email" ? { emailIdentity: "literal" } : {}
5594
5839
  };
5595
5840
  });
5596
5841
  }
@@ -5640,10 +5885,10 @@ function validateGiftOfferCatalog(input, tierIds) {
5640
5885
  }
5641
5886
  const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
5642
5887
  for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
5643
- const id6 = text3(item.id, `${path}.id`, errors, 80);
5644
- if (id6 && !ID3.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
5645
- if (ids2.has(id6)) errors.push(`${path}.id duplicates "${id6}"`);
5646
- ids2.add(id6);
5888
+ const id7 = text3(item.id, `${path}.id`, errors, 80);
5889
+ if (id7 && !ID3.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
5890
+ if (ids2.has(id7)) errors.push(`${path}.id duplicates "${id7}"`);
5891
+ ids2.add(id7);
5647
5892
  const targetRaw = record(item.target);
5648
5893
  let target = { type: "entitlement", entitlementKey: "invalid" };
5649
5894
  if (targetRaw?.type === "tier") {
@@ -5669,7 +5914,7 @@ function validateGiftOfferCatalog(input, tierIds) {
5669
5914
  if (activeUntil !== void 0 && (!Number.isSafeInteger(activeUntil) || activeUntil < 1)) errors.push(`${path}.activeUntil must be epoch milliseconds`);
5670
5915
  if (activeFrom !== void 0 && activeUntil !== void 0 && activeUntil <= activeFrom) errors.push(`${path}.activeUntil must follow activeFrom`);
5671
5916
  offers.push({
5672
- id: id6,
5917
+ id: id7,
5673
5918
  target,
5674
5919
  billingMode: item.billingMode,
5675
5920
  stripePriceId,
@@ -5769,9 +6014,9 @@ async function materializeSignupControl(db, envelope, previous, receivedAt) {
5769
6014
  const ops = [];
5770
6015
  if (previous?.active) ops.push({ t: "update", ns: "signupControls", id: previous.id, attrs: { active: false } });
5771
6016
  for (const row2 of currentRows) {
5772
- const id7 = String(row2.id ?? "");
5773
- if (id7 && !incoming.has(id7)) {
5774
- ops.push({ t: "update", ns: "tiers", id: byPublicId("tiers", id7), attrs: { active: false } });
6017
+ const id8 = String(row2.id ?? "");
6018
+ if (id8 && !incoming.has(id8)) {
6019
+ ops.push({ t: "update", ns: "tiers", id: byPublicId("tiers", id8), attrs: { active: false } });
5775
6020
  }
5776
6021
  }
5777
6022
  for (const tier of document.tiers) {
@@ -5806,9 +6051,9 @@ async function materializeSignupControl(db, envelope, previous, receivedAt) {
5806
6051
  if (!("stripePriceId" in attrs)) {
5807
6052
  ops.push({ t: "retract", ns: "groups", id: groupRef, attrs: ["stripePriceId"] });
5808
6053
  }
5809
- const id6 = `signup-control:${document.target.environment}:${document.revision}`;
5810
- ops.push({ t: "update", ns: "signupControls", id: id6, attrs: {
5811
- id: id6,
6054
+ const id7 = `signup-control:${document.target.environment}:${document.revision}`;
6055
+ ops.push({ t: "update", ns: "signupControls", id: id7, attrs: {
6056
+ id: id7,
5812
6057
  sourceId: document.source.appId,
5813
6058
  targetId: document.target.chapterId,
5814
6059
  environment: document.target.environment,
@@ -5847,6 +6092,8 @@ function foundingOf(offer) {
5847
6092
  async function createPaymentQuote(input) {
5848
6093
  const { group, tier, policy: policy2 } = input;
5849
6094
  const terms2 = input.billingTerms ? subscriptionBillingTerms(input.billingTerms) : void 0;
6095
+ if (input.seatOffer && (!terms2 || !Number.isSafeInteger(input.seatOffer.amountCents) || input.seatOffer.amountCents < 0) || input.namedSeat && !input.seatOffer) throw new Error("invalid checkout seat offer");
6096
+ const seatCents = input.namedSeat ? input.seatOffer.amountCents : 0;
5850
6097
  const compareAt = input.presentation?.compareAtPriceCents;
5851
6098
  const presentation = Number.isSafeInteger(compareAt) && Number(compareAt) > tier.priceCents ? { compareAtPriceCents: Number(compareAt) } : void 0;
5852
6099
  const dueToday = terms2 ? subscriptionAmountDue({ priceCents: tier.priceCents, billingTerms: terms2 }) : void 0;
@@ -5855,6 +6102,7 @@ async function createPaymentQuote(input) {
5855
6102
  discountCents: tier.priceCents - dueToday,
5856
6103
  dueTodayCents: dueToday
5857
6104
  };
6105
+ if (!Number.isSafeInteger(lines.dueTodayCents + seatCents) || !Number.isSafeInteger(tier.priceCents + seatCents)) throw new Error("invalid checkout total");
5858
6106
  const quote = {
5859
6107
  applicationId: input.applicationId,
5860
6108
  chapterId: input.chapterId,
@@ -5862,11 +6110,17 @@ async function createPaymentQuote(input) {
5862
6110
  tierName: tier.name,
5863
6111
  currency: tier.currency ?? String(group.currency ?? "usd"),
5864
6112
  interval: tier.interval ?? (group.interval === "month" ? "month" : "year"),
5865
- lineItems: presentation ? {
5866
- standardCents: presentation.compareAtPriceCents,
5867
- discountCents: presentation.compareAtPriceCents - lines.dueTodayCents,
5868
- dueTodayCents: lines.dueTodayCents
5869
- } : lines,
6113
+ lineItems: {
6114
+ ...presentation ? {
6115
+ standardCents: presentation.compareAtPriceCents,
6116
+ discountCents: presentation.compareAtPriceCents - lines.dueTodayCents,
6117
+ dueTodayCents: lines.dueTodayCents
6118
+ } : lines,
6119
+ dueTodayCents: lines.dueTodayCents + seatCents,
6120
+ ...input.namedSeat ? { namedSeatCents: seatCents } : {}
6121
+ },
6122
+ ...input.seatOffer ? { seatOffer: input.seatOffer } : {},
6123
+ ...input.namedSeat ? { namedSeat: input.namedSeat } : {},
5870
6124
  ...presentation ? { presentation } : {},
5871
6125
  refundPolicyText: policy2?.refundPolicyText ?? String(group.refundPolicyText ?? ""),
5872
6126
  merchantDisclosureText: String(group.merchantDisclosureText ?? ""),
@@ -5878,7 +6132,7 @@ async function createPaymentQuote(input) {
5878
6132
  discountDuration: terms2.discount.duration === "once" ? "first_term" : "while_active"
5879
6133
  } : {},
5880
6134
  ...terms2.initialTermEndsAt !== void 0 ? { initialTermEndsAt: terms2.initialTermEndsAt } : {},
5881
- renewalCents: terms2.discount?.duration === "forever" ? dueToday : tier.priceCents,
6135
+ renewalCents: (terms2.discount?.duration === "forever" ? dueToday : tier.priceCents) + seatCents,
5882
6136
  autoRenew: true
5883
6137
  } } : {}
5884
6138
  };
@@ -5898,22 +6152,23 @@ async function createPaymentQuote(input) {
5898
6152
  // src/membership-quote-projection.ts
5899
6153
  var MEMBERSHIP_QUOTE_NAMESPACE = "membershipQuoteProjections";
5900
6154
  var keys2 = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId"];
5901
- var fail5 = (code) => {
6155
+ var fail6 = (code) => {
5902
6156
  throw Object.assign(new Error(code), { code });
5903
6157
  };
5904
- var load3 = async (db, ns, id6) => (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
6158
+ var load3 = async (db, ns, id7) => (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
5905
6159
  async function restore2(row2, target, now, allowExpired = false) {
5906
- 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");
5907
- if (row2.status !== "ready") return fail5("membership_quote_pending");
6160
+ 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");
6161
+ if (row2.status !== "ready") return fail6("membership_quote_pending");
5908
6162
  const offer = await verifyIssuedMembershipOffer(row2.offer, { ...target, offerId: await membershipOfferIdForRequest({
5909
6163
  ...target,
5910
6164
  requestId: row2.requestId
5911
6165
  }), now, lifetimeMs: 6e4 });
5912
- if (offer.issuedAt > now + 3e4 || !allowExpired && offer.expiresAt <= now) return fail5("membership_quote_expired");
6166
+ if (offer.issuedAt > now + 3e4 || !allowExpired && offer.expiresAt <= now) return fail6("membership_quote_expired");
5913
6167
  const tier = row2.tier, quote = row2.quote;
5914
6168
  const offering = offer.contract.offering;
6169
+ if (quote?.seatOffer && quote.seatOffer.amountCents !== offering.seatPriceCents) return fail6("membership_quote_conflict");
5915
6170
  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) {
5916
- return fail5("membership_catalog_mismatch");
6171
+ return fail6("membership_catalog_mismatch");
5917
6172
  }
5918
6173
  const expected = await createPaymentQuote({
5919
6174
  ...target,
@@ -5922,19 +6177,21 @@ async function restore2(row2, target, now, allowExpired = false) {
5922
6177
  policy: { refundPolicyText: quote.refundPolicyText, trustCopy: quote.trustCopy },
5923
6178
  billingTerms: offer.contract.billingTerms,
5924
6179
  presentation: quote.presentation,
5925
- founding: foundingOf(offer)
6180
+ founding: foundingOf(offer),
6181
+ seatOffer: quote.seatOffer,
6182
+ namedSeat: quote.namedSeat
5926
6183
  });
5927
- if (quote.digest !== expected.digest) return fail5("membership_quote_conflict");
6184
+ if (quote.digest !== expected.digest) return fail6("membership_quote_conflict");
5928
6185
  return { tier, quote: expected, billingTerms: offer.contract.billingTerms, membershipOffer: offer, projectionRevision: Number(row2.revision) };
5929
6186
  }
5930
6187
  async function readMembershipQuote(db, target, now) {
5931
6188
  return restore2(await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, target.applicationId), target, now);
5932
6189
  }
5933
6190
  async function prepareMembershipQuote(db, input, issue) {
5934
- const { applicationId: id6, now, app } = input;
5935
- 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");
5936
- let row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
5937
- if (row2 && keys2.some((key3) => row2[key3] !== input[key3])) return fail5("membership_quote_conflict");
6191
+ const { applicationId: id7, now, app } = input;
6192
+ 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");
6193
+ let row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7);
6194
+ if (row2 && keys2.some((key3) => row2[key3] !== input[key3])) return fail6("membership_quote_conflict");
5938
6195
  if (row2?.status === "ready") {
5939
6196
  try {
5940
6197
  return await restore2(row2, input, now);
@@ -5945,15 +6202,15 @@ async function prepareMembershipQuote(db, input, issue) {
5945
6202
  const identity2 = ["groupId", "tierId", "stripeRuntime", "email", "emailNormalized", "status", "canceled", "membershipRestartId", "namedSeatId", "namedSeatClaimPending", "giftClaimId", "giftOfferId"];
5946
6203
  const guards = [{
5947
6204
  ns: "applications",
5948
- id: id6,
6205
+ id: id7,
5949
6206
  exists: true,
5950
6207
  equals: Object.fromEntries(identity2.filter((key3) => app[key3] !== void 0).map((key3) => [key3, app[key3]])),
5951
6208
  absent: identity2.filter((key3) => app[key3] === void 0)
5952
- }, { ns: "subscriptionCheckoutIntents", id: id6, exists: false }];
6209
+ }, { ns: "subscriptionCheckoutIntents", id: id7, exists: false }];
5953
6210
  if (!row2 || row2.status === "ready" || row2.status === "restart") {
5954
6211
  const revision = Number(row2?.revision ?? 0) + 1;
5955
6212
  const pending = {
5956
- id: id6,
6213
+ id: id7,
5957
6214
  ...Object.fromEntries(keys2.map((key3) => [key3, input[key3]])),
5958
6215
  revision,
5959
6216
  status: "pending",
@@ -5961,25 +6218,25 @@ async function prepareMembershipQuote(db, input, issue) {
5961
6218
  updatedAt: now
5962
6219
  };
5963
6220
  try {
5964
- await db.transact([{ t: "update", ns: MEMBERSHIP_QUOTE_NAMESPACE, id: id6, attrs: pending }], {
5965
- mutationId: `membership-quote:${id6}:${revision}`,
6221
+ await db.transact([{ t: "update", ns: MEMBERSHIP_QUOTE_NAMESPACE, id: id7, attrs: pending }], {
6222
+ mutationId: `membership-quote:${id7}:${revision}`,
5966
6223
  guards: [...guards, {
5967
6224
  ns: MEMBERSHIP_QUOTE_NAMESPACE,
5968
- id: id6,
6225
+ id: id7,
5969
6226
  exists: Boolean(row2),
5970
6227
  ...row2 ? { equals: { revision: row2.revision, status: row2.status } } : {}
5971
6228
  }]
5972
6229
  });
5973
6230
  } catch (error) {
5974
6231
  if (error.code !== "transact_guard_failed") throw error;
5975
- const winner = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
6232
+ const winner = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7);
5976
6233
  if (!winner || winner.revision !== revision) throw error;
5977
6234
  }
5978
- row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
6235
+ row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7);
5979
6236
  }
5980
6237
  if (row2?.status === "ready") return restore2(row2, input, now);
5981
6238
  if (!row2 || row2.status !== "pending" || keys2.some((key3) => row2[key3] !== input[key3]) || typeof row2.requestId !== "string") {
5982
- return fail5("membership_quote_pending");
6239
+ return fail6("membership_quote_pending");
5983
6240
  }
5984
6241
  const target = Object.fromEntries(keys2.map((key3) => [key3, input[key3]]));
5985
6242
  const offer = await verifyIssuedMembershipOffer(await issue({ ...target, requestId: row2.requestId }), {
@@ -6000,7 +6257,8 @@ async function prepareMembershipQuote(db, input, issue) {
6000
6257
  policy: input.policy,
6001
6258
  billingTerms: offer.contract.billingTerms,
6002
6259
  presentation: input.presentation,
6003
- founding: foundingOf(offer)
6260
+ founding: foundingOf(offer),
6261
+ ...input.namedSeatsEnabled ? { seatOffer: { amountCents: offer.contract.offering.seatPriceCents } } : {}
6004
6262
  });
6005
6263
  const ready = { ...row2, offer, tier, quote, status: "ready", updatedAt: now };
6006
6264
  await restore2(ready, target, now, true);
@@ -6008,23 +6266,100 @@ async function prepareMembershipQuote(db, input, issue) {
6008
6266
  await db.transact([{
6009
6267
  t: "update",
6010
6268
  ns: MEMBERSHIP_QUOTE_NAMESPACE,
6011
- id: id6,
6269
+ id: id7,
6012
6270
  attrs: { offer, offerDigest: offer.digest, tier, quote, status: "ready", updatedAt: now }
6013
6271
  }], {
6014
- mutationId: `membership-quote-ready:${id6}:${row2.revision}`,
6272
+ mutationId: `membership-quote-ready:${id7}:${row2.revision}`,
6015
6273
  guards: [...guards, {
6016
6274
  ns: MEMBERSHIP_QUOTE_NAMESPACE,
6017
- id: id6,
6275
+ id: id7,
6018
6276
  exists: true,
6019
6277
  equals: { status: "pending", revision: row2.revision, requestId: row2.requestId }
6020
6278
  }]
6021
6279
  });
6022
6280
  } catch (error) {
6023
6281
  if (error.code !== "transact_guard_failed") throw error;
6024
- const winner = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
6282
+ const winner = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7);
6025
6283
  if (winner?.status !== "ready" || winner.requestId !== row2.requestId || winner.offerDigest !== offer.digest) throw error;
6026
6284
  }
6027
- return restore2(await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6), target, now);
6285
+ return restore2(await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7), target, now);
6286
+ }
6287
+ async function selectCheckoutNamedSeat(db, target, input) {
6288
+ if (!input.enabled) return fail6("named_seat_purchase_unavailable");
6289
+ const row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, target.applicationId);
6290
+ const current = await restore2(row2, target, input.now);
6291
+ const namedSeat = input.selection === null ? void 0 : checkoutNamedSeatSelection(input.selection, input.purchaserEmail);
6292
+ const quote = await createPaymentQuote({
6293
+ ...target,
6294
+ tier: current.tier,
6295
+ group: { merchantDisclosureText: current.quote.merchantDisclosureText },
6296
+ policy: { refundPolicyText: current.quote.refundPolicyText, trustCopy: current.quote.trustCopy },
6297
+ billingTerms: current.billingTerms,
6298
+ presentation: current.quote.presentation,
6299
+ founding: current.quote.founding,
6300
+ seatOffer: { amountCents: current.membershipOffer.contract.offering.seatPriceCents },
6301
+ namedSeat
6302
+ });
6303
+ if (quote.digest === current.quote.digest) return current;
6304
+ await db.transact([{
6305
+ t: "update",
6306
+ ns: MEMBERSHIP_QUOTE_NAMESPACE,
6307
+ id: target.applicationId,
6308
+ attrs: { quote, revision: current.projectionRevision + 1, updatedAt: input.now }
6309
+ }], {
6310
+ mutationId: `membership-quote-selection:${target.applicationId}:${current.projectionRevision}:${quote.digest}`,
6311
+ guards: [
6312
+ {
6313
+ ns: MEMBERSHIP_QUOTE_NAMESPACE,
6314
+ id: target.applicationId,
6315
+ exists: true,
6316
+ equals: { revision: current.projectionRevision, status: "ready" }
6317
+ },
6318
+ { ns: "subscriptionCheckoutIntents", id: target.applicationId, exists: false }
6319
+ ]
6320
+ });
6321
+ return restore2(await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, target.applicationId), target, input.now);
6322
+ }
6323
+
6324
+ // src/bounded-request-text.ts
6325
+ async function readBoundedRequestText(req, maxBytes) {
6326
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) throw new TypeError("invalid request body cap");
6327
+ const reader = req.body?.getReader();
6328
+ const cancel = () => {
6329
+ void reader?.cancel().catch(() => {
6330
+ });
6331
+ };
6332
+ const invalid2 = () => json({ error: "invalid JSON body" }, 400);
6333
+ let complete = false;
6334
+ req.signal.addEventListener("abort", cancel, { once: true });
6335
+ try {
6336
+ if (req.signal.aborted) return invalid2();
6337
+ const declared = req.headers.get("content-length");
6338
+ if (declared && /^\d+$/.test(declared) && Number(declared) > maxBytes) {
6339
+ return json({ error: "request body too large" }, 413);
6340
+ }
6341
+ if (!reader) return "";
6342
+ const decoder = new TextDecoder();
6343
+ let bytes = 0;
6344
+ let text8 = "";
6345
+ for (; ; ) {
6346
+ const { done, value } = await reader.read();
6347
+ if (req.signal.aborted) return invalid2();
6348
+ if (done) {
6349
+ complete = true;
6350
+ return text8 + decoder.decode();
6351
+ }
6352
+ bytes += value.byteLength;
6353
+ if (bytes > maxBytes) return json({ error: "request body too large" }, 413);
6354
+ text8 += decoder.decode(value, { stream: true });
6355
+ }
6356
+ } catch {
6357
+ return invalid2();
6358
+ } finally {
6359
+ req.signal.removeEventListener("abort", cancel);
6360
+ if (!complete) cancel();
6361
+ reader?.releaseLock();
6362
+ }
6028
6363
  }
6029
6364
 
6030
6365
  // src/payment-quote-route.ts
@@ -6058,8 +6393,11 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
6058
6393
  policy: { refundPolicyText: quote2.refundPolicyText, trustCopy: quote2.trustCopy },
6059
6394
  billingTerms,
6060
6395
  presentation: quote2.presentation,
6061
- founding: foundingOf(intent.membershipOffer)
6396
+ founding: foundingOf(intent.membershipOffer),
6397
+ seatOffer: quote2.seatOffer,
6398
+ namedSeat: quote2.namedSeat
6062
6399
  });
6400
+ if (quote2.seatOffer && quote2.seatOffer.amountCents !== intent.membershipOffer?.contract.offering.seatPriceCents) throw new Error("invalid seat snapshot");
6063
6401
  if (expected.digest !== quote2.digest) throw new Error("invalid commercial snapshot");
6064
6402
  return { tier: tier2, quote: expected, billingTerms, membershipOffer: intent.membershipOffer };
6065
6403
  }
@@ -6088,7 +6426,16 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
6088
6426
  const presentation = tierPresentationFor(control3?.document, tier.id);
6089
6427
  if (ctx.membershipAuthority) return prepareMembershipQuote(
6090
6428
  db,
6091
- { ...target, app, group, tier, policy: policy2, presentation, now: Date.now() },
6429
+ {
6430
+ ...target,
6431
+ app,
6432
+ group,
6433
+ tier,
6434
+ policy: policy2,
6435
+ presentation,
6436
+ namedSeatsEnabled: Boolean(ctx.membershipAuthority.namedSeats) && !app.membershipRestartId,
6437
+ now: Date.now()
6438
+ },
6092
6439
  (input) => ctx.membershipAuthority.issueOffer(input, env)
6093
6440
  );
6094
6441
  const quote = await createPaymentQuote({
@@ -6104,20 +6451,54 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
6104
6451
  });
6105
6452
  return { tier, quote, billingTerms: void 0 };
6106
6453
  }
6107
- async function paymentQuoteRoute(url, env, ctx, prepare = false) {
6108
- const id6 = url.searchParams.get("application");
6109
- if (!id6 || id6.length > 160) return json({ error: "application required" }, 400);
6454
+ async function paymentQuoteRoute(url, env, ctx, prepare = false, request) {
6455
+ let selection;
6456
+ if (request?.body) {
6457
+ try {
6458
+ const text8 = await readBoundedRequestText(request, 2048);
6459
+ if (text8 instanceof Response) return text8;
6460
+ const body = JSON.parse(text8);
6461
+ if (!body || typeof body !== "object" || Array.isArray(body) || Object.keys(body).some((key3) => key3 !== "namedSeat")) throw new Error("invalid body");
6462
+ selection = body.namedSeat;
6463
+ } catch {
6464
+ return json({ error: "invalid seat selection" }, 400);
6465
+ }
6466
+ }
6467
+ const id7 = url.searchParams.get("application");
6468
+ if (!id7 || id7.length > 160) return json({ error: "application required" }, 400);
6110
6469
  const db = ctx.makeDb(env);
6111
- const app = (await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } })).applications?.[0];
6470
+ const app = (await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } })).applications?.[0];
6112
6471
  if (!app || String(app.groupId ?? ctx.chapter.id) !== ctx.chapter.id) return json({ error: "not found" }, 404);
6113
6472
  if (typeof app.stripeRuntime === "string" && app.stripeRuntime !== stripeRuntimeIdentity(env.ODLA_RUNTIME)) return json({ error: "not found" }, 404);
6114
6473
  if (app.status !== "submitted" || app.giftOfferId || app.namedSeatId || app.namedSeatClaimPending || app.giftClaimId || app.canceled === true) return json({ error: "membership checkout unavailable" }, 409);
6115
6474
  const group = (await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups?.[0];
6116
6475
  if (!group) return json({ error: "payments not configured" }, 503);
6117
6476
  try {
6118
- const { quote } = await checkoutQuoteForApplication(db, app, group, env, ctx, prepare);
6119
- return new Response(JSON.stringify(quote), { headers: { "content-type": "application/json", "cache-control": "no-store" } });
6477
+ let { quote } = await checkoutQuoteForApplication(db, app, group, env, ctx, prepare);
6478
+ const intent = (await db.query({ subscriptionCheckoutIntents: { $: { where: { id: id7 }, limit: 1 } } })).subscriptionCheckoutIntents?.[0];
6479
+ if (prepare && !intent && !quote.seatOffer && ctx.membershipAuthority?.namedSeats && !app.membershipRestartId && selection === void 0) selection = null;
6480
+ if (selection !== void 0) {
6481
+ if (intent) return json({ error: "checkout has already started", code: "checkout_intent_mismatch" }, 409);
6482
+ ({ quote } = await selectCheckoutNamedSeat(
6483
+ db,
6484
+ {
6485
+ appId: env.ODLA_APP_ID,
6486
+ chapterId: ctx.chapter.id,
6487
+ environment: env.ODLA_ENV,
6488
+ runtime: stripeRuntimeIdentity(env.ODLA_RUNTIME),
6489
+ applicationId: id7,
6490
+ skuId: String(app.tierId)
6491
+ },
6492
+ { selection, purchaserEmail: String(app.email ?? ""), now: Date.now(), enabled: Boolean(ctx.membershipAuthority?.namedSeats) && !app.membershipRestartId }
6493
+ ));
6494
+ }
6495
+ return new Response(JSON.stringify(quote), { headers: {
6496
+ "content-type": "application/json",
6497
+ "cache-control": "no-store",
6498
+ "x-chapter-checkout-started": intent ? "true" : "false"
6499
+ } });
6120
6500
  } catch (error) {
6501
+ if (error.code === "invalid_named_seat_recipient") return json({ error: "Enter a different recipient's name and email." }, 400);
6121
6502
  const expired = error.code === "membership_quote_expired";
6122
6503
  return json({ error: "payment quote unavailable", ...expired ? { code: "payment_quote_expired" } : {} }, expired ? 409 : 503);
6123
6504
  }
@@ -6223,47 +6604,6 @@ async function resumeJoin(url, env, ctx) {
6223
6604
  return json({ error: "Application continuation is unavailable. Retry when its payment or membership configuration is restored." }, 409);
6224
6605
  }
6225
6606
 
6226
- // src/bounded-request-text.ts
6227
- async function readBoundedRequestText(req, maxBytes) {
6228
- if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) throw new TypeError("invalid request body cap");
6229
- const reader = req.body?.getReader();
6230
- const cancel = () => {
6231
- void reader?.cancel().catch(() => {
6232
- });
6233
- };
6234
- const invalid2 = () => json({ error: "invalid JSON body" }, 400);
6235
- let complete = false;
6236
- req.signal.addEventListener("abort", cancel, { once: true });
6237
- try {
6238
- if (req.signal.aborted) return invalid2();
6239
- const declared = req.headers.get("content-length");
6240
- if (declared && /^\d+$/.test(declared) && Number(declared) > maxBytes) {
6241
- return json({ error: "request body too large" }, 413);
6242
- }
6243
- if (!reader) return "";
6244
- const decoder = new TextDecoder();
6245
- let bytes = 0;
6246
- let text8 = "";
6247
- for (; ; ) {
6248
- const { done, value } = await reader.read();
6249
- if (req.signal.aborted) return invalid2();
6250
- if (done) {
6251
- complete = true;
6252
- return text8 + decoder.decode();
6253
- }
6254
- bytes += value.byteLength;
6255
- if (bytes > maxBytes) return json({ error: "request body too large" }, 413);
6256
- text8 += decoder.decode(value, { stream: true });
6257
- }
6258
- } catch {
6259
- return invalid2();
6260
- } finally {
6261
- req.signal.removeEventListener("abort", cancel);
6262
- if (!complete) cancel();
6263
- reader?.releaseLock();
6264
- }
6265
- }
6266
-
6267
6607
  // src/named-seat-application.ts
6268
6608
  async function namedSeatPurchaserName(db, applicationId, chapterId, runtime) {
6269
6609
  const app = await membershipLoad(db, "applications", applicationId);
@@ -7158,10 +7498,10 @@ function tiersOf(value, errors) {
7158
7498
  return [];
7159
7499
  }
7160
7500
  unknownKeys(item, TIER_KEYS2, path, errors);
7161
- const id6 = text4(item.id, `${path}.id`, errors, 80);
7162
- if (id6 && !TIER.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7163
- if (seen.has(id6)) errors.push(`${path}.id duplicates "${id6}"`);
7164
- seen.add(id6);
7501
+ const id7 = text4(item.id, `${path}.id`, errors, 80);
7502
+ if (id7 && !TIER.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7503
+ if (seen.has(id7)) errors.push(`${path}.id duplicates "${id7}"`);
7504
+ seen.add(id7);
7165
7505
  const name = text4(item.name, `${path}.name`, errors, 160);
7166
7506
  if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
7167
7507
  if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
@@ -7182,7 +7522,7 @@ function tiersOf(value, errors) {
7182
7522
  }
7183
7523
  const blurb = item.blurb === void 0 ? void 0 : text4(item.blurb, `${path}.blurb`, errors, 4e3);
7184
7524
  return [{
7185
- id: id6,
7525
+ id: id7,
7186
7526
  name,
7187
7527
  available: item.available === true,
7188
7528
  sortOrder: Number(item.sortOrder) || 0,
@@ -7244,15 +7584,15 @@ function policyOf(value, errors, tiers) {
7244
7584
  if (input.tierPolicies !== void 0) {
7245
7585
  const overrides = recordOf(input.tierPolicies);
7246
7586
  if (!overrides || Object.keys(overrides).length > 20) errors.push("document.policy.tierPolicies must be a bounded tier map");
7247
- else for (const [id6, raw2] of Object.entries(overrides)) {
7248
- const path = `document.policy.tierPolicies.${id6}`;
7587
+ else for (const [id7, raw2] of Object.entries(overrides)) {
7588
+ const path = `document.policy.tierPolicies.${id7}`;
7249
7589
  const policy2 = recordOf(raw2);
7250
- if (!tiers.some((tier) => tier.id === id6) || !policy2) {
7590
+ if (!tiers.some((tier) => tier.id === id7) || !policy2) {
7251
7591
  errors.push(`${path} must name a declared tier and policy`);
7252
7592
  continue;
7253
7593
  }
7254
7594
  unknownKeys(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
7255
- tierPolicies[id6] = {
7595
+ tierPolicies[id7] = {
7256
7596
  refundPolicyText: text4(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
7257
7597
  ...policy2.trustCopy !== void 0 ? { trustCopy: text4(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
7258
7598
  };
@@ -7545,14 +7885,14 @@ function validateMemberContentDocument(input, options = {}) {
7545
7885
  continue;
7546
7886
  }
7547
7887
  unknownKeys2(raw2, PAGE_KEYS, path, errors);
7548
- const id6 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
7888
+ const id7 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
7549
7889
  const slug = textValue(raw2.slug, `${path}.slug`, errors, 120, true) ?? "";
7550
7890
  const title = textValue(raw2.title, `${path}.title`, errors, MAX_TEXT, true) ?? "";
7551
- if (id6 && !ID4.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7891
+ if (id7 && !ID4.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7552
7892
  if (slug && !SLUG2.test(slug)) errors.push(`${path}.slug must be a lowercase URL slug`);
7553
- if (ids2.has(id6) || knownParentIds.has(id6)) errors.push(`${path}.id duplicates page "${id6}"`);
7893
+ if (ids2.has(id7) || knownParentIds.has(id7)) errors.push(`${path}.id duplicates page "${id7}"`);
7554
7894
  if (slugs.has(slug) || (options.parentPages ?? []).some((page) => page.slug === slug)) errors.push(`${path}.slug duplicates "${slug}"`);
7555
- ids2.add(id6);
7895
+ ids2.add(id7);
7556
7896
  slugs.add(slug);
7557
7897
  const navRaw = record2(raw2.navigation);
7558
7898
  let navigation = { section: "General", order: index2 };
@@ -7608,7 +7948,7 @@ function validateMemberContentDocument(input, options = {}) {
7608
7948
  else if (owner === "chapter") upsell = { owner, ...offer ? { offer } : {} };
7609
7949
  }
7610
7950
  }
7611
- pages.push({ id: id6, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
7951
+ pages.push({ id: id7, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
7612
7952
  }
7613
7953
  const allIds = /* @__PURE__ */ new Set([...knownParentIds, ...ids2]);
7614
7954
  for (const [index2, page] of pages.entries()) {
@@ -7751,20 +8091,20 @@ async function loadLatestMemberContentControl(db, targetId, environment) {
7751
8091
  return control(Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
7752
8092
  }
7753
8093
  async function loadLocalMemberContent(db, chapterId, environment) {
7754
- const id6 = `member-content-local:${environment}`;
7755
- const rows5 = (await db.query({ memberContentLocal: { $: { where: { id: id6 }, limit: 1 } } })).memberContentLocal;
8094
+ const id7 = `member-content-local:${environment}`;
8095
+ const rows5 = (await db.query({ memberContentLocal: { $: { where: { id: id7 }, limit: 1 } } })).memberContentLocal;
7756
8096
  return local2(Array.isArray(rows5) ? rows5[0] : void 0);
7757
8097
  }
7758
8098
  async function materializeMemberContentControl(db, envelope, previous, receivedAt) {
7759
8099
  const { document, digest } = envelope;
7760
8100
  const target = document.target;
7761
- const id6 = `member-content-control:${target.environment}:${document.revision}`;
8101
+ const id7 = `member-content-control:${target.environment}:${document.revision}`;
7762
8102
  const ops = [];
7763
8103
  if (previous?.active) {
7764
8104
  ops.push({ t: "update", ns: "memberContentControls", id: previous.id, attrs: { active: false } });
7765
8105
  }
7766
- ops.push({ t: "update", ns: "memberContentControls", id: id6, attrs: {
7767
- id: id6,
8106
+ ops.push({ t: "update", ns: "memberContentControls", id: id7, attrs: {
8107
+ id: id7,
7768
8108
  sourceId: document.source.appId,
7769
8109
  targetId: target.chapterId,
7770
8110
  environment: target.environment,
@@ -7777,11 +8117,11 @@ async function materializeMemberContentControl(db, envelope, previous, receivedA
7777
8117
  await db.transact(ops, { mutationId: `member-content-control:${target.environment}:${document.revision}:${digest}` });
7778
8118
  }
7779
8119
  async function saveLocalMemberContent(db, document, environment, expectedRevision, updatedAt, updatedBy) {
7780
- const id6 = `member-content-local:${environment}`;
8120
+ const id7 = `member-content-local:${environment}`;
7781
8121
  const current = await loadLocalMemberContent(db, document.source.appId, environment);
7782
8122
  if ((current?.revision ?? 0) !== expectedRevision) throw new Error("member_content_revision_conflict");
7783
8123
  const row2 = {
7784
- id: id6,
8124
+ id: id7,
7785
8125
  chapterId: document.source.appId,
7786
8126
  environment,
7787
8127
  revision: document.revision,
@@ -7789,9 +8129,9 @@ async function saveLocalMemberContent(db, document, environment, expectedRevisio
7789
8129
  updatedAt,
7790
8130
  ...updatedBy ? { updatedBy } : {}
7791
8131
  };
7792
- await db.transact([{ t: "update", ns: "memberContentLocal", id: id6, attrs: row2 }], {
8132
+ await db.transact([{ t: "update", ns: "memberContentLocal", id: id7, attrs: row2 }], {
7793
8133
  mutationId: `member-content-local:${environment}:${document.revision}`,
7794
- guards: current ? [{ ns: "memberContentLocal", id: id6, exists: true, equals: { revision: expectedRevision } }] : [{ ns: "memberContentLocal", id: id6, exists: false }]
8134
+ guards: current ? [{ ns: "memberContentLocal", id: id7, exists: true, equals: { revision: expectedRevision } }] : [{ ns: "memberContentLocal", id: id7, exists: false }]
7795
8135
  });
7796
8136
  return row2;
7797
8137
  }
@@ -7928,7 +8268,7 @@ var handleAdminMemberContent = async (req, url, env, ctx) => {
7928
8268
  parent: parent ? { revision: parent.revision, digest: parent.digest, document: parent.document } : null,
7929
8269
  chapter: chapter ? { revision: chapter.revision, document: chapter.document } : null,
7930
8270
  effective,
7931
- tiers: tiers.map(({ id: id6, name, priceCents, active }) => ({ id: id6, name, priceCents, active })),
8271
+ tiers: tiers.map(({ id: id7, name, priceCents, active }) => ({ id: id7, name, priceCents, active })),
7932
8272
  localAuthoring: ctx.chapter.memberContent.localAuthoring
7933
8273
  });
7934
8274
  }
@@ -8086,11 +8426,11 @@ var handleAdminAdmissionGrants = async (req, url, env, ctx) => {
8086
8426
  return json({ error: "expiresAt must be a future epoch timestamp" }, 400);
8087
8427
  }
8088
8428
  const sourceGrantId = crypto.randomUUID();
8089
- const id6 = `admission:local:${ctx.chapter.id}:${sourceGrantId}`;
8429
+ const id7 = `admission:local:${ctx.chapter.id}:${sourceGrantId}`;
8090
8430
  const createdAt = Date.now();
8091
8431
  const actor = auth.user.email ?? auth.user.userId;
8092
- await auth.db.transact([{ t: "update", ns: "admissionGrants", id: id6, attrs: {
8093
- id: id6,
8432
+ await auth.db.transact([{ t: "update", ns: "admissionGrants", id: id7, attrs: {
8433
+ id: id7,
8094
8434
  sourceGrantId,
8095
8435
  sourceScope: "local",
8096
8436
  sourceId: ctx.chapter.id,
@@ -8106,7 +8446,7 @@ var handleAdminAdmissionGrants = async (req, url, env, ctx) => {
8106
8446
  updatedAt: createdAt,
8107
8447
  updatedBy: actor
8108
8448
  } }], { mutationId: `admission-local-create:${sourceGrantId}` });
8109
- return json({ grant: { id: id6, sourceGrantId, sourceScope: "local", email, decision, active: true, createdAt, ...note ? { note } : {}, ...expiresAt ? { expiresAt } : {} } }, 201);
8449
+ return json({ grant: { id: id7, sourceGrantId, sourceScope: "local", email, decision, active: true, createdAt, ...note ? { note } : {}, ...expiresAt ? { expiresAt } : {} } }, 201);
8110
8450
  }
8111
8451
  const prefix = "/api/admin/admission-grants/";
8112
8452
  let revokeId = "";
@@ -8120,20 +8460,20 @@ var handleAdminAdmissionGrants = async (req, url, env, ctx) => {
8120
8460
  if (/^admission:local:[a-z0-9-]+:[0-9a-f-]+$/.test(revokeId) && req.method === "DELETE") {
8121
8461
  const auth = await gate(req, env, ctx);
8122
8462
  if (auth instanceof Response) return auth;
8123
- const id6 = revokeId;
8124
- const found = await auth.db.query({ admissionGrants: { $: { where: { id: id6 }, limit: 1 } } });
8463
+ const id7 = revokeId;
8464
+ const found = await auth.db.query({ admissionGrants: { $: { where: { id: id7 }, limit: 1 } } });
8125
8465
  const row2 = Array.isArray(found.admissionGrants) ? found.admissionGrants[0] : void 0;
8126
8466
  if (!row2 || row2.targetId !== ctx.chapter.id || row2.sourceScope !== "local") return json({ error: "not found" }, 404);
8127
8467
  if (row2.active !== true) return json({ ok: true, replayed: true });
8128
8468
  const revokedAt = Date.now();
8129
8469
  const actor = auth.user.email ?? auth.user.userId;
8130
- await auth.db.transact([{ t: "update", ns: "admissionGrants", id: id6, attrs: {
8470
+ await auth.db.transact([{ t: "update", ns: "admissionGrants", id: id7, attrs: {
8131
8471
  active: false,
8132
8472
  revokedAt,
8133
8473
  revokedBy: actor,
8134
8474
  updatedAt: revokedAt,
8135
8475
  updatedBy: actor
8136
- } }], { mutationId: `admission-local-revoke:${id6}` });
8476
+ } }], { mutationId: `admission-local-revoke:${id7}` });
8137
8477
  return json({ ok: true, replayed: false });
8138
8478
  }
8139
8479
  return null;
@@ -8195,8 +8535,8 @@ async function loadLeaderAdmissionAssignments(db, targetId) {
8195
8535
  return await readAdmissionRows(db, "leaderAdmissionAssignments", { targetId }, MAX_ASSIGNMENTS);
8196
8536
  }
8197
8537
  async function loadLeaderAdmissionDelivery(db, targetId, environment) {
8198
- const id6 = deliveryId(targetId, environment);
8199
- const result = await db.query({ leaderAdmissionDeliveries: { $: { where: { id: id6 }, limit: 1 } } });
8538
+ const id7 = deliveryId(targetId, environment);
8539
+ const result = await db.query({ leaderAdmissionDeliveries: { $: { where: { id: id7 }, limit: 1 } } });
8200
8540
  return admissionRows(result.leaderAdmissionDeliveries)[0] ?? null;
8201
8541
  }
8202
8542
  function personAdmissionEmail(ctx, record4) {
@@ -8224,8 +8564,8 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8224
8564
  const now = Date.now();
8225
8565
  const previous = await loadLeaderAdmissionDelivery(opts.db, opts.target.id, opts.env.ODLA_ENV);
8226
8566
  const current = await loadLeaderAdmissionAssignments(opts.db, opts.target.id);
8227
- const id6 = assignmentId(opts.target.id, opts.personId);
8228
- const existing = current.find((row2) => row2.id === id6);
8567
+ const id7 = assignmentId(opts.target.id, opts.personId);
8568
+ const existing = current.find((row2) => row2.id === id7);
8229
8569
  const digest = await signupControlDigest({
8230
8570
  targetId: opts.target.id,
8231
8571
  personId: opts.personId,
@@ -8255,7 +8595,7 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8255
8595
  return { delivery: saved, duplicate: true };
8256
8596
  }
8257
8597
  const assignment = {
8258
- id: id6,
8598
+ id: id7,
8259
8599
  targetId: opts.target.id,
8260
8600
  personId: opts.personId,
8261
8601
  grantId: existing?.grantId ?? crypto.randomUUID().replaceAll("-", ""),
@@ -8270,7 +8610,7 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8270
8610
  updatedAt: now,
8271
8611
  updatedBy: opts.actor
8272
8612
  };
8273
- const desired = current.filter((row2) => row2.id !== id6 && row2.active).concat(assignment.active ? [assignment] : []);
8613
+ const desired = current.filter((row2) => row2.id !== id7 && row2.active).concat(assignment.active ? [assignment] : []);
8274
8614
  const grants = await Promise.all(desired.map((row2) => grantInput(opts.db, opts.ctx, row2, opts.resolveEmail)));
8275
8615
  const revision = (previous?.revision ?? 0) + 1;
8276
8616
  const envelope = await createAdmissionGrantEnvelope({
@@ -8293,7 +8633,7 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8293
8633
  updatedAt: now
8294
8634
  };
8295
8635
  const ops = [
8296
- { t: "update", ns: "leaderAdmissionAssignments", id: id6, attrs: assignment },
8636
+ { t: "update", ns: "leaderAdmissionAssignments", id: id7, attrs: assignment },
8297
8637
  { t: "update", ns: "leaderAdmissionDeliveries", id: delivery.id, attrs: delivery },
8298
8638
  { t: "update", ns: "leaderAdmissionMutations", id: mutationReceiptId, attrs: {
8299
8639
  id: mutationReceiptId,
@@ -8308,7 +8648,7 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8308
8648
  ...!opts.note && existing?.note !== void 0 ? ["note"] : [],
8309
8649
  ...opts.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
8310
8650
  ];
8311
- if (retract.length) ops.push({ t: "retract", ns: "leaderAdmissionAssignments", id: id6, attrs: retract });
8651
+ if (retract.length) ops.push({ t: "retract", ns: "leaderAdmissionAssignments", id: id7, attrs: retract });
8312
8652
  const tx = await opts.db.transact(ops, {
8313
8653
  mutationId: `leader-admission:${opts.target.id}:${opts.mutationId}`,
8314
8654
  guards: previous ? [{ ns: "leaderAdmissionDeliveries", id: previous.id, exists: true, equals: { revision: previous.revision } }] : [{ ns: "leaderAdmissionDeliveries", id: delivery.id, exists: false }]
@@ -8368,11 +8708,11 @@ function projectRecord(record4, allowed2, editable, stagesEnabled, crm) {
8368
8708
  allowed2.flatMap((name) => Object.prototype.hasOwnProperty.call(record4.fields, name) ? [[name, record4.fields[name]]] : [])
8369
8709
  );
8370
8710
  const def = crm.type(record4.type);
8371
- const editFields = editable.map((id6) => {
8372
- const field = def.fields[id6];
8711
+ const editFields = editable.map((id7) => {
8712
+ const field = def.fields[id7];
8373
8713
  return {
8374
- id: id6,
8375
- label: field.label ?? id6,
8714
+ id: id7,
8715
+ label: field.label ?? id7,
8376
8716
  type: field.type,
8377
8717
  required: field.required ?? false,
8378
8718
  ...field.options ? { options: field.options } : {}
@@ -8489,8 +8829,8 @@ async function gate2(req, env, ctx) {
8489
8829
  if (!await networkWriteAllowed(req, db, user, ctx, true)) return { response: reject6(403, "super_admin_required", "super-admin required") };
8490
8830
  return { db, user };
8491
8831
  }
8492
- function targetFor(ctx, id6) {
8493
- return ctx.chapter.network.targets.find((target) => target.id === id6 && target.admissionGrants);
8832
+ function targetFor(ctx, id7) {
8833
+ return ctx.chapter.network.targets.find((target) => target.id === id7 && target.admissionGrants);
8494
8834
  }
8495
8835
  async function deliverSaved(db, env, ctx, target, delivery) {
8496
8836
  if (delivery.status === "acknowledged") return delivery;
@@ -8594,7 +8934,7 @@ var handleAdminNetworkAdmissions = async (req, url, env, ctx) => {
8594
8934
  if (!personId || !MUTATION_ID2.test(mutationId)) {
8595
8935
  return reject6(422, "invalid_request", "personId and a stable mutationId are required");
8596
8936
  }
8597
- const resolveEmail = (id6) => resolveAdmissionPersonEmail(db, env, ctx, target, id6);
8937
+ const resolveEmail = (id7) => resolveAdmissionPersonEmail(db, env, ctx, target, id7);
8598
8938
  let email;
8599
8939
  try {
8600
8940
  email = await resolveEmail(personId);
@@ -8833,28 +9173,28 @@ var handleSchedule = async (req, url, env, ctx) => {
8833
9173
  };
8834
9174
 
8835
9175
  // src/payments-stripe.ts
8836
- import { stripeRequest as stripeRequest9, verifyStripeSignature as verifyStripeSignature2 } from "@odla-ai/stripe";
9176
+ import { stripeRequest as stripeRequest10, verifyStripeSignature as verifyStripeSignature2 } from "@odla-ai/stripe";
8837
9177
 
8838
9178
  // src/payments-refund.ts
8839
- import { stripeRequest as stripeRequest7 } from "@odla-ai/stripe";
9179
+ import { stripeRequest as stripeRequest8 } from "@odla-ai/stripe";
8840
9180
  var row = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
8841
- var id2 = (value) => typeof value === "string" ? value : String(row(value).id ?? "");
9181
+ var id3 = (value) => typeof value === "string" ? value : String(row(value).id ?? "");
8842
9182
  var providerId2 = (value, prefix) => new RegExp(`^${prefix}_[A-Za-z0-9]+$`).test(value);
8843
- function fail6(code = "refund_target_mismatch") {
9183
+ function fail7(code = "refund_target_mismatch") {
8844
9184
  throw Object.assign(new Error(code), { code });
8845
9185
  }
8846
9186
  async function refundSubscriptionPayment(secretKey, input) {
8847
- if (!providerId2(input.customerId, "cus") || !providerId2(input.subscriptionId, "sub")) fail6();
9187
+ if (!providerId2(input.customerId, "cus") || !providerId2(input.subscriptionId, "sub")) fail7();
8848
9188
  const call = async (method, path, params, key3) => {
8849
- const result = await stripeRequest7({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
8850
- if (!result.ok) fail6("stripe_error");
8851
- if (!result.body || typeof result.body !== "object" || Array.isArray(result.body)) fail6("stripe_invalid_response");
9189
+ const result = await stripeRequest8({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
9190
+ if (!result.ok) fail7("stripe_error");
9191
+ if (!result.body || typeof result.body !== "object" || Array.isArray(result.body)) fail7("stripe_invalid_response");
8852
9192
  return result.body;
8853
9193
  };
8854
9194
  const sub = await call("GET", `/v1/subscriptions/${input.subscriptionId}`);
8855
- if (id2(sub) !== input.subscriptionId || id2(sub.customer) !== input.customerId) fail6();
9195
+ if (id3(sub) !== input.subscriptionId || id3(sub.customer) !== input.customerId) fail7();
8856
9196
  for (const [key3, value] of Object.entries(input.metadata ?? {})) {
8857
- if (row(sub.metadata)[key3] !== value) fail6();
9197
+ if (row(sub.metadata)[key3] !== value) fail7();
8858
9198
  }
8859
9199
  let cursor;
8860
9200
  let earliest;
@@ -8867,79 +9207,79 @@ async function refundSubscriptionPayment(secretKey, input) {
8867
9207
  limit: 100,
8868
9208
  ...cursor ? { starting_after: cursor } : {}
8869
9209
  });
8870
- if (!Array.isArray(list.data) || typeof list.has_more !== "boolean") fail6("stripe_invalid_response");
9210
+ if (!Array.isArray(list.data) || typeof list.has_more !== "boolean") fail7("stripe_invalid_response");
8871
9211
  for (const value of list.data) {
8872
9212
  const invoice2 = row(value);
8873
9213
  const subscription = invoice2.subscription ?? row(row(invoice2.parent).subscription_details).subscription;
8874
- 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();
8875
- seen.add(id2(invoice2));
9214
+ 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();
9215
+ seen.add(id3(invoice2));
8876
9216
  if (Number(invoice2.amount_paid) > 0 && (!earliest || Number(invoice2.created) < Number(earliest.created))) earliest = invoice2;
8877
9217
  }
8878
9218
  if (!list.has_more) break;
8879
- if (!list.data.length || page === 9) fail6("refund_history_incomplete");
8880
- cursor = id2(list.data[list.data.length - 1]);
9219
+ if (!list.data.length || page === 9) fail7("refund_history_incomplete");
9220
+ cursor = id3(list.data[list.data.length - 1]);
8881
9221
  }
8882
- if (!earliest) fail6("no_charge");
9222
+ if (!earliest) fail7("no_charge");
8883
9223
  const invoice = earliest;
8884
- if (input.expectedInvoiceId !== void 0 && id2(invoice) !== input.expectedInvoiceId || input.expectedAmountCents !== void 0 && invoice.amount_paid !== input.expectedAmountCents) fail6();
8885
- let chargeId = id2(invoice.charge);
8886
- let paymentIntentId = id2(invoice.payment_intent);
9224
+ if (input.expectedInvoiceId !== void 0 && id3(invoice) !== input.expectedInvoiceId || input.expectedAmountCents !== void 0 && invoice.amount_paid !== input.expectedAmountCents) fail7();
9225
+ let chargeId = id3(invoice.charge);
9226
+ let paymentIntentId = id3(invoice.payment_intent);
8887
9227
  if (!chargeId && !paymentIntentId) {
8888
- const payments = await call("GET", "/v1/invoice_payments", { invoice: id2(invoice), status: "paid", limit: 100 });
8889
- if (payments.has_more !== false || !Array.isArray(payments.data) || payments.data.length !== 1) fail6("refund_payment_ambiguous");
9228
+ const payments = await call("GET", "/v1/invoice_payments", { invoice: id3(invoice), status: "paid", limit: 100 });
9229
+ if (payments.has_more !== false || !Array.isArray(payments.data) || payments.data.length !== 1) fail7("refund_payment_ambiguous");
8890
9230
  const payment = row(payments.data[0]);
8891
- if (id2(payment.invoice) !== id2(invoice) || payment.status !== "paid" || row(payment.payment).type !== "payment_intent") fail6();
8892
- paymentIntentId = id2(row(payment.payment).payment_intent);
9231
+ if (id3(payment.invoice) !== id3(invoice) || payment.status !== "paid" || row(payment.payment).type !== "payment_intent") fail7();
9232
+ paymentIntentId = id3(row(payment.payment).payment_intent);
8893
9233
  }
8894
9234
  if (!chargeId) {
8895
- if (!providerId2(paymentIntentId, "pi")) fail6();
9235
+ if (!providerId2(paymentIntentId, "pi")) fail7();
8896
9236
  const payment = await call("GET", `/v1/payment_intents/${paymentIntentId}`);
8897
- if (id2(payment) !== paymentIntentId || id2(payment.customer) !== input.customerId || payment.status !== "succeeded") fail6();
8898
- chargeId = id2(payment.latest_charge);
9237
+ if (id3(payment) !== paymentIntentId || id3(payment.customer) !== input.customerId || payment.status !== "succeeded") fail7();
9238
+ chargeId = id3(payment.latest_charge);
8899
9239
  }
8900
- if (!providerId2(chargeId, "ch")) fail6();
9240
+ if (!providerId2(chargeId, "ch")) fail7();
8901
9241
  const charge = await call("GET", `/v1/charges/${chargeId}`);
8902
- 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();
8903
- if (input.alreadyRefundedChargeId !== void 0 && (chargeId !== input.alreadyRefundedChargeId || charge.amount_refunded !== charge.amount || charge.refunded !== true)) fail6();
9242
+ 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();
9243
+ if (input.alreadyRefundedChargeId !== void 0 && (chargeId !== input.alreadyRefundedChargeId || charge.amount_refunded !== charge.amount || charge.refunded !== true)) fail7();
8904
9244
  if (charge.amount_refunded !== charge.amount) {
8905
9245
  const refund = await call("POST", "/v1/refunds", {
8906
9246
  charge: chargeId,
8907
9247
  ...input.metadata ? { metadata: input.metadata } : {}
8908
9248
  }, `chapter-initial-refund:${input.subscriptionId}:${chargeId}`);
8909
- 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");
9249
+ 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");
8910
9250
  }
8911
9251
  let subscriptionCanceled = sub.status === "canceled";
8912
9252
  if (input.cancelSubscription !== false && !subscriptionCanceled) {
8913
9253
  const canceled = await call("DELETE", `/v1/subscriptions/${input.subscriptionId}`);
8914
- if (id2(canceled) !== input.subscriptionId || canceled.status !== "canceled") fail6("refund_cancel_incomplete");
9254
+ if (id3(canceled) !== input.subscriptionId || canceled.status !== "canceled") fail7("refund_cancel_incomplete");
8915
9255
  subscriptionCanceled = true;
8916
9256
  }
8917
9257
  const confirmed = await call("GET", `/v1/charges/${chargeId}`);
8918
- 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");
9258
+ 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");
8919
9259
  return { refundedCents: Number(charge.amount), subscriptionCanceled };
8920
9260
  }
8921
9261
 
8922
9262
  // src/subscription-payment-identity.ts
8923
- import { stripeRequest as stripeRequest8 } from "@odla-ai/stripe";
8924
- var id3 = (value) => typeof value === "string" ? value : String(value?.id ?? "");
9263
+ import { stripeRequest as stripeRequest9 } from "@odla-ai/stripe";
9264
+ var id4 = (value) => typeof value === "string" ? value : String(value?.id ?? "");
8925
9265
  async function tagSubscriptionPayment(secretKey, invoice, customerId, metadata, clientSecret2, applicationId) {
8926
- let paymentId = id3(invoice.payment_intent);
9266
+ let paymentId = id4(invoice.payment_intent);
8927
9267
  if (!paymentId) {
8928
- if (!/^in_[A-Za-z0-9]+$/.test(id3(invoice))) subscriptionContractMismatch();
8929
- const result2 = await stripeRequest8({ secretKey }, "GET", "/v1/invoice_payments", { invoice: id3(invoice), limit: 100 });
9268
+ if (!/^in_[A-Za-z0-9]+$/.test(id4(invoice))) subscriptionContractMismatch();
9269
+ const result2 = await stripeRequest9({ secretKey }, "GET", "/v1/invoice_payments", { invoice: id4(invoice), limit: 100 });
8930
9270
  const list = result2.body;
8931
9271
  if (!result2.ok || list?.has_more !== false || !Array.isArray(list.data) || list.data.length !== 1) subscriptionContractMismatch();
8932
9272
  const link = list.data[0];
8933
- if (id3(link.invoice) !== id3(invoice) || !["open", "paid"].includes(link.status) || link.payment?.type !== "payment_intent") subscriptionContractMismatch();
8934
- paymentId = id3(link.payment.payment_intent);
9273
+ if (id4(link.invoice) !== id4(invoice) || !["open", "paid"].includes(link.status) || link.payment?.type !== "payment_intent") subscriptionContractMismatch();
9274
+ paymentId = id4(link.payment.payment_intent);
8935
9275
  }
8936
9276
  if (!/^pi_[A-Za-z0-9]+$/.test(paymentId)) subscriptionContractMismatch();
8937
9277
  const path = `/v1/payment_intents/${paymentId}`;
8938
- const result = await stripeRequest8({ secretKey }, "GET", path);
9278
+ const result = await stripeRequest9({ secretKey }, "GET", path);
8939
9279
  const payment = result.body;
8940
- 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();
9280
+ 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();
8941
9281
  if (Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === value)) return;
8942
- const tagged = await stripeRequest8(
9282
+ const tagged = await stripeRequest9(
8943
9283
  { secretKey },
8944
9284
  "POST",
8945
9285
  path,
@@ -8951,7 +9291,7 @@ async function tagSubscriptionPayment(secretKey, invoice, customerId, metadata,
8951
9291
 
8952
9292
  // src/payments-stripe.ts
8953
9293
  async function stripeCall(sk, method, path, params, idempotencyKey) {
8954
- return stripeRequest9(
9294
+ return stripeRequest10(
8955
9295
  { secretKey: sk },
8956
9296
  method,
8957
9297
  path,
@@ -8959,7 +9299,7 @@ async function stripeCall(sk, method, path, params, idempotencyKey) {
8959
9299
  idempotencyKey ? { idempotencyKey } : {}
8960
9300
  );
8961
9301
  }
8962
- function fail7(op, r) {
9302
+ function fail8(op, r) {
8963
9303
  const err = new Error(`stripe ${op} failed: ${r.status}`);
8964
9304
  err.code = "stripe_error";
8965
9305
  throw err;
@@ -8998,6 +9338,15 @@ function createStripeProvider(config) {
8998
9338
  let customerId = input.existingCustomerId;
8999
9339
  if (customerId && !/^cus_[A-Za-z0-9]+$/.test(customerId) || input.existingSubscriptionId && (!customerId || !/^sub_[A-Za-z0-9]+$/.test(input.existingSubscriptionId))) subscriptionContractMismatch();
9000
9340
  const productId = await verifySubscriptionPrice(sk, input, Boolean(input.existingSubscriptionId));
9341
+ const seat = input.namedSeat?.amountCents ? input.namedSeat : void 0;
9342
+ const seatProductId = seat ? await verifySubscriptionPrice(sk, {
9343
+ ...input,
9344
+ priceId: seat.priceId,
9345
+ priceCents: seat.amountCents,
9346
+ billingTerms: void 0,
9347
+ namedSeat: void 0
9348
+ }, Boolean(input.existingSubscriptionId)) : void 0;
9349
+ if (seat && (!seatProductId || !productId || seatProductId === productId)) subscriptionContractMismatch();
9001
9350
  if (input.billingTerms?.discount && !productId) subscriptionContractMismatch();
9002
9351
  await prepareSubscriptionBillingTerms(sk, { ...input, discountProductId: productId }, Boolean(input.existingSubscriptionId));
9003
9352
  const billingParams = subscriptionBillingParams(input, productId);
@@ -9009,7 +9358,7 @@ function createStripeProvider(config) {
9009
9358
  { email: input.email, name: input.name, metadata: meta },
9010
9359
  `cus:${input.applicationId}`
9011
9360
  );
9012
- if (!cust.ok) fail7("customer create", cust);
9361
+ if (!cust.ok) fail8("customer create", cust);
9013
9362
  if (typeof cust.body.id !== "string" || !/^cus_[A-Za-z0-9]+$/.test(cust.body.id)) subscriptionContractMismatch();
9014
9363
  customerId = cust.body.id;
9015
9364
  }
@@ -9029,19 +9378,32 @@ function createStripeProvider(config) {
9029
9378
  {
9030
9379
  customer: customerId,
9031
9380
  "items[0][price]": input.priceId,
9381
+ ...seat ? {
9382
+ "items[1][price]": seat.priceId,
9383
+ "items[1][quantity]": 1,
9384
+ "items[1][metadata][seatId]": seat.seatId,
9385
+ "items[1][metadata][membershipId]": seat.membershipId
9386
+ } : {},
9032
9387
  payment_behavior: "default_incomplete",
9033
9388
  "payment_settings[save_default_payment_method]": "on_subscription",
9034
9389
  "payment_settings[payment_method_types][0]": "card",
9035
9390
  "expand[0]": "latest_invoice.confirmation_secret",
9036
9391
  "expand[1]": "latest_invoice.payment_intent",
9037
9392
  metadata: meta,
9038
- ...billingParams
9393
+ ...billingParams,
9394
+ ...seat && input.billingTerms?.initialTermEndsAt ? {
9395
+ "add_invoice_items[1][price_data][product]": seatProductId,
9396
+ "add_invoice_items[1][price_data][currency]": input.currency,
9397
+ "add_invoice_items[1][price_data][unit_amount]": seat.amountCents,
9398
+ "add_invoice_items[1][quantity]": 1,
9399
+ "add_invoice_items[1][discountable]": false
9400
+ } : {}
9039
9401
  },
9040
9402
  // Hardening: one subscription per application, so a client
9041
9403
  // retry between create and the db write can't orphan a second one.
9042
9404
  `sub:${input.applicationId}${input.purchaseAttemptId ? `:${input.purchaseAttemptId}` : ""}`
9043
9405
  );
9044
- if (!sub.ok) fail7("subscription create", sub);
9406
+ if (!sub.ok) fail8("subscription create", sub);
9045
9407
  if (input.billingTerms?.discount?.duration === "once" && Array.isArray(sub.body.discounts) && sub.body.discounts.length === 0) {
9046
9408
  const subscriptionId = sub.body.id;
9047
9409
  if (typeof subscriptionId !== "string" || !/^sub_[A-Za-z0-9]+$/.test(subscriptionId)) subscriptionContractMismatch();
@@ -9051,14 +9413,14 @@ function createStripeProvider(config) {
9051
9413
  "expand[2]": "discounts",
9052
9414
  "expand[3]": "latest_invoice.discounts"
9053
9415
  });
9054
- if (!expanded.ok) fail7("subscription invoice evidence", expanded);
9416
+ if (!expanded.ok) fail8("subscription invoice evidence", expanded);
9055
9417
  if (expanded.body.id !== subscriptionId) subscriptionContractMismatch();
9056
9418
  sub = expanded;
9057
9419
  }
9058
9420
  verifySubscriptionResult(sub.body, input, customerId, meta);
9059
9421
  const invoice = sub.body.latest_invoice;
9060
9422
  const clientSecret2 = clientSecretOf(sub.body);
9061
- if (!clientSecret2) fail7("subscription confirmation-secret missing", sub);
9423
+ if (!clientSecret2) fail8("subscription confirmation-secret missing", sub);
9062
9424
  await tagSubscriptionPayment(
9063
9425
  sk,
9064
9426
  invoice,
@@ -9270,9 +9632,9 @@ function matchesIntent(stored, expected) {
9270
9632
  "purchaseAttemptId"
9271
9633
  ].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;
9272
9634
  }
9273
- async function loadIntent(db, id6) {
9635
+ async function loadIntent(db, id7) {
9274
9636
  return first3((await db.query({
9275
- subscriptionCheckoutIntents: { $: { where: { id: id6 }, limit: 1 } }
9637
+ subscriptionCheckoutIntents: { $: { where: { id: id7 }, limit: 1 } }
9276
9638
  })).subscriptionCheckoutIntents);
9277
9639
  }
9278
9640
  async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCheckout) {
@@ -9293,16 +9655,22 @@ async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCh
9293
9655
  if (input.purchaseAttemptId !== application?.membershipRestartId) mismatch();
9294
9656
  if (application?.namedSeatId || application?.namedSeatClaimPending || application?.giftClaimId) mismatch();
9295
9657
  if (input.membershipOffer && (!application || application.canceled === true || application.status !== "submitted")) mismatch();
9658
+ if (input.paymentQuote?.namedSeat && application?.email !== input.email) mismatch();
9296
9659
  const applicationGuard = {
9297
9660
  ns: "applications",
9298
9661
  id: input.applicationId,
9299
9662
  exists: true,
9300
- equals: { status: "submitted", ...input.purchaseAttemptId ? { membershipRestartId: input.purchaseAttemptId } : {}, ...input.membershipOffer ? {
9301
- groupId: input.groupId,
9302
- tierId: input.tierId,
9303
- stripeRuntime: stripeRuntimeIdentity(input.runtime),
9304
- ...application?.canceled !== void 0 ? { canceled: application.canceled } : {}
9305
- } : {} },
9663
+ equals: {
9664
+ status: "submitted",
9665
+ ...input.paymentQuote?.namedSeat ? { email: input.email } : {},
9666
+ ...input.purchaseAttemptId ? { membershipRestartId: input.purchaseAttemptId } : {},
9667
+ ...input.membershipOffer ? {
9668
+ groupId: input.groupId,
9669
+ tierId: input.tierId,
9670
+ stripeRuntime: stripeRuntimeIdentity(input.runtime),
9671
+ ...application?.canceled !== void 0 ? { canceled: application.canceled } : {}
9672
+ } : {}
9673
+ },
9306
9674
  absent: ["namedSeatId", "namedSeatClaimPending", "giftClaimId", ...!input.purchaseAttemptId ? ["membershipRestartId"] : [], ...input.membershipOffer && application?.canceled === void 0 ? ["canceled"] : []]
9307
9675
  };
9308
9676
  let stored = await loadIntent(db, input.applicationId);
@@ -9326,7 +9694,11 @@ async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCh
9326
9694
  ns: MEMBERSHIP_QUOTE_NAMESPACE,
9327
9695
  id: input.applicationId,
9328
9696
  exists: true,
9329
- equals: { status: "ready", offerDigest: input.membershipOffer.digest }
9697
+ equals: {
9698
+ status: "ready",
9699
+ offerDigest: input.membershipOffer.digest,
9700
+ ...input.projectionRevision !== void 0 ? { revision: input.projectionRevision } : {}
9701
+ }
9330
9702
  }
9331
9703
  ] : []
9332
9704
  ]
@@ -9341,7 +9713,8 @@ async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCh
9341
9713
  if (!stored.providerSubscriptionId && input.now - Number(stored.createdAt) >= 23 * 60 * 6e4) {
9342
9714
  throw Object.assign(new Error("checkout requires provider reconciliation"), { code: "checkout_recovery_required" });
9343
9715
  }
9344
- if (input.membershipOffer) await authorizeNetworkCheckout();
9716
+ const binding = input.membershipOffer ? await authorizeNetworkCheckout() : void 0;
9717
+ 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();
9345
9718
  const result = await provider.createSubscription({
9346
9719
  appId: input.appId,
9347
9720
  chapterId: input.chapterId,
@@ -9356,6 +9729,7 @@ async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCh
9356
9729
  priceCents: input.priceCents,
9357
9730
  currency: input.currency,
9358
9731
  interval: input.interval,
9732
+ ...binding?.namedSeat ? { namedSeat: binding.namedSeat } : {},
9359
9733
  ...stored.billingTerms ? { billingTerms: stored.billingTerms } : {},
9360
9734
  ...typeof stored.providerSubscriptionId === "string" ? { existingSubscriptionId: stored.providerSubscriptionId } : {},
9361
9735
  existingCustomerId: typeof stored.providerCustomerId === "string" ? stored.providerCustomerId : input.existingCustomerId
@@ -9472,6 +9846,7 @@ async function startSubscription(req, env, ctx) {
9472
9846
  return json({ error: "payment quote unavailable", ...expired ? { code: "payment_quote_changed" } : {} }, expired ? 409 : 503);
9473
9847
  }
9474
9848
  const { tier, quote, billingTerms } = checkout2;
9849
+ if (quote.namedSeat && body.seatTermsAck !== true) return json({ error: "seatTermsAck required" }, 400);
9475
9850
  const membershipOffer = "membershipOffer" in checkout2 ? checkout2.membershipOffer : void 0;
9476
9851
  if (ctx.membershipAuthority && !membershipOffer) return json({ error: "checkout requires reconciliation", code: "checkout_recovery_required" }, 409);
9477
9852
  if (!tier) return json({ error: "tier not found", code: "tier_unresolved" }, 400);
@@ -9496,6 +9871,7 @@ async function startSubscription(req, env, ctx) {
9496
9871
  priceCents: billingTerms ? tier.priceCents : quote.lineItems.dueTodayCents,
9497
9872
  ...billingTerms ? { billingTerms } : {},
9498
9873
  ...membershipOffer ? { membershipOffer } : {},
9874
+ ..."projectionRevision" in checkout2 ? { projectionRevision: checkout2.projectionRevision } : {},
9499
9875
  ...ctx.requirePaymentQuote || ctx.membershipAuthority || body.quoteDigest ? { paymentQuote: quote } : {},
9500
9876
  currency: tier.currency ?? String(group.currency ?? "usd"),
9501
9877
  interval: tier.interval ?? (group.interval === "month" ? "month" : "year"),
@@ -9517,9 +9893,10 @@ async function startSubscription(req, env, ctx) {
9517
9893
  offerId: membershipOffer.id,
9518
9894
  offerDigest: membershipOffer.digest,
9519
9895
  quoteDigest: quote.digest,
9520
- priceId: String(priceId)
9896
+ priceId: String(priceId),
9897
+ ...quote.namedSeat ? { namedSeat: { ...quote.namedSeat, purchaserEmail: normalizeChapterEmail(String(app.email ?? "")) } } : {}
9521
9898
  };
9522
- await verifyMembershipCheckoutBinding(await ctx.membershipAuthority.bindCheckout(binding, env), binding);
9899
+ return verifyMembershipCheckoutBinding(await ctx.membershipAuthority.bindCheckout(binding, env), binding);
9523
9900
  } : void 0);
9524
9901
  } catch (err) {
9525
9902
  const code = paymentErrorCode(err);
@@ -9541,7 +9918,7 @@ async function ingestWebhook(req, env, ctx) {
9541
9918
  var handlePayments = async (req, url, env, ctx) => {
9542
9919
  if (ctx.chapter.mode !== "chapter") return null;
9543
9920
  if (req.method === "GET" && url.pathname === "/api/payments/quote") return paymentQuoteRoute(url, env, ctx);
9544
- if (req.method === "POST" && url.pathname === "/api/payments/quote") return paymentQuoteRoute(url, env, ctx, true);
9921
+ if (req.method === "POST" && url.pathname === "/api/payments/quote") return paymentQuoteRoute(url, env, ctx, true, req);
9545
9922
  if (req.method === "POST" && url.pathname === "/api/payments/subscription") return startSubscription(req, env, ctx);
9546
9923
  if (req.method === "POST" && url.pathname === "/api/webhooks/stripe") return ingestWebhook(req, env, ctx);
9547
9924
  return null;
@@ -9555,16 +9932,16 @@ var userFor = async (req, env, ctx) => {
9555
9932
  const user = await ctx.verifyUser(req, env);
9556
9933
  return user?.email ? user : null;
9557
9934
  };
9558
- var loadClaim = async (db, id6) => first4((await db.query({ giftClaims: { $: { where: { id: id6 }, limit: 1 } } })).giftClaims);
9935
+ var loadClaim = async (db, id7) => first4((await db.query({ giftClaims: { $: { where: { id: id7 }, limit: 1 } } })).giftClaims);
9559
9936
  async function consumeRateLimit(db, chapterId, requester, now) {
9560
9937
  const bucket = await giftRequesterBucket(chapterId, requester);
9561
- const id6 = `gift-rate:${chapterId}:${bucket}`;
9562
- const current = first4((await db.query({ giftRedemptionAttempts: { $: { where: { id: id6 }, limit: 1 } } })).giftRedemptionAttempts);
9938
+ const id7 = `gift-rate:${chapterId}:${bucket}`;
9939
+ const current = first4((await db.query({ giftRedemptionAttempts: { $: { where: { id: id7 }, limit: 1 } } })).giftRedemptionAttempts);
9563
9940
  const within = Boolean(current && now - Number(current.windowStartedAt) < 15 * 6e4);
9564
9941
  const attemptCount = within ? Number(current?.attemptCount ?? 0) + 1 : 1;
9565
9942
  if (attemptCount > 10) return false;
9566
- await db.transact([{ t: "update", ns: "giftRedemptionAttempts", id: id6, attrs: {
9567
- id: id6,
9943
+ await db.transact([{ t: "update", ns: "giftRedemptionAttempts", id: id7, attrs: {
9944
+ id: id7,
9568
9945
  chapterId,
9569
9946
  bucket,
9570
9947
  attemptCount,
@@ -9738,14 +10115,14 @@ var at = (value) => Number.isSafeInteger(value) ? Number(value) : void 0;
9738
10115
  async function readNamedSeatOffer(db, input, now) {
9739
10116
  membershipClock(now);
9740
10117
  await loadMembershipAuthorityState(db, input);
9741
- const id6 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id6);
10118
+ const id7 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id7);
9742
10119
  requireMembershipScope(member, input);
9743
10120
  const offering = membershipOfferingVersion(member.offeringSnapshot);
9744
10121
  const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
9745
10122
  if (seat) requireMembershipScope(seat, input);
9746
10123
  const purchaseEndsAt = seatPurchaseDeadline(offering, member);
9747
10124
  const offer = {
9748
- membershipId: id6,
10125
+ membershipId: id7,
9749
10126
  amountCents: offering.seatPriceCents,
9750
10127
  currency: offering.currency,
9751
10128
  interval: offering.interval,
@@ -10114,18 +10491,18 @@ var handleEmailDeliveryResolution = async (req, url, env, ctx) => {
10114
10491
  if (!user) return json({ error: "unauthorized" }, 401);
10115
10492
  if (!await ctx.isAdmin(db, user)) return json({ error: "forbidden" }, 403);
10116
10493
  try {
10117
- const id6 = decodeURIComponent(match[1]);
10494
+ const id7 = decodeURIComponent(match[1]);
10118
10495
  const body = await namedSeatRequest(req);
10119
10496
  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))) {
10120
10497
  return json({ error: "invalid delivery evidence" }, 400);
10121
10498
  }
10122
- const chapterDb = db, row2 = await membershipLoad(chapterDb, "emailLog", id6);
10499
+ const chapterDb = db, row2 = await membershipLoad(chapterDb, "emailLog", id7);
10123
10500
  if (!row2 || row2.groupId !== ctx.chapter.id) return json({ error: "not found" }, 404);
10124
10501
  if (row2.applicationId) {
10125
10502
  const app = await membershipLoad(chapterDb, "applications", String(row2.applicationId));
10126
10503
  if (!app || !chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME)) return json({ error: "not found" }, 404);
10127
10504
  }
10128
- await reconcileLifecycleDelivery(chapterDb, { ...body, id: id6, groupId: ctx.chapter.id, actorId: user.userId }, Date.now());
10505
+ await reconcileLifecycleDelivery(chapterDb, { ...body, id: id7, groupId: ctx.chapter.id, actorId: user.userId }, Date.now());
10129
10506
  return json({ ok: true, sent: false });
10130
10507
  } catch {
10131
10508
  return json({ error: "delivery evidence is invalid, stale, or still in progress; reload the exact attempt" }, 409);
@@ -10228,13 +10605,13 @@ var handlePeopleProfile = async (req, url, env, ctx) => {
10228
10605
  const user = await ctx.verifyUser(req, env);
10229
10606
  if (!user) return json({ error: "unauthorized" }, 401);
10230
10607
  if (!await ctx.isAdmin(db, user)) return json({ error: "forbidden" }, 403);
10231
- let id6;
10608
+ let id7;
10232
10609
  try {
10233
- id6 = dedicated ? url.searchParams.get("recordId") ?? "" : decodeURIComponent(match[1]);
10610
+ id7 = dedicated ? url.searchParams.get("recordId") ?? "" : decodeURIComponent(match[1]);
10234
10611
  } catch {
10235
10612
  return json({ error: "invalid record id" }, 422);
10236
10613
  }
10237
- const record4 = await getRecord3({ crm: ctx.chapter.crm, db }, id6);
10614
+ const record4 = await getRecord3({ crm: ctx.chapter.crm, db }, id7);
10238
10615
  if (!record4) return json({ error: "not found" }, 404);
10239
10616
  if (record4.type !== "person") return dedicated ? json({ error: "person required" }, 422) : null;
10240
10617
  if (match?.[2]) {
@@ -10249,9 +10626,9 @@ var handlePeopleProfile = async (req, url, env, ctx) => {
10249
10626
  }
10250
10627
  if (req.method === "GET" && dedicated) {
10251
10628
  const excluded = profileReadOnlyFields(record4, ctx);
10252
- const fields = Object.entries(ctx.chapter.crm.type(record4.type).fields).filter(([id7]) => !excluded.includes(id7)).map(([id7, field]) => ({
10253
- id: id7,
10254
- label: field.label ?? id7,
10629
+ const fields = Object.entries(ctx.chapter.crm.type(record4.type).fields).filter(([id8]) => !excluded.includes(id8)).map(([id8, field]) => ({
10630
+ id: id8,
10631
+ label: field.label ?? id8,
10255
10632
  type: field.type,
10256
10633
  required: field.required ?? false,
10257
10634
  ...field.options ? { options: field.options } : {}
@@ -11022,12 +11399,12 @@ var readJson = async (req) => {
11022
11399
  return null;
11023
11400
  }
11024
11401
  };
11025
- async function loadApp(db, id6) {
11026
- const { applications } = await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } });
11402
+ async function loadApp(db, id7) {
11403
+ const { applications } = await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } });
11027
11404
  return applications?.[0] ?? null;
11028
11405
  }
11029
- async function loadGroup(db, id6) {
11030
- const { groups } = await db.query({ groups: { $: { where: { id: id6 }, limit: 1 } } });
11406
+ async function loadGroup(db, id7) {
11407
+ const { groups } = await db.query({ groups: { $: { where: { id: id7 }, limit: 1 } } });
11031
11408
  return groups?.[0] ?? null;
11032
11409
  }
11033
11410
  var handleAdminApplicationActions = async (req, url, env, ctx) => {
@@ -11113,8 +11490,8 @@ var handleAdminApprove = async (req, url, env, ctx) => {
11113
11490
  const got = await gate4(req, env, ctx);
11114
11491
  if (got instanceof Response) return got;
11115
11492
  const db = got;
11116
- const id6 = m[1];
11117
- const app = await loadApp(db, id6);
11493
+ const id7 = m[1];
11494
+ const app = await loadApp(db, id7);
11118
11495
  if (!app) return json({ error: "not found" }, 404);
11119
11496
  const result = await approveApplication({ db, env, ctx, app, origin: url.origin, reason: "manual" });
11120
11497
  if (!result.ok) return json({ error: result.error }, result.status);
@@ -11143,10 +11520,10 @@ var handleAdminApplicationPatch = async (req, url, env, ctx) => {
11143
11520
  const got = await gate4(req, env, ctx);
11144
11521
  if (got instanceof Response) return got;
11145
11522
  const db = got;
11146
- const id6 = m[1];
11523
+ const id7 = m[1];
11147
11524
  const body = await readJson(req);
11148
11525
  if (!body) return json({ error: "invalid JSON body" }, 400);
11149
- const app = await loadApp(db, id6);
11526
+ const app = await loadApp(db, id7);
11150
11527
  if (!app) return json({ error: "not found" }, 404);
11151
11528
  return patchApplication(db, env, ctx, app, body);
11152
11529
  };
@@ -11256,10 +11633,10 @@ async function updateEmailSettings(db, ctx, env, body) {
11256
11633
  const missing = Object.fromEntries(Object.keys(events).filter((key3) => !group?.emailTemplates?.[key3]).map((key3) => [key3, { subject: "", text: "", enabled: false }]));
11257
11634
  await saveConfig({ db, crm: ctx.chapter.crm }, { emailTemplates: { ...missing, ...clean }, notificationEmail, replyTo, debugEmail });
11258
11635
  } else {
11259
- const id6 = { ns: "groups", attr: "id", value: ctx.chapter.id };
11636
+ const id7 = { ns: "groups", attr: "id", value: ctx.chapter.id };
11260
11637
  await db.transact([
11261
- { t: "merge", ns: "groups", id: id6, attrs: { emailTemplates: clean } },
11262
- { t: "update", ns: "groups", id: id6, attrs: { commitmentText, normsText, notificationEmail, replyTo, debugEmail, fromName } }
11638
+ { t: "merge", ns: "groups", id: id7, attrs: { emailTemplates: clean } },
11639
+ { t: "update", ns: "groups", id: id7, attrs: { commitmentText, normsText, notificationEmail, replyTo, debugEmail, fromName } }
11263
11640
  ]);
11264
11641
  }
11265
11642
  return json({ ok: true });
@@ -11274,8 +11651,8 @@ async function gate5(req, env, ctx) {
11274
11651
  if (!await ctx.isAdmin(rawDb, u)) return json({ error: "forbidden" }, 403);
11275
11652
  return rawDb;
11276
11653
  }
11277
- async function loadGroup2(db, id6) {
11278
- const { groups } = await db.query({ groups: { $: { where: { id: id6 }, limit: 1 } } });
11654
+ async function loadGroup2(db, id7) {
11655
+ const { groups } = await db.query({ groups: { $: { where: { id: id7 }, limit: 1 } } });
11279
11656
  return groups?.[0] ?? null;
11280
11657
  }
11281
11658
  var str3 = (v, d = "") => typeof v === "string" ? v : d;
@@ -11529,7 +11906,7 @@ var handleAdminNetworkTargets = async (req, url, env, ctx) => {
11529
11906
  if ("response" in got) return got.response;
11530
11907
  return json({
11531
11908
  targets: ctx.chapter.network.targets.map(({
11532
- id: id6,
11909
+ id: id7,
11533
11910
  name,
11534
11911
  url: targetUrl,
11535
11912
  fields,
@@ -11538,7 +11915,7 @@ var handleAdminNetworkTargets = async (req, url, env, ctx) => {
11538
11915
  stageTransitions,
11539
11916
  commercialParity
11540
11917
  }) => ({
11541
- id: id6,
11918
+ id: id7,
11542
11919
  name,
11543
11920
  url: targetUrl,
11544
11921
  types: Object.keys(fields ?? DEFAULT_SHARE_FIELDS),
@@ -11700,7 +12077,7 @@ var handleAdminNetworkPush = async (req, url, env, ctx) => {
11700
12077
  if (typeof body.recordId !== "string" || !Array.isArray(body.targetIds) || body.targetIds.length === 0) {
11701
12078
  return json({ error: "recordId and a non-empty targetIds array are required" }, 400);
11702
12079
  }
11703
- const requested = new Set(body.targetIds.filter((id6) => typeof id6 === "string"));
12080
+ const requested = new Set(body.targetIds.filter((id7) => typeof id7 === "string"));
11704
12081
  if (requested.size !== body.targetIds.length) return json({ error: "targetIds must contain unique strings" }, 400);
11705
12082
  const targets = ctx.chapter.network.targets.filter((target) => requested.has(target.id));
11706
12083
  if (targets.length !== requested.size) return json({ error: "one or more targetIds are not configured" }, 400);
@@ -11833,7 +12210,7 @@ async function operationalReaderRecord(record4, reader, db, env, ctx) {
11833
12210
  const appId = record4.fields.applicationId;
11834
12211
  const app = typeof appId === "string" ? (await db.query({ applications: { $: { where: { id: appId }, limit: 1 } } })).applications?.[0] : void 0;
11835
12212
  if (reader.stageTransitions.includes(record4.type)) {
11836
- result.edit.allowedStages = app ? applicationActionStages(app, env, ctx).map((id6) => ({ id: id6, label: id6.replaceAll("_", " ") })) : result.edit.allowedStages.filter((stage) => !isApplicationStage(ctx, stage.id));
12213
+ result.edit.allowedStages = app ? applicationActionStages(app, env, ctx).map((id7) => ({ id: id7, label: id7.replaceAll("_", " ") })) : result.edit.allowedStages.filter((stage) => !isApplicationStage(ctx, stage.id));
11837
12214
  const lock = (await db.query({ crm_activity: { $: { where: { id: `network-action-lock:${record4.id}` }, limit: 1 } } })).crm_activity?.[0];
11838
12215
  const meta = lock?.meta;
11839
12216
  if (meta?.state === "pending" && typeof meta.activityId === "string") {
@@ -11938,9 +12315,9 @@ async function listReaderRecords(db, type, allowed2, emailField, window) {
11938
12315
  // src/network-mutation-receipt.ts
11939
12316
  import { CRM_NS as CRM_NS2 } from "@odla-ai/crm";
11940
12317
  var NETWORK_MUTATION_META = "chapterNetworkMutation";
11941
- async function priorMutation(db, id6) {
12318
+ async function priorMutation(db, id7) {
11942
12319
  const result = await db.query({
11943
- [CRM_NS2.activity]: { $: { where: { id: id6 }, limit: 1 } }
12320
+ [CRM_NS2.activity]: { $: { where: { id: id7 }, limit: 1 } }
11944
12321
  });
11945
12322
  const row2 = (result[CRM_NS2.activity] ?? [])[0];
11946
12323
  const meta = row2?.meta;
@@ -12200,8 +12577,8 @@ async function gate7(req, env, ctx) {
12200
12577
  if (!await networkWriteAllowed(req, db, user, ctx)) return { response: json({ error: "super-admin required", code: "super_admin_required" }, 403) };
12201
12578
  return { db, user };
12202
12579
  }
12203
- function targetFor2(ctx, id6) {
12204
- return ctx.chapter.network.targets.find((target) => target.id === id6);
12580
+ function targetFor2(ctx, id7) {
12581
+ return ctx.chapter.network.targets.find((target) => target.id === id7);
12205
12582
  }
12206
12583
  async function readTargetRecords(db, env, ctx, target, query) {
12207
12584
  const secret = await getVaultSecret(db, target.secretName);
@@ -12336,7 +12713,7 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
12336
12713
  if (ctx.chapter.network.requireSuperAdminWrites && !await ctx.isSuperAdminEmail(got.db, got.user.email, got.user.userId)) {
12337
12714
  return json({ ...result.page, records: result.page.records.map(({ edit: _edit, ...record4 }) => ({
12338
12715
  ...record4,
12339
- fields: Object.fromEntries(Object.entries(record4.fields).filter(([id6]) => id6 === "name" || id6 === "email"))
12716
+ fields: Object.fromEntries(Object.entries(record4.fields).filter(([id7]) => id7 === "name" || id7 === "email"))
12340
12717
  })) });
12341
12718
  }
12342
12719
  return json(result.page);
@@ -12406,9 +12783,9 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
12406
12783
  }
12407
12784
  const body = typeof input.body === "string" ? input.body.trim() : "";
12408
12785
  if (!body || body.length > MAX_NOTE3) return json({ error: `note must be 1\u2013${MAX_NOTE3} characters` }, 400);
12409
- const id6 = crypto.randomUUID();
12786
+ const id7 = crypto.randomUUID();
12410
12787
  const note = {
12411
- id: id6,
12788
+ id: id7,
12412
12789
  targetId: identity2.targetId,
12413
12790
  recordType: identity2.recordType,
12414
12791
  recordId: identity2.recordId,
@@ -12420,7 +12797,7 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
12420
12797
  await got.db.transact([{
12421
12798
  t: "update",
12422
12799
  ns: "networkNotes",
12423
- id: id6,
12800
+ id: id7,
12424
12801
  attrs: { ...note, recordKey: identity2.recordKey }
12425
12802
  }]);
12426
12803
  return json({ note }, 201);
@@ -12524,13 +12901,13 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
12524
12901
  return json({ error: `body must be 1\u2013${MAX_NOTE4} characters and mutationId must be valid` }, 400);
12525
12902
  }
12526
12903
  const createdAt = Date.now();
12527
- const id6 = `network:${verified.sender}:${mutationId}`;
12904
+ const id7 = `network:${verified.sender}:${mutationId}`;
12528
12905
  const result = await addActivity(
12529
12906
  {
12530
12907
  crm: ctx.chapter.crm,
12531
12908
  db,
12532
12909
  now: () => createdAt,
12533
- newId: () => id6
12910
+ newId: () => id7
12534
12911
  },
12535
12912
  {
12536
12913
  recordId,
@@ -12545,11 +12922,11 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
12545
12922
  body
12546
12923
  }
12547
12924
  },
12548
- mutationId: id6
12925
+ mutationId: id7
12549
12926
  }
12550
12927
  );
12551
12928
  const note = {
12552
- id: id6,
12929
+ id: id7,
12553
12930
  sourceId: verified.sender,
12554
12931
  recordType,
12555
12932
  recordId,
@@ -12558,8 +12935,8 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
12558
12935
  };
12559
12936
  return json({ version: 1, site: { id: ctx.chapter.id, name: ctx.chapter.name }, note, duplicate: result.duplicate }, 201);
12560
12937
  };
12561
- function targetFor3(ctx, id6) {
12562
- return ctx.chapter.network.targets.find((target) => target.id === id6);
12938
+ function targetFor3(ctx, id7) {
12939
+ return ctx.chapter.network.targets.find((target) => target.id === id7);
12563
12940
  }
12564
12941
  var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
12565
12942
  if (url.pathname !== "/api/admin/network/shared-notes" || req.method !== "GET" && req.method !== "POST") return null;
@@ -12828,10 +13205,10 @@ async function linkNetworkChapterPerson(db, input, mutationId, actor, now = Date
12828
13205
  if (!state.claims.some((claim) => claim.id === expectedClaim.id && claim.personId === person.id && claim.kind === "email")) {
12829
13206
  throw new NetworkPersonRegistryError("identity_mismatch", "source email is not an exact verified identity on this Person");
12830
13207
  }
12831
- const id6 = await networkChapterLinkId(person.id, input.chapterId, input.chapterPersonId);
12832
- const previous = state.links.find((link2) => link2.id === id6);
13208
+ const id7 = await networkChapterLinkId(person.id, input.chapterId, input.chapterPersonId);
13209
+ const previous = state.links.find((link2) => link2.id === id7);
12833
13210
  const link = {
12834
- id: id6,
13211
+ id: id7,
12835
13212
  personId: person.id,
12836
13213
  chapterId: input.chapterId,
12837
13214
  chapterPersonId: input.chapterPersonId,
@@ -12842,11 +13219,11 @@ async function linkNetworkChapterPerson(db, input, mutationId, actor, now = Date
12842
13219
  const nextPerson = { ...person, revision: person.revision + 1, updatedAt: now };
12843
13220
  await db.transact([
12844
13221
  { t: "update", ns: "networkPersons", id: person.id, attrs: { ...nextPerson } },
12845
- { t: "update", ns: "networkChapterPersonLinks", id: id6, attrs: { ...link } },
13222
+ { t: "update", ns: "networkChapterPersonLinks", id: id7, attrs: { ...link } },
12846
13223
  networkPersonEventOp("chapter_source_linked", mutationId, actor, now, { chapterId: input.chapterId }, { personId: person.id })
12847
13224
  ], { mutationId, guards: [
12848
13225
  { ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision, state: "active" } },
12849
- { ns: "networkChapterPersonLinks", id: id6, exists: Boolean(previous), ...previous ? { equals: { state: "revoked" } } : {} }
13226
+ { ns: "networkChapterPersonLinks", id: id7, exists: Boolean(previous), ...previous ? { equals: { state: "revoked" } } : {} }
12850
13227
  ] });
12851
13228
  return { person: nextPerson, link };
12852
13229
  }
@@ -12883,9 +13260,9 @@ var defaultPolicy = (now, consent) => ({
12883
13260
  var isGuardConflict = (error) => error instanceof Error && error.code === "transact_guard_failed";
12884
13261
  async function linkOps(personId, links, now) {
12885
13262
  return Promise.all(links.map(async (link) => {
12886
- const id6 = await networkChapterLinkId(personId, link.chapterId, link.chapterPersonId);
13263
+ const id7 = await networkChapterLinkId(personId, link.chapterId, link.chapterPersonId);
12887
13264
  const sourceKey = await networkChapterPersonSourceKey(link.chapterId, link.chapterPersonId);
12888
- return { t: "update", ns: "networkChapterPersonLinks", id: id6, attrs: { id: id6, personId, chapterId: link.chapterId, chapterPersonId: link.chapterPersonId, sourceKey, state: "shadow", createdAt: now } };
13265
+ return { t: "update", ns: "networkChapterPersonLinks", id: id7, attrs: { id: id7, personId, chapterId: link.chapterId, chapterPersonId: link.chapterPersonId, sourceKey, state: "shadow", createdAt: now } };
12889
13266
  }));
12890
13267
  }
12891
13268
  async function ambiguousAlias(db, input, aliasId, claims, candidates, mutationId, actor, now) {
@@ -13104,10 +13481,10 @@ async function networkPersonBulkBatchDigest(rows5) {
13104
13481
  }
13105
13482
  async function networkPersonRegistryStateDigest(state) {
13106
13483
  const snapshot = {
13107
- persons: state.persons.map(({ id: id6, revision, state: personState, mergedIntoId }) => ({ id: id6, revision, state: personState, mergedIntoId })).sort((a, b) => a.id.localeCompare(b.id)),
13108
- claims: state.claims.map(({ id: id6, personId }) => ({ id: id6, personId })).sort((a, b) => a.id.localeCompare(b.id)),
13109
- aliases: state.aliases.map(({ id: id6, revision, status: status2, personId }) => ({ id: id6, revision, status: status2, personId })).sort((a, b) => a.id.localeCompare(b.id)),
13110
- links: state.links.map(({ id: id6, personId, state: linkState }) => ({ id: id6, personId, state: linkState })).sort((a, b) => a.id.localeCompare(b.id))
13484
+ persons: state.persons.map(({ id: id7, revision, state: personState, mergedIntoId }) => ({ id: id7, revision, state: personState, mergedIntoId })).sort((a, b) => a.id.localeCompare(b.id)),
13485
+ claims: state.claims.map(({ id: id7, personId }) => ({ id: id7, personId })).sort((a, b) => a.id.localeCompare(b.id)),
13486
+ aliases: state.aliases.map(({ id: id7, revision, status: status2, personId }) => ({ id: id7, revision, status: status2, personId })).sort((a, b) => a.id.localeCompare(b.id)),
13487
+ links: state.links.map(({ id: id7, personId, state: linkState }) => ({ id: id7, personId, state: linkState })).sort((a, b) => a.id.localeCompare(b.id))
13111
13488
  };
13112
13489
  return networkPersonBulkSha256(JSON.stringify(stableValue(snapshot)));
13113
13490
  }
@@ -13332,7 +13709,7 @@ var safeNetworkPersonAlias = async (alias) => ({
13332
13709
  recordedAt: alias.consent.recordedAt,
13333
13710
  hasBasis: Boolean(alias.consent.basis)
13334
13711
  },
13335
- identityClaims: alias.identityClaims.map(({ id: id6, kind, hint, verifiedAt }) => ({ id: id6, kind, hint: hint ?? null, verifiedAt })),
13712
+ identityClaims: alias.identityClaims.map(({ id: id7, kind, hint, verifiedAt }) => ({ id: id7, kind, hint: hint ?? null, verifiedAt })),
13336
13713
  chapterIds: [...new Set(alias.chapterLinks.map((link) => link.chapterId))],
13337
13714
  chapterSources: await Promise.all(alias.chapterLinks.map(async (link) => ({
13338
13715
  chapterId: link.chapterId,
@@ -13361,7 +13738,7 @@ async function projectNetworkPersonRegistryState(state) {
13361
13738
  return {
13362
13739
  ...safeNetworkPerson(person, claims),
13363
13740
  canonicalPersonId: canonicalId,
13364
- identityClaims: claims.map(({ id: id6, kind, hint, verifiedAt }) => ({ id: id6, kind, hint: hint ?? null, verifiedAt })),
13741
+ identityClaims: claims.map(({ id: id7, kind, hint, verifiedAt }) => ({ id: id7, kind, hint: hint ?? null, verifiedAt })),
13365
13742
  chapterLinks: safeLinks,
13366
13743
  chapterProjections: projectNetworkContactPolicy(canonical3.contactPolicy, safeLinks).map(({ contactPolicy, ...projection }) => ({ ...projection, contactPolicy: safeNetworkContactPolicy(contactPolicy) }))
13367
13744
  };
@@ -14154,14 +14531,14 @@ var handleFormation = async (req, url, env, ctx) => {
14154
14531
  }
14155
14532
  }
14156
14533
  const db = ctx.makeDb(env);
14157
- const id6 = submissionId ? await applicationIdForSubmission(`formation:${ctx.chapter.id}:${submissionId}`) : crypto.randomUUID();
14534
+ const id7 = submissionId ? await applicationIdForSubmission(`formation:${ctx.chapter.id}:${submissionId}`) : crypto.randomUUID();
14158
14535
  try {
14159
14536
  const created = await createRecord3(
14160
14537
  {
14161
14538
  crm: ctx.chapter.crm,
14162
14539
  db,
14163
14540
  now: () => Date.now(),
14164
- newId: () => id6
14541
+ newId: () => id7
14165
14542
  },
14166
14543
  {
14167
14544
  type: formation.type,
@@ -14170,7 +14547,7 @@ var handleFormation = async (req, url, env, ctx) => {
14170
14547
  }
14171
14548
  );
14172
14549
  return json({
14173
- id: id6,
14550
+ id: id7,
14174
14551
  duplicate: created.duplicate,
14175
14552
  status: created.record.stage ?? null
14176
14553
  }, created.duplicate ? 200 : 201);
@@ -14247,8 +14624,8 @@ function resourceValue(value, path, errors) {
14247
14624
  return null;
14248
14625
  }
14249
14626
  unknownKeys3(raw2, RESOURCE_KEYS, path, errors);
14250
- const id6 = text7(raw2.id, `${path}.id`, errors, 80) ?? "";
14251
- if (id6 && !ID5.test(id6)) errors.push(`${path}.id must be lowercase kebab-case`);
14627
+ const id7 = text7(raw2.id, `${path}.id`, errors, 80) ?? "";
14628
+ if (id7 && !ID5.test(id7)) errors.push(`${path}.id must be lowercase kebab-case`);
14252
14629
  const kind = text7(raw2.kind, `${path}.kind`, errors) ?? "";
14253
14630
  if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
14254
14631
  const title = text7(raw2.title, `${path}.title`, errors, 240) ?? "";
@@ -14305,7 +14682,7 @@ function resourceValue(value, path, errors) {
14305
14682
  updatedAt: integer4(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
14306
14683
  ...promotedFrom ? { promotedFrom } : {}
14307
14684
  };
14308
- return { id: id6, kind, title, ...summary ? { summary } : {}, tags, scope: scope2, status: status2, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance: provenance2 };
14685
+ return { id: id7, kind, title, ...summary ? { summary } : {}, tags, scope: scope2, status: status2, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance: provenance2 };
14309
14686
  }
14310
14687
  function validateBuilderLibraryDocument(value, options = {}) {
14311
14688
  const errors = [];
@@ -14336,7 +14713,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
14336
14713
  const inputs = Array.isArray(raw2.resources) ? raw2.resources : [];
14337
14714
  if (inputs.length > MAX_RESOURCES) errors.push(`document.resources may contain at most ${MAX_RESOURCES} items`);
14338
14715
  const resources = inputs.map((item, index2) => resourceValue(item, `document.resources[${index2}]`, errors)).filter(Boolean);
14339
- const ids2 = resources.map(({ id: id6 }) => id6);
14716
+ const ids2 = resources.map(({ id: id7 }) => id7);
14340
14717
  if (new Set(ids2).size !== ids2.length) errors.push("document.resources ids must be unique");
14341
14718
  for (const resource of resources) {
14342
14719
  if (sourceType === "chapter" && (resource.scope.kind !== "chapter" || resource.scope.chapterId !== sourceAppId)) {
@@ -14425,18 +14802,18 @@ async function loadLatestBuilderLibraryControl(db, sourceId, targetId, environme
14425
14802
  return control2(Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
14426
14803
  }
14427
14804
  async function loadLocalBuilderLibrary(db, appId, environment) {
14428
- const id6 = `builder-library-local:${environment}`;
14429
- const rows5 = (await db.query({ builderLibraryLocal: { $: { where: { id: id6 }, limit: 1 } } })).builderLibraryLocal;
14805
+ const id7 = `builder-library-local:${environment}`;
14806
+ const rows5 = (await db.query({ builderLibraryLocal: { $: { where: { id: id7 }, limit: 1 } } })).builderLibraryLocal;
14430
14807
  return local3(Array.isArray(rows5) ? rows5[0] : void 0);
14431
14808
  }
14432
14809
  async function materializeBuilderLibraryControl(db, envelope, previous, receivedAt) {
14433
14810
  const { document, digest } = envelope;
14434
14811
  const target = document.target;
14435
- const id6 = `builder-library-control:${document.source.appId}:${target.environment}:${document.revision}`;
14812
+ const id7 = `builder-library-control:${document.source.appId}:${target.environment}:${document.revision}`;
14436
14813
  const ops = [];
14437
14814
  if (previous?.active) ops.push({ t: "update", ns: "builderLibraryControls", id: previous.id, attrs: { active: false } });
14438
- ops.push({ t: "update", ns: "builderLibraryControls", id: id6, attrs: {
14439
- id: id6,
14815
+ ops.push({ t: "update", ns: "builderLibraryControls", id: id7, attrs: {
14816
+ id: id7,
14440
14817
  sourceId: document.source.appId,
14441
14818
  targetId: target.appId,
14442
14819
  environment: target.environment,
@@ -14449,13 +14826,13 @@ async function materializeBuilderLibraryControl(db, envelope, previous, received
14449
14826
  await db.transact(ops, { mutationId: `builder-library-control:${document.source.appId}:${target.environment}:${document.revision}:${digest}` });
14450
14827
  }
14451
14828
  async function saveLocalBuilderLibrary(db, document, environment, expectedRevision, updatedAt, updatedBy) {
14452
- const id6 = `builder-library-local:${environment}`;
14829
+ const id7 = `builder-library-local:${environment}`;
14453
14830
  const current = await loadLocalBuilderLibrary(db, document.source.appId, environment);
14454
14831
  if ((current?.revision ?? 0) !== expectedRevision) throw new Error("builder_library_revision_conflict");
14455
- const row2 = { id: id6, appId: document.source.appId, environment, revision: document.revision, document, updatedAt, ...updatedBy ? { updatedBy } : {} };
14456
- await db.transact([{ t: "update", ns: "builderLibraryLocal", id: id6, attrs: row2 }], {
14832
+ const row2 = { id: id7, appId: document.source.appId, environment, revision: document.revision, document, updatedAt, ...updatedBy ? { updatedBy } : {} };
14833
+ await db.transact([{ t: "update", ns: "builderLibraryLocal", id: id7, attrs: row2 }], {
14457
14834
  mutationId: `builder-library-local:${environment}:${document.revision}`,
14458
- guards: current ? [{ ns: "builderLibraryLocal", id: id6, exists: true, equals: { revision: expectedRevision } }] : [{ ns: "builderLibraryLocal", id: id6, exists: false }]
14835
+ guards: current ? [{ ns: "builderLibraryLocal", id: id7, exists: true, equals: { revision: expectedRevision } }] : [{ ns: "builderLibraryLocal", id: id7, exists: false }]
14459
14836
  });
14460
14837
  return row2;
14461
14838
  }
@@ -14670,30 +15047,30 @@ var handleAdminBuilderLibrary = async (req, url, env, ctx) => {
14670
15047
  };
14671
15048
 
14672
15049
  // src/membership-cancel-authority.ts
14673
- import { stripeRequest as stripeRequest10 } from "@odla-ai/stripe";
15050
+ import { stripeRequest as stripeRequest11 } from "@odla-ai/stripe";
14674
15051
  async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
14675
15052
  membershipClock(now);
14676
- const id6 = await networkMembershipId(target);
15053
+ const id7 = await networkMembershipId(target);
14677
15054
  const state = await loadMembershipAuthorityState(db, target);
14678
- const member = await membershipLoad(db, "networkMembership", id6);
15055
+ const member = await membershipLoad(db, "networkMembership", id7);
14679
15056
  requireMembershipScope(member, target);
14680
15057
  if (!["active", "cancel_at_term_end", "payment_grace", "paid_pending_approval", "lapsed"].includes(String(member.status))) {
14681
15058
  membershipFail("membership_cancellation_unavailable");
14682
15059
  }
14683
15060
  const path = `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`;
14684
- const read2 = await stripeRequest10({ secretKey: stripeSecret }, "GET", path);
15061
+ const read2 = await stripeRequest11({ secretKey: stripeSecret }, "GET", path);
14685
15062
  const sub = read2.body;
14686
15063
  if (!read2.ok || sub.id !== member.providerSubscriptionId || sub.customer !== member.providerCustomerId || sub.livemode !== (target.environment === "prod") || !Object.entries({
14687
15064
  ...stripePartitionMetadata(target),
14688
15065
  applicationId: target.applicationId
14689
15066
  }).every(([key3, value]) => sub.metadata?.[key3] === value)) membershipFail("membership_cancellation_provider_mismatch");
14690
15067
  if (sub.status !== "canceled" && sub.cancel_at_period_end !== true) {
14691
- const write = await stripeRequest10(
15068
+ const write = await stripeRequest11(
14692
15069
  { secretKey: stripeSecret },
14693
15070
  "POST",
14694
15071
  path,
14695
15072
  { cancel_at_period_end: true, proration_behavior: "none" },
14696
- { idempotencyKey: `membership-cancel-at-term:${id6}:${member.providerSubscriptionId}` }
15073
+ { idempotencyKey: `membership-cancel-at-term:${id7}:${member.providerSubscriptionId}` }
14697
15074
  );
14698
15075
  if (!write.ok || write.body.id !== member.providerSubscriptionId || write.body.cancel_at_period_end !== true) {
14699
15076
  membershipFail("membership_cancellation_pending");
@@ -14701,7 +15078,7 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
14701
15078
  }
14702
15079
  const status2 = Number(member.termEndsAt) <= now ? "lapsed" : member.journeyApprovedAt ? "cancel_at_term_end" : "paid_pending_approval";
14703
15080
  if (member.status !== status2 || member.autoRenew !== false || Number(member.graceEndsAt ?? 0) !== 0) {
14704
- await db.transact([membershipUpdate("networkMembership", id6, {
15081
+ await db.transact([membershipUpdate("networkMembership", id7, {
14705
15082
  status: status2,
14706
15083
  autoRenew: false,
14707
15084
  graceEndsAt: 0,
@@ -14710,12 +15087,12 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
14710
15087
  updatedAt: now
14711
15088
  })], {
14712
15089
  guards: [
14713
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
15090
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
14714
15091
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
14715
15092
  ]
14716
15093
  });
14717
15094
  }
14718
- return { membershipId: id6, status: status2, termEndsAt: Number(member.termEndsAt), graceEndsAt: 0, autoRenew: false };
15095
+ return { membershipId: id7, status: status2, termEndsAt: Number(member.termEndsAt), graceEndsAt: 0, autoRenew: false };
14719
15096
  }
14720
15097
 
14721
15098
  // src/membership-authority-handler.ts
@@ -14754,17 +15131,17 @@ async function namedSeatPrimaryActivation(db, member, now) {
14754
15131
  // src/membership-approval-authority.ts
14755
15132
  async function approveNetworkMembership(db, input, now) {
14756
15133
  membershipClock(now);
14757
- const id6 = await networkMembershipId(input), entitlementId = `${id6}:primary`;
15134
+ const id7 = await networkMembershipId(input), entitlementId = `${id7}:primary`;
14758
15135
  for (let attempt = 0; attempt < 8; attempt++) {
14759
15136
  const state = await loadMembershipAuthorityState(db, input);
14760
- const member = await membershipLoad(db, "networkMembership", id6);
15137
+ const member = await membershipLoad(db, "networkMembership", id7);
14761
15138
  requireMembershipScope(member, input);
14762
15139
  const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
14763
15140
  requireMembershipScope(entitlement, input);
14764
- if (entitlement.membershipId !== id6 || entitlement.kind !== "primary") membershipFail("membership_entitlement_conflict");
15141
+ if (entitlement.membershipId !== id7 || entitlement.kind !== "primary") membershipFail("membership_entitlement_conflict");
14765
15142
  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) {
14766
15143
  return {
14767
- membershipId: id6,
15144
+ membershipId: id7,
14768
15145
  status: String(member.status),
14769
15146
  cohort: String(member.cohort),
14770
15147
  memberNumber: Number(member.memberNumber),
@@ -14784,7 +15161,7 @@ async function approveNetworkMembership(db, input, now) {
14784
15161
  const seatActivation = await namedSeatPrimaryActivation(db, member, now);
14785
15162
  try {
14786
15163
  await db.transact([
14787
- membershipUpdate("networkMembership", id6, {
15164
+ membershipUpdate("networkMembership", id7, {
14788
15165
  status: member.autoRenew ? "active" : "cancel_at_term_end",
14789
15166
  memberNumber,
14790
15167
  journeyApprovedAt: now,
@@ -14799,8 +15176,8 @@ async function approveNetworkMembership(db, input, now) {
14799
15176
  updatedAt: now
14800
15177
  }),
14801
15178
  ...seatActivation.ops
14802
- ], { mutationId: `membership-approve:${id6}`, guards: [
14803
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision, status: "paid_pending_approval" }, absent: ["memberNumber"] },
15179
+ ], { mutationId: `membership-approve:${id7}`, guards: [
15180
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision, status: "paid_pending_approval" }, absent: ["memberNumber"] },
14804
15181
  { ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision, status: "pending_approval" } },
14805
15182
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
14806
15183
  ...seatActivation.guards
@@ -14815,41 +15192,41 @@ async function approveNetworkMembership(db, input, now) {
14815
15192
  // src/membership-refund-authority.ts
14816
15193
  async function refundNetworkMembership(db, input, secretKey, now) {
14817
15194
  membershipClock(now);
14818
- const id6 = await networkMembershipId(input), entitlementId = `${id6}:primary`;
14819
- let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id6);
15195
+ const id7 = await networkMembershipId(input), entitlementId = `${id7}:primary`;
15196
+ let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id7);
14820
15197
  requireMembershipScope(member, input);
14821
- if (member.status === "refunded") return { membershipId: id6, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
15198
+ if (member.status === "refunded") return { membershipId: id7, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
14822
15199
  if (!["paid_pending_approval", "active", "cancel_at_term_end", "payment_grace", "lapsed", "refund_pending"].includes(String(member.status))) membershipFail("membership_refund_not_eligible");
14823
15200
  if (member.status !== "refund_pending") {
14824
15201
  const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
14825
15202
  requireMembershipScope(entitlement, input);
14826
15203
  await db.transact([
14827
- membershipUpdate("networkMembership", id6, {
15204
+ membershipUpdate("networkMembership", id7, {
14828
15205
  status: "refund_pending",
14829
15206
  refundRequestedAt: now,
14830
15207
  revision: Number(member.revision) + 1,
14831
15208
  updatedAt: now
14832
15209
  }),
14833
15210
  membershipUpdate("networkEntitlement", entitlementId, { status: "closed", revision: Number(entitlement.revision) + 1, updatedAt: now }),
14834
- membershipUpdate("membershipEffect", `${id6}:refund`, {
14835
- id: `${id6}:refund`,
15211
+ membershipUpdate("membershipEffect", `${id7}:refund`, {
15212
+ id: `${id7}:refund`,
14836
15213
  chapterId: input.chapterId,
14837
15214
  environment: input.environment,
14838
15215
  runtime: input.runtime,
14839
- membershipId: id6,
15216
+ membershipId: id7,
14840
15217
  kind: "refund_membership",
14841
15218
  status: "pending",
14842
15219
  attempts: 0,
14843
15220
  createdAt: now,
14844
15221
  updatedAt: now
14845
15222
  })
14846
- ], { mutationId: `membership-refund-request:${id6}`, guards: [
14847
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
15223
+ ], { mutationId: `membership-refund-request:${id7}`, guards: [
15224
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
14848
15225
  { ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision } },
14849
15226
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
14850
15227
  ] });
14851
15228
  }
14852
- member = await membershipLoad(db, "networkMembership", id6);
15229
+ member = await membershipLoad(db, "networkMembership", id7);
14853
15230
  requireMembershipScope(member, input);
14854
15231
  if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
14855
15232
  const result = await refundSubscriptionPayment(secretKey, {
@@ -14863,23 +15240,23 @@ async function refundNetworkMembership(db, input, secretKey, now) {
14863
15240
  if (!result.subscriptionCanceled || result.refundedCents !== member.amountCents) membershipFail("membership_refund_unconfirmed");
14864
15241
  for (let attempt = 0; attempt < 8; attempt++) {
14865
15242
  state = await loadMembershipAuthorityState(db, input);
14866
- member = await membershipLoad(db, "networkMembership", id6);
15243
+ member = await membershipLoad(db, "networkMembership", id7);
14867
15244
  requireMembershipScope(member, input);
14868
- if (member.status === "refunded") return { membershipId: id6, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
15245
+ if (member.status === "refunded") return { membershipId: id7, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
14869
15246
  if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
14870
15247
  const counted = member.founding === true && member.memberNumber !== void 0;
14871
15248
  const counts = state.cohortCounts;
14872
15249
  if (!counts || counted && (!Number.isSafeInteger(counts[String(member.cohort)]) || counts[String(member.cohort)] < 1)) membershipFail("invalid_cohort_count");
14873
15250
  try {
14874
15251
  await db.transact([
14875
- membershipUpdate("networkMembership", id6, {
15252
+ membershipUpdate("networkMembership", id7, {
14876
15253
  status: "refunded",
14877
15254
  refundedCents: result.refundedCents,
14878
15255
  autoRenew: false,
14879
15256
  revision: Number(member.revision) + 1,
14880
15257
  updatedAt: now
14881
15258
  }),
14882
- membershipUpdate("membershipEffect", `${id6}:refund`, { status: "complete", updatedAt: now }),
15259
+ membershipUpdate("membershipEffect", `${id7}:refund`, { status: "complete", updatedAt: now }),
14883
15260
  ...counted ? [membershipUpdate("membershipAuthorityState", String(state.id), {
14884
15261
  cohortCounts: { ...counts, [String(member.cohort)]: counts[String(member.cohort)] - 1 },
14885
15262
  revision: Number(state.revision) + 1,
@@ -14890,7 +15267,7 @@ async function refundNetworkMembership(db, input, secretKey, now) {
14890
15267
  chapterId: input.chapterId,
14891
15268
  environment: input.environment,
14892
15269
  runtime: input.runtime,
14893
- membershipId: id6,
15270
+ membershipId: id7,
14894
15271
  seatId: member.namedSeatId,
14895
15272
  kind: "refund_seat_with_primary",
14896
15273
  status: "pending",
@@ -14898,8 +15275,8 @@ async function refundNetworkMembership(db, input, secretKey, now) {
14898
15275
  createdAt: now,
14899
15276
  updatedAt: now
14900
15277
  })] : []
14901
- ], { mutationId: `membership-refunded:${id6}`, guards: [
14902
- { ns: "networkMembership", id: id6, exists: true, equals: { status: "refund_pending", revision: member.revision } },
15278
+ ], { mutationId: `membership-refunded:${id7}`, guards: [
15279
+ { ns: "networkMembership", id: id7, exists: true, equals: { status: "refund_pending", revision: member.revision } },
14903
15280
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
14904
15281
  ] });
14905
15282
  } catch (error) {
@@ -14910,12 +15287,12 @@ async function refundNetworkMembership(db, input, secretKey, now) {
14910
15287
  }
14911
15288
 
14912
15289
  // src/membership-provider-lifecycle.ts
14913
- import { stripeRequest as stripeRequest11 } from "@odla-ai/stripe";
15290
+ import { stripeRequest as stripeRequest12 } from "@odla-ai/stripe";
14914
15291
 
14915
15292
  // src/membership-renewal-authority.ts
14916
15293
  async function reconcileMembershipRenewal(db, input, now) {
14917
15294
  membershipClock(now);
14918
- const id6 = await networkMembershipId(input), eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
15295
+ const id7 = await networkMembershipId(input), eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
14919
15296
  const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow?.digest, "membership_renewal_evidence_pending");
14920
15297
  const fact = eventRow?.fact;
14921
15298
  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({
@@ -14928,10 +15305,10 @@ async function reconcileMembershipRenewal(db, input, now) {
14928
15305
  return membershipFail("membership_renewal_evidence_pending");
14929
15306
  }
14930
15307
  for (let attempt = 0; attempt < 8; attempt++) {
14931
- const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id6);
15308
+ const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id7);
14932
15309
  requireMembershipScope(member, input);
14933
15310
  const receipt = () => ({
14934
- membershipId: id6,
15311
+ membershipId: id7,
14935
15312
  status: String(member.status),
14936
15313
  termEndsAt: Number(member.termEndsAt),
14937
15314
  graceEndsAt: Number(member.graceEndsAt ?? 0),
@@ -14956,13 +15333,13 @@ async function reconcileMembershipRenewal(db, input, now) {
14956
15333
  const paid = fact.eventType === "invoice.payment_succeeded";
14957
15334
  if (!paid && (fact.status !== "open" || !Number.isSafeInteger(fact.amountPaidCents) || Number(fact.amountPaidCents) < 0 || Number(fact.amountPaidCents) >= amount)) membershipFail("membership_renewal_failure_mismatch");
14958
15335
  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");
14959
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id6}:primary`);
15336
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id7}:primary`);
14960
15337
  requireMembershipScope(entitlement, input);
14961
15338
  const graceEndsAt = paid ? 0 : start + 14 * 864e5;
14962
15339
  const status2 = paid ? member.journeyApprovedAt ? member.autoRenew ? "active" : "cancel_at_term_end" : "paid_pending_approval" : now < graceEndsAt ? "payment_grace" : "lapsed";
14963
15340
  const entitlementStatus = member.journeyApprovedAt && (paid || now < graceEndsAt) ? "active" : member.journeyApprovedAt ? "closed" : "pending_approval";
14964
15341
  const ops = [
14965
- membershipUpdate("networkMembership", id6, {
15342
+ membershipUpdate("networkMembership", id7, {
14966
15343
  status: status2,
14967
15344
  graceEndsAt,
14968
15345
  ...paid ? {
@@ -14973,7 +15350,7 @@ async function reconcileMembershipRenewal(db, input, now) {
14973
15350
  revision: Number(member.revision) + 1,
14974
15351
  updatedAt: now
14975
15352
  }),
14976
- membershipUpdate("networkEntitlement", `${id6}:primary`, {
15353
+ membershipUpdate("networkEntitlement", `${id7}:primary`, {
14977
15354
  status: entitlementStatus,
14978
15355
  ...paid ? { termStartedAt: start, termEndsAt: end } : {},
14979
15356
  sourceEventId: input.eventId,
@@ -14982,8 +15359,8 @@ async function reconcileMembershipRenewal(db, input, now) {
14982
15359
  })
14983
15360
  ];
14984
15361
  const guards = [
14985
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
14986
- { ns: "networkEntitlement", id: `${id6}:primary`, exists: true, equals: { revision: entitlement.revision } },
15362
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
15363
+ { ns: "networkEntitlement", id: `${id7}:primary`, exists: true, equals: { revision: entitlement.revision } },
14987
15364
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
14988
15365
  { ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
14989
15366
  evidenceGuard
@@ -15008,15 +15385,15 @@ async function reconcileMembershipRenewal(db, input, now) {
15008
15385
  );
15009
15386
  }
15010
15387
  try {
15011
- await db.transact(ops, { mutationId: `membership-renewal:${id6}:${input.eventId}`, guards });
15388
+ await db.transact(ops, { mutationId: `membership-renewal:${id7}:${input.eventId}`, guards });
15012
15389
  } catch (error) {
15013
15390
  if (error.code !== "transact_guard_failed") throw error;
15014
15391
  continue;
15015
15392
  }
15016
- const saved = await membershipLoad(db, "networkMembership", id6);
15393
+ const saved = await membershipLoad(db, "networkMembership", id7);
15017
15394
  requireMembershipScope(saved, input);
15018
15395
  return {
15019
- membershipId: id6,
15396
+ membershipId: id7,
15020
15397
  status: String(saved.status),
15021
15398
  termEndsAt: Number(saved.termEndsAt),
15022
15399
  graceEndsAt: Number(saved.graceEndsAt ?? 0),
@@ -15042,12 +15419,12 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15042
15419
  }).some(([key3, value]) => fact.identity[key3] !== value)) membershipFail("membership_billing_evidence_pending");
15043
15420
  const sk = await getVaultSecret(db, stripeSecretName);
15044
15421
  if (!sk) membershipFail("membership_billing_provider_unavailable");
15045
- const id6 = await networkMembershipId(input);
15422
+ const id7 = await networkMembershipId(input);
15046
15423
  for (let attempt = 0; attempt < 8; attempt++) {
15047
- const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id6);
15424
+ const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id7);
15048
15425
  requireMembershipScope(member, input);
15049
15426
  const receipt = (value = member) => ({
15050
- membershipId: id6,
15427
+ membershipId: id7,
15051
15428
  status: String(value.status),
15052
15429
  termEndsAt: Number(value.termEndsAt),
15053
15430
  graceEndsAt: Number(value.graceEndsAt ?? 0),
@@ -15068,13 +15445,13 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15068
15445
  });
15069
15446
  await commercialEvidenceGuard(db, input.eventId, row2?.digest, "membership_billing_evidence_pending");
15070
15447
  await refundNetworkMembership(db, input, sk, now);
15071
- const saved2 = await membershipLoad(db, "networkMembership", id6);
15448
+ const saved2 = await membershipLoad(db, "networkMembership", id7);
15072
15449
  requireMembershipScope(saved2, input);
15073
15450
  return receipt(saved2);
15074
15451
  }
15075
15452
  if (fact.subscriptionId !== member.providerSubscriptionId || fact.objectKind !== "subscription") membershipFail("membership_billing_scope_mismatch");
15076
15453
  if (["refund_pending", "refunded", "declined", "canceled", "restarting"].includes(String(member.status))) return receipt();
15077
- const result = await stripeRequest11({ secretKey: sk }, "GET", `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`);
15454
+ const result = await stripeRequest12({ secretKey: sk }, "GET", `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`);
15078
15455
  const sub = result.body;
15079
15456
  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)) {
15080
15457
  membershipFail("membership_billing_provider_mismatch");
@@ -15083,18 +15460,18 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15083
15460
  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";
15084
15461
  const graceEndsAt = autoRenew ? Number(member.graceEndsAt ?? 0) : 0;
15085
15462
  if (member.autoRenew === autoRenew && member.status === status2 && Number(member.graceEndsAt ?? 0) === graceEndsAt) return receipt();
15086
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id6}:primary`);
15463
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id7}:primary`);
15087
15464
  requireMembershipScope(entitlement, input);
15088
15465
  try {
15089
15466
  await db.transact([
15090
- membershipUpdate("networkMembership", id6, { status: status2, autoRenew, graceEndsAt, revision: Number(member.revision) + 1, updatedAt: now }),
15467
+ membershipUpdate("networkMembership", id7, { status: status2, autoRenew, graceEndsAt, revision: Number(member.revision) + 1, updatedAt: now }),
15091
15468
  membershipUpdate("networkEntitlement", String(entitlement.id), {
15092
15469
  status: status2 === "lapsed" ? "closed" : entitlement.status,
15093
15470
  revision: Number(entitlement.revision) + 1,
15094
15471
  updatedAt: now
15095
15472
  })
15096
15473
  ], { guards: [
15097
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
15474
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
15098
15475
  { ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision } },
15099
15476
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
15100
15477
  { ns: "networkCommercialEvents", id: String(row2.id), exists: true, equals: { digest: fact.digest } },
@@ -15104,7 +15481,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15104
15481
  if (error.code !== "transact_guard_failed") throw error;
15105
15482
  continue;
15106
15483
  }
15107
- const saved = await membershipLoad(db, "networkMembership", id6);
15484
+ const saved = await membershipLoad(db, "networkMembership", id7);
15108
15485
  requireMembershipScope(saved, input);
15109
15486
  return receipt(saved);
15110
15487
  }
@@ -15112,7 +15489,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15112
15489
  }
15113
15490
 
15114
15491
  // src/membership-seat-stripe.ts
15115
- import { stripeRequest as stripeRequest12 } from "@odla-ai/stripe";
15492
+ import { stripeRequest as stripeRequest13 } from "@odla-ai/stripe";
15116
15493
  var reject7 = () => {
15117
15494
  throw Object.assign(new Error("named seat provider contract mismatch"), { code: "named_seat_provider_mismatch" });
15118
15495
  };
@@ -15124,7 +15501,7 @@ var metadataFor = (seat) => ({
15124
15501
  });
15125
15502
  var matches = (value, expected) => value && Object.entries(expected).every(([key3, item]) => value[key3] === item);
15126
15503
  async function read(sk, path, params) {
15127
- const response = await stripeRequest12({ secretKey: sk }, "GET", path, params);
15504
+ const response = await stripeRequest13({ secretKey: sk }, "GET", path, params);
15128
15505
  if (!response.ok) return reject7();
15129
15506
  return response.body;
15130
15507
  }
@@ -15135,7 +15512,7 @@ async function subscriptionForSeat(sk, seat) {
15135
15512
  currency: seat.currency,
15136
15513
  interval: seat.interval,
15137
15514
  environment: seat.environment
15138
- }, Boolean(seat.providerPaymentIntentId));
15515
+ }, Boolean(seat.providerPaymentIntentId || seat.combinedInvoice));
15139
15516
  if (!productId) reject7();
15140
15517
  const sub = await read(sk, `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}?expand[]=discounts`);
15141
15518
  const items = sub.items?.data;
@@ -15161,11 +15538,11 @@ async function createNamedSeatStripePayment(sk, seat, now) {
15161
15538
  }
15162
15539
  await subscriptionForSeat(sk, seat);
15163
15540
  const metadata = metadataFor(seat);
15164
- const result = seat.providerPaymentIntentId ? await stripeRequest12(
15541
+ const result = seat.providerPaymentIntentId ? await stripeRequest13(
15165
15542
  { secretKey: sk },
15166
15543
  "GET",
15167
15544
  `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`
15168
- ) : await stripeRequest12({ secretKey: sk }, "POST", "/v1/payment_intents", {
15545
+ ) : await stripeRequest13({ secretKey: sk }, "POST", "/v1/payment_intents", {
15169
15546
  amount: seat.amountCents,
15170
15547
  currency: seat.currency,
15171
15548
  customer: seat.providerCustomerId,
@@ -15179,11 +15556,15 @@ async function createNamedSeatStripePayment(sk, seat, now) {
15179
15556
  return { paymentIntentId: pi.id, clientSecret: pi.client_secret, status: pi.status };
15180
15557
  }
15181
15558
  async function attachNamedSeatStripeRenewal(sk, seat) {
15182
- if (!seat.providerPaymentIntentId || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject7();
15559
+ if (!seat.providerPaymentIntentId && !seat.combinedInvoice || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject7();
15183
15560
  const sub = await subscriptionForSeat(sk, seat);
15184
15561
  const existing = sub.items.data.find((item2) => item2.metadata?.seatId === seat.id);
15185
- if (existing) return String(existing.id);
15186
- const result = await stripeRequest12({ secretKey: sk }, "POST", "/v1/subscription_items", {
15562
+ if (existing) {
15563
+ if (!/^si_[A-Za-z0-9_]+$/.test(existing.id)) reject7();
15564
+ return String(existing.id);
15565
+ }
15566
+ if (seat.combinedInvoice) reject7();
15567
+ const result = await stripeRequest13({ secretKey: sk }, "POST", "/v1/subscription_items", {
15187
15568
  subscription: seat.providerSubscriptionId,
15188
15569
  price: seat.priceId,
15189
15570
  quantity: 1,
@@ -15400,7 +15781,7 @@ async function recordRecipient(db, input, now, approve) {
15400
15781
  const state = await loadMembershipAuthorityState(db, input);
15401
15782
  const seat = await membershipLoad(db, "networkSeat", input.seatId);
15402
15783
  requireMembershipScope(seat, input);
15403
- if (!seat.purchaserUserId || seat.purchaserUserId === input.recipientUserId) membershipFail("named_seat_recipient_must_differ_from_purchaser");
15784
+ if (!seat.purchaserUserId && !seat.purchaserEmailNormalized || seat.purchaserUserId === input.recipientUserId || seat.purchaserEmailNormalized === normalizeChapterEmail(input.verifiedRecipientEmail)) membershipFail("named_seat_recipient_must_differ_from_purchaser");
15404
15785
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
15405
15786
  requireMembershipScope(member, input);
15406
15787
  const entitlement = await membershipLoad(db, "networkEntitlement", String(seat.entitlementId));
@@ -15720,9 +16101,14 @@ function createMembershipAuthorityHandler(options) {
15720
16101
  try {
15721
16102
  input = JSON.parse(verified.body);
15722
16103
  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] ?? []];
15723
- 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])))) {
16104
+ 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])))) {
15724
16105
  throw new TypeError("invalid request");
15725
16106
  }
16107
+ if (action === "checkouts" && input.namedSeat !== void 0) {
16108
+ const seat = input.namedSeat;
16109
+ 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");
16110
+ checkoutNamedSeatSelection(seat, seat.purchaserEmail);
16111
+ }
15726
16112
  } catch {
15727
16113
  return respond({ error: "invalid_offer_request" }, 400);
15728
16114
  }
@@ -15795,8 +16181,9 @@ function createMembershipAuthorityHandler(options) {
15795
16181
  offerId: String(input.offerId),
15796
16182
  offerDigest: String(input.offerDigest),
15797
16183
  quoteDigest: String(input.quoteDigest),
15798
- priceId: String(input.priceId)
15799
- }, options.now?.() ?? Date.now());
16184
+ priceId: String(input.priceId),
16185
+ ...input.namedSeat ? { namedSeat: input.namedSeat } : {}
16186
+ }, options.now?.() ?? Date.now(), peer.seatPriceId);
15800
16187
  return respond({ ok: true, binding });
15801
16188
  }
15802
16189
  const offerId = await membershipOfferIdForRequest({ ...peer, applicationId: String(input.applicationId), requestId: String(input.requestId) });
@@ -15824,13 +16211,13 @@ function createMembershipAuthorityHandler(options) {
15824
16211
 
15825
16212
  // src/schema-membership-authority.ts
15826
16213
  var attr = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
15827
- var id4 = () => attr("string", false, true, true);
16214
+ var id5 = () => attr("string", false, true, true);
15828
16215
  var key = (optional = false) => attr("string", optional, false, true);
15829
16216
  var scope = () => ({ appId: key(), chapterId: key(), environment: key(), runtime: key(), applicationId: key(), skuId: key() });
15830
16217
  var times = () => ({ revision: attr("number"), createdAt: attr("number"), updatedAt: attr("number") });
15831
16218
  var membershipAuthorityEntities = {
15832
16219
  networkMembership: { attrs: {
15833
- id: id4(),
16220
+ id: id5(),
15834
16221
  membershipId: key(),
15835
16222
  ...scope(),
15836
16223
  personId: key(true),
@@ -15870,7 +16257,7 @@ var membershipAuthorityEntities = {
15870
16257
  restartId: key(true)
15871
16258
  } },
15872
16259
  membershipRestart: { attrs: {
15873
- id: id4(),
16260
+ id: id5(),
15874
16261
  ...scope(),
15875
16262
  membershipId: key(),
15876
16263
  restartId: key(),
@@ -15880,7 +16267,7 @@ var membershipAuthorityEntities = {
15880
16267
  createdAt: attr("number")
15881
16268
  } },
15882
16269
  membershipPurchaseHistory: { attrs: {
15883
- id: id4(),
16270
+ id: id5(),
15884
16271
  ...scope(),
15885
16272
  membershipId: key(),
15886
16273
  providerSubscriptionId: key(),
@@ -15889,7 +16276,7 @@ var membershipAuthorityEntities = {
15889
16276
  archivedAt: attr("number")
15890
16277
  } },
15891
16278
  networkEntitlement: { attrs: {
15892
- id: id4(),
16279
+ id: id5(),
15893
16280
  ...scope(),
15894
16281
  membershipId: key(),
15895
16282
  seatId: key(true),
@@ -15906,10 +16293,12 @@ var membershipAuthorityEntities = {
15906
16293
  journeyApprovedAt: attr("number", true)
15907
16294
  } },
15908
16295
  networkSeat: { attrs: {
15909
- id: id4(),
16296
+ id: id5(),
15910
16297
  ...scope(),
15911
16298
  primaryMembershipId: key(),
15912
- purchaserUserId: key(),
16299
+ purchaserUserId: key(true),
16300
+ purchaserEmailNormalized: key(true),
16301
+ combinedInvoice: attr("json", true),
15913
16302
  recipientName: attr("string"),
15914
16303
  recipientEmailNormalized: key(),
15915
16304
  recipientClaimId: key(),
@@ -15941,9 +16330,9 @@ var membershipAuthorityEntities = {
15941
16330
  refundedCents: attr("number", true),
15942
16331
  providerRefundIds: attr("json", true)
15943
16332
  } },
15944
- networkSeatRecipient: { attrs: { id: id4(), appId: key(), chapterId: key(), environment: key(), runtime: key(), seatId: key(), recipientEmailNormalized: key(), createdAt: attr("number") } },
16333
+ networkSeatRecipient: { attrs: { id: id5(), appId: key(), chapterId: key(), environment: key(), runtime: key(), seatId: key(), recipientEmailNormalized: key(), createdAt: attr("number") } },
15945
16334
  membershipEffect: { attrs: {
15946
- id: id4(),
16335
+ id: id5(),
15947
16336
  chapterId: key(),
15948
16337
  environment: key(),
15949
16338
  runtime: key(),
@@ -15962,11 +16351,11 @@ var membershipAuthorityRules = Object.fromEntries(Object.keys(membershipAuthorit
15962
16351
 
15963
16352
  // src/schema-membership-offers.ts
15964
16353
  var attr2 = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
15965
- var id5 = () => attr2("string", false, true, true);
16354
+ var id6 = () => attr2("string", false, true, true);
15966
16355
  var key2 = () => attr2("string", false, false, true);
15967
16356
  var membershipOfferEntities = {
15968
16357
  membershipAuthorityState: { attrs: {
15969
- id: id5(),
16358
+ id: id6(),
15970
16359
  chapterId: key2(),
15971
16360
  environment: key2(),
15972
16361
  mode: key2(),
@@ -15976,7 +16365,7 @@ var membershipOfferEntities = {
15976
16365
  updatedAt: attr2("number")
15977
16366
  } },
15978
16367
  membershipCheckoutOffer: { attrs: {
15979
- id: id5(),
16368
+ id: id6(),
15980
16369
  applicationId: key2(),
15981
16370
  appId: key2(),
15982
16371
  chapterId: key2(),
@@ -15991,7 +16380,7 @@ var membershipOfferEntities = {
15991
16380
  authorityRevision: attr2("number")
15992
16381
  } },
15993
16382
  membershipOfferCheckout: { attrs: {
15994
- id: id5(),
16383
+ id: id6(),
15995
16384
  appId: key2(),
15996
16385
  chapterId: key2(),
15997
16386
  environment: key2(),
@@ -16002,7 +16391,8 @@ var membershipOfferEntities = {
16002
16391
  offerDigest: attr2("string"),
16003
16392
  quoteDigest: attr2("string"),
16004
16393
  priceId: attr2("string"),
16005
- boundAt: attr2("number")
16394
+ boundAt: attr2("number"),
16395
+ namedSeat: attr2("json", true)
16006
16396
  } }
16007
16397
  };
16008
16398
  var membershipOfferRules = Object.fromEntries(Object.keys(membershipOfferEntities).map((ns) => [ns, { view: "false", create: "false", update: "false", delete: "false" }]));
@@ -16057,7 +16447,7 @@ function createMembershipAuthorityStorage(db, secretNames) {
16057
16447
  }
16058
16448
 
16059
16449
  // src/membership-seat-cancel.ts
16060
- import { initStripe as initStripe3, stripeRequest as stripeRequest13 } from "@odla-ai/stripe";
16450
+ import { initStripe as initStripe3, stripeRequest as stripeRequest14 } from "@odla-ai/stripe";
16061
16451
  async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
16062
16452
  membershipClock(now);
16063
16453
  const state = await loadMembershipAuthorityState(db, target);
@@ -16085,7 +16475,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
16085
16475
  }
16086
16476
  const metadata = { ...stripePartitionMetadata(target), applicationId: target.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
16087
16477
  const request = async (method, path, params) => {
16088
- const result = await stripeRequest13(
16478
+ const result = await stripeRequest14(
16089
16479
  { secretKey },
16090
16480
  method,
16091
16481
  path,
@@ -16111,9 +16501,9 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
16111
16501
  if (payment.status === "succeeded" || payment.amount_received > 0) throw new Error("paid seat requires payment evidence and full refund");
16112
16502
  if (payment.status !== "canceled") {
16113
16503
  if (!["requires_payment_method", "requires_confirmation", "requires_action", "processing"].includes(payment.status)) throw new Error("seat payment outcome unresolved");
16114
- const id6 = payment.id;
16115
- payment = await request("POST", `/v1/payment_intents/${encodeURIComponent(id6)}/cancel`, { cancellation_reason: "abandoned" });
16116
- if (payment.id !== id6) throw new Error("seat cancellation receipt mismatch");
16504
+ const id7 = payment.id;
16505
+ payment = await request("POST", `/v1/payment_intents/${encodeURIComponent(id7)}/cancel`, { cancellation_reason: "abandoned" });
16506
+ if (payment.id !== id7) throw new Error("seat cancellation receipt mismatch");
16117
16507
  }
16118
16508
  if (!valid(payment) || payment.status !== "canceled" || payment.amount_received !== 0) throw new Error("seat cancellation unconfirmed");
16119
16509
  await db.transact([membershipUpdate("networkSeat", target.seatId, { status: "canceled", revision: Number(seat.revision) + 1, updatedAt: now })], {
@@ -16445,7 +16835,8 @@ async function requestMembershipCheckoutBinding(options) {
16445
16835
  offerId: options.offerId,
16446
16836
  offerDigest: options.offerDigest,
16447
16837
  quoteDigest: options.quoteDigest,
16448
- priceId: options.priceId
16838
+ priceId: options.priceId,
16839
+ ...options.namedSeat ? { namedSeat: options.namedSeat } : {}
16449
16840
  });
16450
16841
  return verifyMembershipCheckoutBinding(result.binding, options);
16451
16842
  }