@odla-ai/chapter 0.54.2 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -133,10 +133,10 @@ async function clerkGetUserByEmail(secretKey, email, fetchImpl = fetch, source =
133
133
  const user = Array.isArray(data) ? data[0] : void 0;
134
134
  return user ? toRecord(user, source) : null;
135
135
  }
136
- async function clerkGetUser(secretKey, id6, fetchImpl = fetch, source = "public") {
137
- const data = await clerkGet(`/v1/users/${encodeURIComponent(id6)}`, secretKey, fetchImpl).catch(() => null);
136
+ async function clerkGetUser(secretKey, id7, fetchImpl = fetch, source = "public") {
137
+ const data = await clerkGet(`/v1/users/${encodeURIComponent(id7)}`, secretKey, fetchImpl).catch(() => null);
138
138
  const record4 = data ? toRecord(data, source) : null;
139
- return record4?.id === id6 ? record4 : null;
139
+ return record4?.id === id7 ? record4 : null;
140
140
  }
141
141
  async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
142
142
  const out = [];
@@ -152,8 +152,8 @@ async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
152
152
  }
153
153
  return out;
154
154
  }
155
- async function clerkSetRole(secretKey, id6, role, fetchImpl = fetch, source = "public") {
156
- const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id6)}/metadata`, {
155
+ async function clerkSetRole(secretKey, id7, role, fetchImpl = fetch, source = "public") {
156
+ const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id7)}/metadata`, {
157
157
  method: "PATCH",
158
158
  headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
159
159
  redirect: "manual",
@@ -212,10 +212,10 @@ async function ensureProjectedPerson(deps, input, options = {}) {
212
212
  const existing = await find();
213
213
  if (existing) return { record: existing, created: false };
214
214
  const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`chapter:person:v1:${email}`)));
215
- const id6 = `person-${Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
215
+ const id7 = `person-${Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
216
216
  try {
217
217
  const created = await (0, import_crm.createRecord)({ ...deps, db: deps.db }, {
218
- id: id6,
218
+ id: id7,
219
219
  type: "person",
220
220
  input: { ...input, email },
221
221
  ...options
@@ -668,7 +668,10 @@ function verifyInitialMembershipInvoice(intent, evidence) {
668
668
  if (!intent.providerSubscriptionId || !intent.providerCustomerId) return { status: "retry" };
669
669
  try {
670
670
  const terms2 = intent.billingTerms ? subscriptionBillingTerms(intent.billingTerms) : void 0;
671
- const due = subscriptionAmountDue({ priceCents: Number(intent.priceCents), billingTerms: terms2 });
671
+ const quote = intent.paymentQuote;
672
+ const seatCents = quote?.namedSeat ? quote.lineItems.namedSeatCents : 0;
673
+ if (!Number.isSafeInteger(seatCents) || Number(seatCents) < 0 || quote?.namedSeat && quote.seatOffer?.amountCents !== seatCents) return { status: "mismatch" };
674
+ const due = subscriptionAmountDue({ priceCents: Number(intent.priceCents), billingTerms: terms2 }) + Number(seatCents);
672
675
  if (!evidence || !/^in_[A-Za-z0-9]+$/.test(evidence.invoiceId ?? "") || evidence.subscriptionId !== intent.providerSubscriptionId || evidence.customerId !== intent.providerCustomerId || evidence.currency !== intent.currency || evidence.total !== due || evidence.amountPaid !== due || due <= 0 || evidence.status !== "paid") return { status: "mismatch" };
673
676
  return { status: "verified", ...terms2?.initialTermEndsAt ? { renewalAt: terms2.initialTermEndsAt } : {} };
674
677
  } catch {
@@ -1019,8 +1022,8 @@ async function rebuildNetworkCommercialProjection(db, sources, mutationId, now =
1019
1022
  return projection;
1020
1023
  }
1021
1024
  async function collide(db, sources, stored, fact, now, legacyDigest, alerts) {
1022
- const id6 = exceptionId(fact.eventId, "collision");
1023
- const exception = (await db.query({ networkCommercialExceptions: { $: { where: { id: id6 }, limit: 1 } } })).networkCommercialExceptions?.[0];
1025
+ const id7 = exceptionId(fact.eventId, "collision");
1026
+ const exception = (await db.query({ networkCommercialExceptions: { $: { where: { id: id7 }, limit: 1 } } })).networkCommercialExceptions?.[0];
1024
1027
  const exact = stored.digest === fact.digest || stored.digest === legacyDigest;
1025
1028
  const canonical3 = !exact && sameNetworkCommercialFact(stored.fact, fact);
1026
1029
  if (exact || canonical3) {
@@ -1041,7 +1044,7 @@ async function collide(db, sources, stored, fact, now, legacyDigest, alerts) {
1041
1044
  action: "canonical_replay",
1042
1045
  reason: "a resend matched the retained canonical fact"
1043
1046
  } },
1044
- { t: "update", ns: "networkCommercialExceptions", id: id6, attrs: {
1047
+ { t: "update", ns: "networkCommercialExceptions", id: id7, attrs: {
1045
1048
  status: "resolved",
1046
1049
  resolvedDigest: stored.digest,
1047
1050
  reviewedDigest: fact.digest,
@@ -1050,7 +1053,7 @@ async function collide(db, sources, stored, fact, now, legacyDigest, alerts) {
1050
1053
  updatedAt: now
1051
1054
  } }
1052
1055
  ], { mutationId: `network-commercial-resolve:${fact.eventId}:${fact.digest}`, guards: [
1053
- { ns: "networkCommercialExceptions", id: id6, exists: true, equals: { status: "active", updatedAt: exception.updatedAt } }
1056
+ { ns: "networkCommercialExceptions", id: id7, exists: true, equals: { status: "active", updatedAt: exception.updatedAt } }
1054
1057
  ] });
1055
1058
  const projection2 = await rebuildNetworkCommercialProjection(db, sources, `network-commercial-resolve-rebuild:${fact.eventId}:${fact.digest}`, now, alerts);
1056
1059
  return { status: "replayed", resolvedCollision: true, projection: projection2 };
@@ -1060,9 +1063,9 @@ async function collide(db, sources, stored, fact, now, legacyDigest, alerts) {
1060
1063
  }
1061
1064
  const stale = RESOLUTION_ATTRS.filter((attr3) => exception !== void 0 && Object.hasOwn(exception, attr3));
1062
1065
  await db.transact([
1063
- ...stale.length ? [{ t: "retract", ns: "networkCommercialExceptions", id: id6, attrs: stale }] : [],
1064
- { t: "update", ns: "networkCommercialExceptions", id: id6, attrs: {
1065
- id: id6,
1066
+ ...stale.length ? [{ t: "retract", ns: "networkCommercialExceptions", id: id7, attrs: stale }] : [],
1067
+ { t: "update", ns: "networkCommercialExceptions", id: id7, attrs: {
1068
+ id: id7,
1066
1069
  eventId: fact.eventId,
1067
1070
  kind: "collision",
1068
1071
  status: "active",
@@ -1082,12 +1085,12 @@ async function ingestNetworkCommercialFact(db, sources, fact, now = Date.now(),
1082
1085
  const found = await db.query({ networkCommercialEvents: { $: { where: { eventId: fact.eventId }, limit: 1 } } });
1083
1086
  const existing = found.networkCommercialEvents?.[0];
1084
1087
  if (existing) return collide(db, sources, existing, fact, now, legacyDigest, alerts);
1085
- const id6 = eventRowId(fact.eventId);
1088
+ const id7 = eventRowId(fact.eventId);
1086
1089
  let lostRace = false;
1087
1090
  let duplicate = false;
1088
1091
  try {
1089
- const write = await db.transact([{ t: "update", ns: "networkCommercialEvents", id: id6, attrs: {
1090
- id: id6,
1092
+ const write = await db.transact([{ t: "update", ns: "networkCommercialEvents", id: id7, attrs: {
1093
+ id: id7,
1091
1094
  eventId: fact.eventId,
1092
1095
  digest: fact.digest,
1093
1096
  eventType: fact.eventType,
@@ -1097,7 +1100,7 @@ async function ingestNetworkCommercialFact(db, sources, fact, now = Date.now(),
1097
1100
  receivedAt: now
1098
1101
  } }], {
1099
1102
  mutationId: `network-commercial-ingest:${fact.eventId}:${fact.digest}`,
1100
- guards: [{ ns: "networkCommercialEvents", id: id6, exists: false }]
1103
+ guards: [{ ns: "networkCommercialEvents", id: id7, exists: false }]
1101
1104
  });
1102
1105
  duplicate = write.duplicate;
1103
1106
  } catch (error) {
@@ -1141,10 +1144,10 @@ async function recoverNetworkCommercialEvent(db, sources, eventId, actorUserId,
1141
1144
  const provider = await readStripeRecoveryEvent(db, eventId);
1142
1145
  const fact = normalizeNetworkCommercialStripeEvent(provider.event, provider.digest);
1143
1146
  if (!sameNetworkCommercialFact(stored.fact, fact)) throw new Error("Stripe facts differ; collision requires investigation");
1144
- const id6 = crypto.randomUUID();
1147
+ const id7 = crypto.randomUUID();
1145
1148
  await db.transact([
1146
- { t: "update", ns: "paymentRecoveryReviews", id: id6, attrs: {
1147
- id: id6,
1149
+ { t: "update", ns: "paymentRecoveryReviews", id: id7, attrs: {
1150
+ id: id7,
1148
1151
  eventId,
1149
1152
  actorUserId,
1150
1153
  reviewedAt: now,
@@ -1159,9 +1162,9 @@ async function recoverNetworkCommercialEvent(db, sources, eventId, actorUserId,
1159
1162
  t: "update",
1160
1163
  ns: "networkCommercialExceptions",
1161
1164
  id: exceptionId2,
1162
- attrs: { status: "resolved", resolvedDigest: provider.digest, resolvedBy: actorUserId, reviewId: id6, updatedAt: now }
1165
+ attrs: { status: "resolved", resolvedDigest: provider.digest, resolvedBy: actorUserId, reviewId: id7, updatedAt: now }
1163
1166
  }] : []
1164
- ], { mutationId: `payment-recovery:${id6}`, guards: [
1167
+ ], { mutationId: `payment-recovery:${id7}`, guards: [
1165
1168
  { ns: "networkCommercialEvents", id: eventRowId2, exists: true, equals: { digest: stored.digest } },
1166
1169
  {
1167
1170
  ns: "networkCommercialExceptions",
@@ -1170,8 +1173,8 @@ async function recoverNetworkCommercialEvent(db, sources, eventId, actorUserId,
1170
1173
  ...exception ? { equals: { status: exception.status, updatedAt: exception.updatedAt } } : {}
1171
1174
  }
1172
1175
  ] });
1173
- await rebuildNetworkCommercialProjection(db, sources, `payment-recovery-rebuild:${id6}`, now, alerts);
1174
- return { recovered: true, eventId, reviewId: id6 };
1176
+ await rebuildNetworkCommercialProjection(db, sources, `payment-recovery-rebuild:${id7}`, now, alerts);
1177
+ return { recovered: true, eventId, reviewId: id7 };
1175
1178
  }
1176
1179
 
1177
1180
  // src/payments.ts
@@ -1759,7 +1762,7 @@ function findTier(tiers, tierId) {
1759
1762
  if (!tierId) return tiers.length === 1 ? tiers[0] : null;
1760
1763
  return tiers.find((tier) => tier.id === tierId) ?? null;
1761
1764
  }
1762
- function joinConfigTiers(tiers, defaults = {}, founding = /* @__PURE__ */ new Map()) {
1765
+ function joinConfigTiers(tiers, defaults = {}, founding = /* @__PURE__ */ new Map(), seats = /* @__PURE__ */ new Map()) {
1763
1766
  return tiers.map((tier) => ({
1764
1767
  id: tier.id,
1765
1768
  name: tier.name,
@@ -1768,7 +1771,8 @@ function joinConfigTiers(tiers, defaults = {}, founding = /* @__PURE__ */ new Ma
1768
1771
  interval: tier.interval ?? defaults.interval ?? "year",
1769
1772
  blurb: tier.blurb ?? "",
1770
1773
  free: tierIsFree(tier),
1771
- ...founding.has(tier.id) ? { founding: founding.get(tier.id) } : {}
1774
+ ...founding.has(tier.id) ? { founding: founding.get(tier.id) } : {},
1775
+ ...seats.has(tier.id) ? { seatOffer: seats.get(tier.id) } : {}
1772
1776
  }));
1773
1777
  }
1774
1778
 
@@ -1826,7 +1830,7 @@ async function frozenCheckoutVariables(db, app) {
1826
1830
  const intent = (await db.query({ subscriptionCheckoutIntents: { $: { where: { id: String(app.id) }, limit: 1 } } })).subscriptionCheckoutIntents?.[0];
1827
1831
  const offer = intent?.membershipOffer, contract = offer?.contract;
1828
1832
  const offering = contract?.offering, quote = intent?.paymentQuote;
1829
- const amount = contract?.dueTodayCents ?? intent?.priceCents;
1833
+ const amount = quote?.lineItems?.dueTodayCents ?? contract?.dueTodayCents ?? intent?.priceCents;
1830
1834
  const currency = offering?.currency ?? intent?.currency;
1831
1835
  let formatted = "";
1832
1836
  if (Number.isSafeInteger(amount) && Number(amount) > 0 && typeof currency === "string" && /^[a-z]{3}$/.test(currency)) {
@@ -1950,11 +1954,11 @@ async function sendTemplated(deps, input) {
1950
1954
  const previous = prior[0];
1951
1955
  if (previous && (previous.groupId !== input.group.id || previous.template !== input.template || previous.to !== decision.to || previous.transport !== decision.transport || previous.applicationId !== input.applicationId)) return { sent: false, reason: "delivery-identity-conflict" };
1952
1956
  const hash2 = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input.dedupeKey));
1953
- const id6 = previous ? String(previous.id) : `email_${Array.from(new Uint8Array(hash2), (b) => b.toString(16).padStart(2, "0")).join("")}`;
1957
+ const id7 = previous ? String(previous.id) : `email_${Array.from(new Uint8Array(hash2), (b) => b.toString(16).padStart(2, "0")).join("")}`;
1954
1958
  const attempt = Number(previous?.deliveryAttempt ?? 0) + 1;
1955
1959
  if (!Number.isSafeInteger(attempt) || attempt < 1) return { sent: false, reason: "delivery-state-invalid" };
1956
1960
  const row2 = {
1957
- id: id6,
1961
+ id: id7,
1958
1962
  groupId: input.group.id,
1959
1963
  to: decision.to,
1960
1964
  template: input.template,
@@ -1972,15 +1976,15 @@ async function sendTemplated(deps, input) {
1972
1976
  if (isRetiredEmailCopy(String(row2.subject), String(row2.body))) return { sent: false, reason: "template-retired" };
1973
1977
  if ((0, import_crm7.validateEmailCopy)({ subject: row2.subject, text: row2.body }, { allowedVariables: [] }).length) return { sent: false, reason: "template-invalid" };
1974
1978
  try {
1975
- const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id: id6, attrs: row2 }], {
1976
- mutationId: `email-claim:${id6}:${attempt}`,
1979
+ const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id: id7, attrs: row2 }], {
1980
+ mutationId: `email-claim:${id7}:${attempt}`,
1977
1981
  guards: [previous ? {
1978
1982
  ns: "emailLog",
1979
- id: id6,
1983
+ id: id7,
1980
1984
  exists: true,
1981
1985
  equals: { deliveryState: "retry_ready", ...previous.deliveryAttempt !== void 0 ? { deliveryAttempt: previous.deliveryAttempt } : {} },
1982
1986
  ...previous.deliveryAttempt === void 0 ? { absent: ["deliveryAttempt"] } : {}
1983
- } : { ns: "emailLog", id: id6, exists: false }]
1987
+ } : { ns: "emailLog", id: id7, exists: false }]
1984
1988
  });
1985
1989
  if (claim.duplicate) return { sent: false, reason: "delivery-uncertain" };
1986
1990
  } catch (error) {
@@ -2000,16 +2004,16 @@ async function sendTemplated(deps, input) {
2000
2004
  if (!response || typeof response.messageId !== "string" || !response.messageId) throw new Error("missing delivery receipt");
2001
2005
  messageId = response.messageId;
2002
2006
  } catch {
2003
- await deps.db.transact([{ t: "update", ns: "emailLog", id: id6, attrs: { deliveryState: "uncertain", error: "delivery-uncertain" } }], {
2004
- guards: [{ ns: "emailLog", id: id6, exists: true, equals: { deliveryState: "sending", deliveryAttempt: attempt } }]
2007
+ await deps.db.transact([{ t: "update", ns: "emailLog", id: id7, attrs: { deliveryState: "uncertain", error: "delivery-uncertain" } }], {
2008
+ guards: [{ ns: "emailLog", id: id7, exists: true, equals: { deliveryState: "sending", deliveryAttempt: attempt } }]
2005
2009
  });
2006
2010
  return { sent: false, reason: "delivery-uncertain" };
2007
2011
  }
2008
- await deps.db.transact([{ t: "update", ns: "emailLog", id: id6, attrs: { deliveryState: "sent", error: "", messageId, sentAt: deps.now() } }], {
2009
- mutationId: `email-confirmed:${id6}:${attempt}`,
2012
+ await deps.db.transact([{ t: "update", ns: "emailLog", id: id7, attrs: { deliveryState: "sent", error: "", messageId, sentAt: deps.now() } }], {
2013
+ mutationId: `email-confirmed:${id7}:${attempt}`,
2010
2014
  guards: [{
2011
2015
  ns: "emailLog",
2012
- id: id6,
2016
+ id: id7,
2013
2017
  exists: true,
2014
2018
  equals: { deliveryState: "sending", deliveryAttempt: attempt }
2015
2019
  }]
@@ -2194,8 +2198,8 @@ async function hash(value) {
2194
2198
  async function membershipDiscountCouponId(input, policyId) {
2195
2199
  return policyId ? `odla_m_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.skuId, policyId])}` : void 0;
2196
2200
  }
2197
- async function load(db, ns, id6) {
2198
- return (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
2201
+ async function load(db, ns, id7) {
2202
+ return (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
2199
2203
  }
2200
2204
  async function membershipOfferIdForRequest(input) {
2201
2205
  return `offer_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.applicationId, input.requestId])}`;
@@ -2261,6 +2265,7 @@ async function previewMembershipCommercialOffer(db, input) {
2261
2265
  ...contract.founding && contract.discountPolicy ? { cohortLabel: contract.discountPolicy.cohortLabel } : {},
2262
2266
  dueTodayCents: contract.dueTodayCents,
2263
2267
  renewalCents: contract.renewalCents,
2268
+ ...offering.seatPriceCents !== void 0 ? { seatPriceCents: offering.seatPriceCents } : {},
2264
2269
  ...discount ? { discountDuration: discount.duration === "once" ? "first_term" : "while_active" } : {},
2265
2270
  ...contract.initialTermEndsAt !== void 0 ? { initialTermEndsAt: contract.initialTermEndsAt } : {}
2266
2271
  };
@@ -2329,20 +2334,20 @@ async function issueMembershipCommercialOffer(db, input) {
2329
2334
 
2330
2335
  // src/commercial-evidence-guard.ts
2331
2336
  async function commercialEvidenceGuard(db, eventId, digest, code = "membership_payment_evidence_quarantined") {
2332
- const ns = "networkCommercialExceptions", id6 = `network-commercial-exception:${eventId}:collision`;
2333
- const row2 = (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
2334
- if (!row2) return { ns, id: id6, exists: false };
2337
+ const ns = "networkCommercialExceptions", id7 = `network-commercial-exception:${eventId}:collision`;
2338
+ const row2 = (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
2339
+ if (!row2) return { ns, id: id7, exists: false };
2335
2340
  if (row2.status !== "resolved" || row2.resolvedDigest !== digest || typeof digest !== "string") {
2336
2341
  throw Object.assign(new Error(code), { code });
2337
2342
  }
2338
- return { ns, id: id6, exists: true, equals: { status: "resolved", resolvedDigest: digest, updatedAt: row2.updatedAt } };
2343
+ return { ns, id: id7, exists: true, equals: { status: "resolved", resolvedDigest: digest, updatedAt: row2.updatedAt } };
2339
2344
  }
2340
2345
 
2341
2346
  // src/membership-authority-state.ts
2342
2347
  var membershipFail = (code) => {
2343
2348
  throw Object.assign(new Error(code), { code });
2344
2349
  };
2345
- var membershipLoad = async (db, ns, id6) => (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
2350
+ var membershipLoad = async (db, ns, id7) => (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
2346
2351
  var membershipScopeKeys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId"];
2347
2352
  var membershipScope = (input) => Object.fromEntries(membershipScopeKeys.map((key3) => [key3, input[key3]]));
2348
2353
  function requireMembershipScope(row2, input) {
@@ -2353,18 +2358,18 @@ async function loadMembershipAuthorityState(db, input) {
2353
2358
  if (!row2 || row2.mode !== "authoritative" || row2.chapterId !== input.chapterId || row2.environment !== input.environment || !Number.isSafeInteger(row2.revision) || Number(row2.revision) < 1) return membershipFail("membership_authority_unavailable");
2354
2359
  return row2;
2355
2360
  }
2356
- var membershipUpdate = (ns, id6, attrs) => ({ t: "update", ns, id: id6, attrs });
2361
+ var membershipUpdate = (ns, id7, attrs) => ({ t: "update", ns, id: id7, attrs });
2357
2362
  var membershipClock = (now) => {
2358
2363
  if (!Number.isSafeInteger(now) || now < 0) membershipFail("invalid_membership_clock");
2359
2364
  };
2360
2365
 
2361
2366
  // src/membership-restart-payment.ts
2362
2367
  async function membershipRestartPayment(db, target, member, now) {
2363
- const id6 = String(member.id), restart = await membershipLoad(db, "membershipRestart", id6);
2368
+ const id7 = String(member.id), restart = await membershipLoad(db, "membershipRestart", id7);
2364
2369
  requireMembershipScope(restart, target);
2365
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id6}:primary`);
2370
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id7}:primary`);
2366
2371
  requireMembershipScope(entitlement, target);
2367
- if (entitlement.membershipId !== id6 || entitlement.kind !== "primary") membershipFail("membership_restart_conflict");
2372
+ if (entitlement.membershipId !== id7 || entitlement.kind !== "primary") membershipFail("membership_restart_conflict");
2368
2373
  if (restart.status !== "ready" || member.status !== "restarting" || member.restartId !== restart.restartId || member.memberNumber !== restart.memberNumber || !Number.isSafeInteger(member.journeyApprovedAt)) membershipFail("membership_restart_conflict");
2369
2374
  return {
2370
2375
  restartId: String(restart.restartId),
@@ -2381,49 +2386,117 @@ async function membershipRestartPayment(db, target, member, now) {
2381
2386
  graceEndsAt: 0
2382
2387
  },
2383
2388
  ops: [
2384
- membershipUpdate("membershipRestart", id6, { status: "complete" }),
2385
- { t: "retract", ns: "networkMembership", id: id6, attrs: ["refundRequestedAt", "cancellationRequestedAt", "discountPolicyVersionId", "discountSnapshot"] }
2389
+ membershipUpdate("membershipRestart", id7, { status: "complete" }),
2390
+ { t: "retract", ns: "networkMembership", id: id7, attrs: ["refundRequestedAt", "cancellationRequestedAt", "discountPolicyVersionId", "discountSnapshot"] }
2386
2391
  ],
2387
2392
  guards: [
2388
- { ns: "networkMembership", id: id6, exists: true, equals: { status: "restarting", revision: member.revision, restartId: restart.restartId } },
2389
- { ns: "membershipRestart", id: id6, exists: true, equals: { status: "ready", restartId: restart.restartId } },
2393
+ { ns: "networkMembership", id: id7, exists: true, equals: { status: "restarting", revision: member.revision, restartId: restart.restartId } },
2394
+ { ns: "membershipRestart", id: id7, exists: true, equals: { status: "ready", restartId: restart.restartId } },
2390
2395
  { ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision, status: entitlement.status } }
2391
2396
  ]
2392
2397
  };
2393
2398
  }
2394
2399
 
2400
+ // src/checkout-named-seat.ts
2401
+ function checkoutNamedSeatSelection(value, purchaserEmail) {
2402
+ const row2 = value;
2403
+ const name = typeof row2?.recipientName === "string" ? row2.recipientName.trim().replace(/\s+/g, " ") : "";
2404
+ const email = normalizeChapterEmail(typeof row2?.recipientEmail === "string" ? row2.recipientEmail : "");
2405
+ const purchaser = normalizeChapterEmail(purchaserEmail);
2406
+ 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) {
2407
+ throw Object.assign(new Error("Enter a different recipient's name and email."), { code: "invalid_named_seat_recipient" });
2408
+ }
2409
+ return { recipientName: name, recipientEmail: email };
2410
+ }
2411
+ async function checkoutNamedSeatIdentity(target, email) {
2412
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([
2413
+ target.appId,
2414
+ target.chapterId,
2415
+ target.environment,
2416
+ target.runtime,
2417
+ email
2418
+ ])));
2419
+ return {
2420
+ seatId: (await membershipOfferIdForRequest({ ...target, requestId: "named_seat" })).replace(/^offer_/, "seat_"),
2421
+ membershipId: (await membershipOfferIdForRequest({ ...target, requestId: "checkout_binding" })).replace(/^offer_/, "membership_"),
2422
+ recipientClaimId: `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`
2423
+ };
2424
+ }
2425
+
2395
2426
  // src/membership-checkout-binding.ts
2396
2427
  var keys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId", "offerId", "offerDigest", "quoteDigest", "priceId"];
2397
2428
  var reject2 = (code) => {
2398
2429
  throw Object.assign(new Error(code), { code });
2399
2430
  };
2400
- var load2 = async (db, ns, id6) => (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
2431
+ var load2 = async (db, ns, id7) => (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
2401
2432
  async function membershipCheckoutBindingId(input) {
2402
2433
  return (await membershipOfferIdForRequest({ ...input, requestId: "checkout_binding" })).replace(/^offer_/, "checkout_");
2403
2434
  }
2404
2435
  async function verifyMembershipCheckoutBinding(value, input) {
2405
2436
  const row2 = value;
2406
2437
  if (!row2 || typeof row2 !== "object" || row2.id !== await membershipCheckoutBindingId(input) || keys.some((key3) => row2[key3] !== input[key3]) || !Number.isSafeInteger(row2.boundAt) || row2.boundAt < 0) return reject2("membership_checkout_conflict");
2407
- return { id: row2.id, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: row2.boundAt };
2438
+ if (Boolean(row2.namedSeat) !== Boolean(input.namedSeat)) return reject2("membership_checkout_conflict");
2439
+ if (input.namedSeat) {
2440
+ const expected = {
2441
+ ...checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail),
2442
+ purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail)
2443
+ };
2444
+ const identity2 = await checkoutNamedSeatIdentity(input, expected.recipientEmail);
2445
+ 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");
2446
+ }
2447
+ return {
2448
+ id: row2.id,
2449
+ ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])),
2450
+ boundAt: row2.boundAt,
2451
+ ...row2.namedSeat ? { namedSeat: row2.namedSeat } : {}
2452
+ };
2408
2453
  }
2409
- async function bindMembershipCheckout(db, input, now) {
2454
+ async function bindMembershipCheckout(db, input, now, seatPriceId) {
2410
2455
  if (!Number.isSafeInteger(now) || now < 0 || !/^[a-f0-9]{64}$/.test(input.offerDigest) || !/^[a-f0-9]{64}$/.test(input.quoteDigest) || !/^price_[A-Za-z0-9_]+$/.test(input.priceId)) return reject2("invalid_membership_checkout");
2411
2456
  const stateId = `${input.chapterId}:${input.environment}`;
2412
2457
  const state = await load2(db, "membershipAuthorityState", stateId);
2413
2458
  if (!state || state.mode !== "authoritative" || state.chapterId !== input.chapterId || state.environment !== input.environment || !Number.isSafeInteger(state.revision) || Number(state.revision) < 1) return reject2("membership_authority_unavailable");
2414
- const id6 = await membershipCheckoutBindingId(input);
2415
- const existing = await load2(db, "membershipOfferCheckout", id6);
2459
+ const id7 = await membershipCheckoutBindingId(input);
2460
+ const existing = await load2(db, "membershipOfferCheckout", id7);
2416
2461
  if (existing) return verifyMembershipCheckoutBinding(existing, input);
2417
2462
  const row2 = await load2(db, "membershipCheckoutOffer", input.offerId);
2418
2463
  const offer = await verifyIssuedMembershipOffer(row2, { ...input, now, lifetimeMs: 6e4 });
2419
2464
  if (offer.digest !== input.offerDigest || offer.expiresAt <= now || offer.issuedAt > now) return reject2("membership_offer_expired");
2420
- const restart = await load2(db, "membershipRestart", id6.replace(/^checkout_/, "membership_"));
2421
- const receipt = { id: id6, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: now };
2465
+ const restart = await load2(db, "membershipRestart", id7.replace(/^checkout_/, "membership_"));
2466
+ let namedSeat;
2467
+ if (input.namedSeat) {
2468
+ if (!seatPriceId || !/^price_[A-Za-z0-9_]+$/.test(seatPriceId) || seatPriceId === input.priceId || restart) return reject2("named_seat_purchase_unavailable");
2469
+ const selection = checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail);
2470
+ namedSeat = {
2471
+ ...selection,
2472
+ purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail),
2473
+ ...await checkoutNamedSeatIdentity(input, selection.recipientEmail),
2474
+ priceId: seatPriceId,
2475
+ amountCents: offer.contract.offering.seatPriceCents
2476
+ };
2477
+ }
2478
+ const receipt = { id: id7, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: now, ...namedSeat ? { namedSeat } : {} };
2422
2479
  try {
2423
- await db.transact([{ t: "update", ns: "membershipOfferCheckout", id: id6, attrs: receipt }], {
2424
- mutationId: `membership-checkout:${id6}${restart?.restartId ? `:${restart.restartId}` : ""}`,
2480
+ await db.transact([
2481
+ { t: "update", ns: "membershipOfferCheckout", id: id7, attrs: receipt },
2482
+ ...namedSeat ? [{ t: "update", ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, attrs: {
2483
+ id: namedSeat.recipientClaimId,
2484
+ appId: input.appId,
2485
+ chapterId: input.chapterId,
2486
+ environment: input.environment,
2487
+ runtime: input.runtime,
2488
+ seatId: namedSeat.seatId,
2489
+ recipientEmailNormalized: namedSeat.recipientEmail,
2490
+ createdAt: now
2491
+ } }] : []
2492
+ ], {
2493
+ mutationId: `membership-checkout:${id7}${restart?.restartId ? `:${restart.restartId}` : ""}`,
2425
2494
  guards: [
2426
- { ns: "membershipOfferCheckout", id: id6, exists: false },
2495
+ { ns: "membershipOfferCheckout", id: id7, exists: false },
2496
+ ...namedSeat ? [
2497
+ { ns: "networkSeat", id: namedSeat.seatId, exists: false },
2498
+ { ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, exists: false }
2499
+ ] : [],
2427
2500
  { ns: "membershipCheckoutOffer", id: input.offerId, exists: true, equals: { digest: input.offerDigest } },
2428
2501
  { ns: "membershipAuthorityState", id: stateId, exists: true, equals: { mode: "authoritative", revision: state.revision } }
2429
2502
  ]
@@ -2431,11 +2504,78 @@ async function bindMembershipCheckout(db, input, now) {
2431
2504
  } catch (error) {
2432
2505
  if (error.code !== "transact_guard_failed") throw error;
2433
2506
  }
2434
- const saved = await load2(db, "membershipOfferCheckout", id6);
2507
+ const saved = await load2(db, "membershipOfferCheckout", id7);
2435
2508
  if (!saved) return reject2("membership_binding_unconfirmed");
2436
2509
  return verifyMembershipCheckoutBinding(saved, input);
2437
2510
  }
2438
2511
 
2512
+ // src/membership-checkout-seat-payment.ts
2513
+ function checkoutSeatPaymentOperations(target, seat, input) {
2514
+ const { now, paidAt, endsAt } = input;
2515
+ const included = seat.amountCents === 0, entitlementId = `${seat.seatId}:entitlement`;
2516
+ const effectId = `${seat.seatId}:${included ? "invite" : "attach-renewal"}`;
2517
+ return [
2518
+ membershipUpdate("networkSeat", seat.seatId, {
2519
+ id: seat.seatId,
2520
+ ...membershipScope(target),
2521
+ primaryMembershipId: seat.membershipId,
2522
+ purchaserEmailNormalized: seat.purchaserEmail,
2523
+ recipientName: seat.recipientName,
2524
+ recipientEmailNormalized: seat.recipientEmail,
2525
+ recipientClaimId: seat.recipientClaimId,
2526
+ priceId: seat.priceId,
2527
+ primaryPriceId: input.primaryPriceId,
2528
+ amountCents: seat.amountCents,
2529
+ currency: input.offering.currency,
2530
+ interval: input.offering.interval,
2531
+ providerCustomerId: input.customerId,
2532
+ providerSubscriptionId: input.subscriptionId,
2533
+ combinedInvoice: { invoiceId: input.invoiceId, totalCents: input.invoiceTotalCents },
2534
+ status: included ? "pending_acceptance" : "renewal_pending",
2535
+ preparedAt: input.boundAt,
2536
+ purchasedAt: paidAt,
2537
+ termStartedAt: paidAt,
2538
+ termEndsAt: endsAt,
2539
+ acceptanceEndsAt: endsAt,
2540
+ seatPurchaseEndsAt: seatPurchaseDeadline(input.offering, { initialTermStartedAt: paidAt, termEndsAt: endsAt }),
2541
+ offeringSnapshot: input.offering,
2542
+ paymentEventId: input.eventId,
2543
+ ...included ? { entitlementId } : {},
2544
+ revision: 1,
2545
+ createdAt: now,
2546
+ updatedAt: now
2547
+ }),
2548
+ ...included ? [membershipUpdate("networkEntitlement", entitlementId, {
2549
+ id: entitlementId,
2550
+ ...membershipScope(target),
2551
+ membershipId: seat.membershipId,
2552
+ seatId: seat.seatId,
2553
+ kind: "named_seat",
2554
+ status: "pending_acceptance",
2555
+ termStartedAt: paidAt,
2556
+ termEndsAt: endsAt,
2557
+ offeringSnapshot: input.offering,
2558
+ sourceEventId: input.eventId,
2559
+ revision: 1,
2560
+ createdAt: now,
2561
+ updatedAt: now
2562
+ })] : [],
2563
+ membershipUpdate("membershipEffect", effectId, {
2564
+ id: effectId,
2565
+ chapterId: target.chapterId,
2566
+ environment: target.environment,
2567
+ runtime: target.runtime,
2568
+ membershipId: seat.membershipId,
2569
+ seatId: seat.seatId,
2570
+ kind: included ? "invite_named_recipient" : "attach_seat_renewal",
2571
+ status: "pending",
2572
+ attempts: 0,
2573
+ createdAt: now,
2574
+ updatedAt: now
2575
+ })
2576
+ ];
2577
+ }
2578
+
2439
2579
  // src/membership-payment-authority.ts
2440
2580
  async function networkMembershipId(input) {
2441
2581
  return (await membershipCheckoutBindingId(input)).replace(/^checkout_/, "membership_");
@@ -2451,11 +2591,14 @@ async function confirmMembershipPayment(db, input, now) {
2451
2591
  { ...input, offerId: String(binding.offerId), now, lifetimeMs: 6e4 }
2452
2592
  );
2453
2593
  if (binding.offerDigest !== offer.digest) membershipFail("membership_checkout_conflict");
2594
+ const { namedSeat } = await verifyMembershipCheckoutBinding(binding, binding);
2595
+ if (namedSeat && namedSeat.amountCents !== offer.contract.offering.seatPriceCents) membershipFail("membership_checkout_conflict");
2596
+ const totalCents = offer.contract.dueTodayCents + (namedSeat?.amountCents ?? 0);
2454
2597
  const eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
2455
2598
  if (!eventRow) membershipFail("membership_payment_evidence_pending");
2456
2599
  const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow.digest);
2457
2600
  const fact = eventRow.fact;
2458
- if (!fact || !fact.identity || !/^[a-f0-9]{64}$/.test(String(eventRow.digest)) || fact.digest !== eventRow.digest || fact.eventId !== input.eventId || fact.eventType !== "invoice.payment_succeeded" || fact.objectKind !== "invoice" || fact.billingReason !== "subscription_create" || fact.livemode !== (input.environment === "prod") || fact.status !== "paid" || fact.applicationId !== input.applicationId || fact.subscriptionId !== input.providerSubscriptionId || fact.customerId !== input.providerCustomerId || !/^in_[A-Za-z0-9_]+$/.test(fact.invoiceId ?? "") || fact.currency !== offer.contract.offering.currency || fact.invoiceTotalCents !== offer.contract.dueTodayCents || fact.amountPaidCents !== offer.contract.dueTodayCents || Object.entries({
2601
+ if (!fact || !fact.identity || !/^[a-f0-9]{64}$/.test(String(eventRow.digest)) || fact.digest !== eventRow.digest || fact.eventId !== input.eventId || fact.eventType !== "invoice.payment_succeeded" || fact.objectKind !== "invoice" || fact.billingReason !== "subscription_create" || fact.livemode !== (input.environment === "prod") || fact.status !== "paid" || fact.applicationId !== input.applicationId || fact.subscriptionId !== input.providerSubscriptionId || fact.customerId !== input.providerCustomerId || !/^in_[A-Za-z0-9_]+$/.test(fact.invoiceId ?? "") || fact.currency !== offer.contract.offering.currency || fact.invoiceTotalCents !== totalCents || fact.amountPaidCents !== totalCents || Object.entries({
2459
2602
  appId: input.appId,
2460
2603
  chapterId: input.chapterId,
2461
2604
  environment: input.environment,
@@ -2467,14 +2610,14 @@ async function confirmMembershipPayment(db, input, now) {
2467
2610
  const paidAt = fact.invoicePaidAt;
2468
2611
  const endsAt = offer.contract.initialTermEndsAt ?? fact.invoicePeriodEndsAt;
2469
2612
  if (!Number.isSafeInteger(paidAt) || Number(paidAt) < Math.floor(Number(binding.boundAt) / 1e3) * 1e3 || Number(paidAt) > now + 3e4 || !Number.isSafeInteger(endsAt) || Number(endsAt) <= Number(paidAt)) membershipFail("membership_payment_period_mismatch");
2470
- const id6 = await networkMembershipId(input), entitlementId = `${id6}:primary`;
2471
- let member = await membershipLoad(db, "networkMembership", id6);
2613
+ const id7 = await networkMembershipId(input), entitlementId = `${id7}:primary`;
2614
+ let member = await membershipLoad(db, "networkMembership", id7);
2472
2615
  if (!member || member.status === "restarting") {
2473
2616
  const offering = offer.contract.offering;
2474
2617
  const restart = member ? await membershipRestartPayment(db, input, member, now) : null;
2475
2618
  const attrs = {
2476
- id: id6,
2477
- membershipId: id6,
2619
+ id: id7,
2620
+ membershipId: id7,
2478
2621
  ...membershipScope(input),
2479
2622
  baseSkuId: input.skuId,
2480
2623
  status: "paid_pending_approval",
@@ -2490,8 +2633,9 @@ async function confirmMembershipPayment(db, input, now) {
2490
2633
  founding: offer.contract.founding,
2491
2634
  cohort: offer.contract.founding ? offer.contract.discountPolicy.cohortLabel : "standard",
2492
2635
  baseAmountCents: offering.basePriceCents,
2493
- amountCents: offer.contract.dueTodayCents,
2636
+ amountCents: totalCents,
2494
2637
  renewalAmountCents: offer.contract.renewalCents,
2638
+ ...namedSeat ? { namedSeatId: namedSeat.seatId } : {},
2495
2639
  currency: offering.currency,
2496
2640
  interval: offering.interval,
2497
2641
  initialTermStartedAt: paidAt,
@@ -2508,11 +2652,11 @@ async function confirmMembershipPayment(db, input, now) {
2508
2652
  };
2509
2653
  try {
2510
2654
  await db.transact([
2511
- membershipUpdate("networkMembership", id6, attrs),
2655
+ membershipUpdate("networkMembership", id7, attrs),
2512
2656
  membershipUpdate("networkEntitlement", entitlementId, {
2513
2657
  id: entitlementId,
2514
2658
  ...membershipScope(input),
2515
- membershipId: id6,
2659
+ membershipId: id7,
2516
2660
  kind: "primary",
2517
2661
  status: restart?.attrs.status === "active" ? "active" : "pending_approval",
2518
2662
  termStartedAt: paidAt,
@@ -2524,23 +2668,45 @@ async function confirmMembershipPayment(db, input, now) {
2524
2668
  updatedAt: now,
2525
2669
  ...restart?.entitlementAttrs
2526
2670
  }),
2527
- ...restart?.ops ?? []
2528
- ], { mutationId: `membership-paid:${id6}${restart ? `:${restart.restartId}` : ""}`, guards: [
2529
- ...restart?.guards ?? [{ ns: "networkMembership", id: id6, exists: false }],
2671
+ ...restart?.ops ?? [],
2672
+ ...namedSeat ? checkoutSeatPaymentOperations(input, namedSeat, {
2673
+ offering,
2674
+ primaryPriceId: String(binding.priceId),
2675
+ invoiceId: fact.invoiceId,
2676
+ invoiceTotalCents: totalCents,
2677
+ subscriptionId: input.providerSubscriptionId,
2678
+ customerId: input.providerCustomerId,
2679
+ eventId: input.eventId,
2680
+ paidAt: Number(paidAt),
2681
+ endsAt: Number(endsAt),
2682
+ boundAt: Number(binding.boundAt),
2683
+ now
2684
+ }) : []
2685
+ ], { mutationId: `membership-paid:${id7}${restart ? `:${restart.restartId}` : ""}`, guards: [
2686
+ ...restart?.guards ?? [{ ns: "networkMembership", id: id7, exists: false }],
2687
+ ...namedSeat ? [
2688
+ { ns: "networkSeat", id: namedSeat.seatId, exists: false },
2689
+ {
2690
+ ns: "networkSeatRecipient",
2691
+ id: namedSeat.recipientClaimId,
2692
+ exists: true,
2693
+ equals: { seatId: namedSeat.seatId, recipientEmailNormalized: namedSeat.recipientEmail }
2694
+ }
2695
+ ] : [],
2530
2696
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
2531
- { ns: "membershipOfferCheckout", id: String(binding.id), exists: true, equals: { offerDigest: offer.digest } },
2697
+ { ns: "membershipOfferCheckout", id: String(binding.id), exists: true, equals: { offerDigest: offer.digest, quoteDigest: binding.quoteDigest } },
2532
2698
  { ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
2533
2699
  evidenceGuard
2534
2700
  ] });
2535
2701
  } catch (error) {
2536
2702
  if (error.code !== "transact_guard_failed") throw error;
2537
2703
  }
2538
- member = await membershipLoad(db, "networkMembership", id6);
2704
+ member = await membershipLoad(db, "networkMembership", id7);
2539
2705
  }
2540
2706
  requireMembershipScope(member, input);
2541
2707
  if (member.purchaseOfferDigest !== offer.digest || member.providerInitialInvoiceId !== fact.invoiceId || member.providerSubscriptionId !== fact.subscriptionId || member.providerCustomerId !== fact.customerId) membershipFail("membership_purchase_conflict");
2542
2708
  return {
2543
- membershipId: id6,
2709
+ membershipId: id7,
2544
2710
  status: String(member.status),
2545
2711
  cohort: String(member.cohort),
2546
2712
  initialTermStartedAt: Number(member.initialTermStartedAt),
@@ -2611,19 +2777,19 @@ async function stopPreviousSubscription(member, target, secret) {
2611
2777
  async function prepareMembershipRestart(db, input, now, stripeSecret) {
2612
2778
  membershipClock(now);
2613
2779
  if (!/^sub_[A-Za-z0-9_]+$/.test(input.previousSubscriptionId)) membershipFail("membership_restart_conflict");
2614
- const id6 = await networkMembershipId(input), bindingId = await membershipCheckoutBindingId(input);
2615
- const restartId = `restart_${await stripePayloadDigest(JSON.stringify([id6, input.previousSubscriptionId]))}`;
2780
+ const id7 = await networkMembershipId(input), bindingId = await membershipCheckoutBindingId(input);
2781
+ const restartId = `restart_${await stripePayloadDigest(JSON.stringify([id7, input.previousSubscriptionId]))}`;
2616
2782
  const state = await loadMembershipAuthorityState(db, input);
2617
- const previous = await membershipLoad(db, "networkMembership", id6);
2783
+ const previous = await membershipLoad(db, "networkMembership", id7);
2618
2784
  requireMembershipScope(previous, input);
2619
2785
  const receipt = {
2620
2786
  applicationId: input.applicationId,
2621
- membershipId: id6,
2787
+ membershipId: id7,
2622
2788
  restartId,
2623
2789
  memberNumber: Number(previous.memberNumber),
2624
2790
  status: "ready"
2625
2791
  };
2626
- const existing = await membershipLoad(db, "membershipRestart", id6);
2792
+ const existing = await membershipLoad(db, "membershipRestart", id7);
2627
2793
  if (existing?.restartId === restartId && existing.status === "ready") {
2628
2794
  requireMembershipScope(existing, input);
2629
2795
  return receipt;
@@ -2639,33 +2805,33 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
2639
2805
  membershipUpdate("membershipPurchaseHistory", restartId, {
2640
2806
  id: restartId,
2641
2807
  ...membershipScope(input),
2642
- membershipId: id6,
2808
+ membershipId: id7,
2643
2809
  providerSubscriptionId: input.previousSubscriptionId,
2644
2810
  memberSnapshot: previous,
2645
2811
  bindingSnapshot: binding2,
2646
2812
  archivedAt: now
2647
2813
  }),
2648
- membershipUpdate("networkMembership", id6, { status: "restarting", restartId, revision: Number(previous.revision) + 1, updatedAt: now })
2814
+ membershipUpdate("networkMembership", id7, { status: "restarting", restartId, revision: Number(previous.revision) + 1, updatedAt: now })
2649
2815
  ], { mutationId: `membership-restart-reserve:${restartId}`, guards: [
2650
2816
  ...seats.guards,
2651
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: previous.revision, status: previous.status } },
2817
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: previous.revision, status: previous.status } },
2652
2818
  { ns: "membershipPurchaseHistory", id: restartId, exists: false },
2653
2819
  { ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding2.offerDigest } },
2654
2820
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
2655
2821
  ] });
2656
2822
  } else if (previous.restartId !== restartId) membershipFail("membership_restart_conflict");
2657
- const reserved = await membershipLoad(db, "networkMembership", id6);
2823
+ const reserved = await membershipLoad(db, "networkMembership", id7);
2658
2824
  requireMembershipScope(reserved, input);
2659
2825
  if (reserved.status !== "restarting" || reserved.restartId !== restartId) membershipFail("membership_restart_conflict");
2660
2826
  await stopPreviousSubscription(reserved, input, stripeSecret);
2661
2827
  const binding = await membershipLoad(db, "membershipOfferCheckout", bindingId);
2662
2828
  requireMembershipScope(binding, input);
2663
2829
  await db.transact([
2664
- membershipUpdate("membershipRestart", id6, { id: id6, ...membershipScope(input), ...receipt, previousSubscriptionId: input.previousSubscriptionId, createdAt: now }),
2830
+ membershipUpdate("membershipRestart", id7, { id: id7, ...membershipScope(input), ...receipt, previousSubscriptionId: input.previousSubscriptionId, createdAt: now }),
2665
2831
  { t: "delete", ns: "membershipOfferCheckout", id: bindingId }
2666
2832
  ], { mutationId: `membership-restart-ready:${restartId}`, guards: [
2667
- { ns: "membershipRestart", id: id6, exists: Boolean(existing), ...existing ? { equals: { restartId: existing.restartId, status: existing.status } } : {} },
2668
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: reserved.revision, status: "restarting", restartId } },
2833
+ { ns: "membershipRestart", id: id7, exists: Boolean(existing), ...existing ? { equals: { restartId: existing.restartId, status: existing.status } } : {} },
2834
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: reserved.revision, status: "restarting", restartId } },
2669
2835
  { ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding.offerDigest } }
2670
2836
  ] });
2671
2837
  return receipt;
@@ -2675,15 +2841,15 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
2675
2841
  async function readMembershipAccess(db, input, now) {
2676
2842
  membershipClock(now);
2677
2843
  await loadMembershipAuthorityState(db, input);
2678
- const id6 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id6);
2679
- if (!member) return { membershipId: id6, allowed: false, status: "unpaid", accessEndsAt: 0, revision: 0 };
2844
+ const id7 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id7);
2845
+ if (!member) return { membershipId: id7, allowed: false, status: "unpaid", accessEndsAt: 0, revision: 0 };
2680
2846
  requireMembershipScope(member, input);
2681
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id6}:primary`);
2847
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id7}:primary`);
2682
2848
  requireMembershipScope(entitlement, input);
2683
2849
  const endsAt = member.status === "payment_grace" ? Number(member.graceEndsAt) : Number(member.termEndsAt);
2684
- const allowed2 = ["active", "cancel_at_term_end", "payment_grace"].includes(String(member.status)) && member.journeyApprovedAt !== void 0 && Number.isSafeInteger(member.memberNumber) && Number(member.memberNumber) > 0 && member.refundedCents === 0 && entitlement.status === "active" && entitlement.membershipId === id6 && entitlement.kind === "primary" && Number.isSafeInteger(endsAt) && endsAt > now && Number(member.termStartedAt) <= now;
2850
+ const allowed2 = ["active", "cancel_at_term_end", "payment_grace"].includes(String(member.status)) && member.journeyApprovedAt !== void 0 && Number.isSafeInteger(member.memberNumber) && Number(member.memberNumber) > 0 && member.refundedCents === 0 && entitlement.status === "active" && entitlement.membershipId === id7 && entitlement.kind === "primary" && Number.isSafeInteger(endsAt) && endsAt > now && Number(member.termStartedAt) <= now;
2685
2851
  return {
2686
- membershipId: id6,
2852
+ membershipId: id7,
2687
2853
  allowed: allowed2,
2688
2854
  status: String(member.status),
2689
2855
  accessEndsAt: Number.isSafeInteger(endsAt) ? endsAt : 0,
@@ -2801,7 +2967,7 @@ async function refundChapterNetworkMembership(db, env, ctx, app) {
2801
2967
  const target = await membershipTargetForIntent(intent, app, env, ctx);
2802
2968
  const receipt = await ctx.membershipAuthority.refundMembership(target, env);
2803
2969
  const offer = intent.membershipOffer;
2804
- if (receipt.membershipId !== await networkMembershipId(target) || receipt.status !== "refunded" || receipt.subscriptionCanceled !== true || receipt.refundedCents !== offer.contract?.dueTodayCents) throw new Error("membership refund unconfirmed");
2970
+ if (receipt.membershipId !== await networkMembershipId(target) || receipt.status !== "refunded" || receipt.subscriptionCanceled !== true || receipt.refundedCents !== Number(offer.contract?.dueTodayCents) + Number(intent.paymentQuote?.lineItems?.namedSeatCents ?? 0)) throw new Error("membership refund unconfirmed");
2805
2971
  return receipt;
2806
2972
  }
2807
2973
  async function renewChapterNetworkMembership(db, env, ctx, app, eventId) {
@@ -2852,14 +3018,14 @@ function facts(snapshots) {
2852
3018
  ["payment_intent.updated", snapshots.payments?.objects ?? []]
2853
3019
  ];
2854
3020
  return groups.flatMap(([eventType, objects]) => objects.map((object2) => {
2855
- const id6 = typeof object2.id === "string" ? object2.id : "invalid";
3021
+ const id7 = typeof object2.id === "string" ? object2.id : "invalid";
2856
3022
  return normalizeNetworkCommercialStripeEvent({
2857
- id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id6}`,
3023
+ id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id7}`,
2858
3024
  type: eventType,
2859
3025
  created: typeof object2.created === "number" ? object2.created : 0,
2860
3026
  livemode: object2.livemode === true,
2861
3027
  data: { object: object2 }
2862
- }, `stripe-snapshot:${eventType}:${id6}`);
3028
+ }, `stripe-snapshot:${eventType}:${id7}`);
2863
3029
  }));
2864
3030
  }
2865
3031
  var selected = (row2) => ({
@@ -2934,9 +3100,9 @@ async function chapterBillingSubscriptions(secretKey, chapterId, env) {
2934
3100
  // src/approve-application.ts
2935
3101
  async function approveApplication(input) {
2936
3102
  const { db, env, ctx } = input;
2937
- const id6 = String(input.app.id ?? "");
2938
- if (!id6) return { ok: false, status: 404, error: "not found" };
2939
- const app = (await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } })).applications?.[0];
3103
+ const id7 = String(input.app.id ?? "");
3104
+ if (!id7) return { ok: false, status: 404, error: "not found" };
3105
+ const app = (await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } })).applications?.[0];
2940
3106
  if (!app || !chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME)) {
2941
3107
  return { ok: false, status: 404, error: "not found" };
2942
3108
  }
@@ -2970,10 +3136,10 @@ async function approveApplication(input) {
2970
3136
  try {
2971
3137
  if (!duplicate) {
2972
3138
  const write = await db.transact(
2973
- [{ t: "update", ns: "applications", id: id6, attrs: { status: "approved", approvalEffectsPending: true, approvalEffectsOrigin: input.origin } }],
2974
- { mutationId: `approve:${id6}`, guards: [{
3139
+ [{ t: "update", ns: "applications", id: id7, attrs: { status: "approved", approvalEffectsPending: true, approvalEffectsOrigin: input.origin } }],
3140
+ { mutationId: `approve:${id7}`, guards: [{
2975
3141
  ns: "applications",
2976
- id: id6,
3142
+ id: id7,
2977
3143
  exists: true,
2978
3144
  equals: Object.fromEntries(guardFields.filter((key3) => app[key3] !== void 0).map((key3) => [key3, app[key3]])),
2979
3145
  absent: guardFields.filter((key3) => app[key3] === void 0)
@@ -2985,7 +3151,7 @@ async function approveApplication(input) {
2985
3151
  if (error.code !== "transact_guard_failed") throw error;
2986
3152
  return { ok: false, status: 409, error: "application changed; reload before approving" };
2987
3153
  }
2988
- const current = (await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } })).applications?.[0];
3154
+ const current = (await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } })).applications?.[0];
2989
3155
  if (!current || current.status !== "approved" || current.canceled === true || guardFields.filter((key3) => key3 !== "status").some((key3) => current[key3] !== app[key3])) {
2990
3156
  return { ok: false, status: 409, error: "application changed; approval effects were not applied" };
2991
3157
  }
@@ -2995,7 +3161,7 @@ async function approveApplication(input) {
2995
3161
  } catch {
2996
3162
  return { ok: false, status: 503, error: "network membership approval reconciliation pending" };
2997
3163
  }
2998
- const afterAuthority = (await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } })).applications?.[0];
3164
+ const afterAuthority = (await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } })).applications?.[0];
2999
3165
  if (!afterAuthority || afterAuthority.status !== "approved" || afterAuthority.canceled === true || guardFields.filter((key3) => key3 !== "status").some((key3) => afterAuthority[key3] !== app[key3])) {
3000
3166
  return { ok: false, status: 409, error: "application changed during network approval" };
3001
3167
  }
@@ -3008,7 +3174,7 @@ async function approveApplication(input) {
3008
3174
  if (!userId && typeof app.email === "string") {
3009
3175
  const found = await clerkGetUserByEmail(secret, app.email, fetch, clerkRoleMetadata(ctx.chapter.auth));
3010
3176
  userId = found?.id ?? null;
3011
- if (userId) await db.transact([{ t: "update", ns: "applications", id: id6, attrs: { clerkUserId: userId } }]);
3177
+ if (userId) await db.transact([{ t: "update", ns: "applications", id: id7, attrs: { clerkUserId: userId } }]);
3012
3178
  }
3013
3179
  if (userId) {
3014
3180
  const user = await clerkGetUser(secret, userId, fetch, clerkRoleMetadata(ctx.chapter.auth));
@@ -3030,8 +3196,8 @@ async function approveApplication(input) {
3030
3196
  template: send,
3031
3197
  to: app.email,
3032
3198
  vars: { firstName: String(app.firstName ?? ""), membersUrl: `${input.origin}/members/` },
3033
- applicationId: id6,
3034
- dedupeKey: `approve:${id6}`
3199
+ applicationId: id7,
3200
+ dedupeKey: `approve:${id7}`
3035
3201
  }
3036
3202
  );
3037
3203
  emailLogged = result.sent;
@@ -3039,10 +3205,10 @@ async function approveApplication(input) {
3039
3205
  return { ok: false, status: 503, error: "approval notice reconciliation pending", rolePromoted, emailLogged };
3040
3206
  }
3041
3207
  }
3042
- await db.transact([{ t: "update", ns: "applications", id: id6, attrs: { approvalEffectsPending: false } }], {
3208
+ await db.transact([{ t: "update", ns: "applications", id: id7, attrs: { approvalEffectsPending: false } }], {
3043
3209
  guards: [{
3044
3210
  ns: "applications",
3045
- id: id6,
3211
+ id: id7,
3046
3212
  exists: true,
3047
3213
  equals: { status: "approved", ...app.canceled !== void 0 ? { canceled: app.canceled } : {} },
3048
3214
  ...app.canceled === void 0 ? { absent: ["canceled"] } : {}
@@ -3071,6 +3237,48 @@ async function revokeRefundedApplicationRole(db, env, ctx, applicationId) {
3071
3237
  }
3072
3238
  }
3073
3239
 
3240
+ // src/payment-receipt.ts
3241
+ async function retainPaymentReceipt(db, intent, rawBody) {
3242
+ const quote = intent.paymentQuote;
3243
+ if (!quote || intent.paymentReceipt) return;
3244
+ const event2 = JSON.parse(rawBody), invoice = event2.data?.object;
3245
+ const paidAt = invoice?.status_transitions?.paid_at ?? event2.created;
3246
+ if (!/^in_[A-Za-z0-9_]+$/.test(invoice?.id ?? "") || !Number.isSafeInteger(paidAt) || paidAt <= 0) return;
3247
+ const receipt = {
3248
+ invoiceId: invoice.id,
3249
+ paidAt: paidAt * 1e3,
3250
+ currency: quote.currency,
3251
+ tierName: quote.tierName,
3252
+ merchant: quote.merchantDisclosureText,
3253
+ membershipCents: quote.lineItems.dueTodayCents - (quote.lineItems.namedSeatCents ?? 0),
3254
+ totalCents: quote.lineItems.dueTodayCents,
3255
+ ...quote.namedSeat ? { namedSeat: { recipientName: quote.namedSeat.recipientName, amountCents: quote.lineItems.namedSeatCents } } : {}
3256
+ };
3257
+ await db.transact([{ t: "update", ns: "subscriptionCheckoutIntents", id: String(intent.id), attrs: { paymentReceipt: receipt } }], {
3258
+ mutationId: `payment-receipt:${invoice.id}`,
3259
+ guards: [{
3260
+ ns: "subscriptionCheckoutIntents",
3261
+ id: String(intent.id),
3262
+ exists: true,
3263
+ equals: { providerSubscriptionId: intent.providerSubscriptionId, providerCustomerId: intent.providerCustomerId },
3264
+ absent: ["paymentReceipt"]
3265
+ }]
3266
+ });
3267
+ }
3268
+ function paymentReceiptText(receipt, chapterName) {
3269
+ const money = (cents) => new Intl.NumberFormat("en-US", { style: "currency", currency: receipt.currency }).format(cents / 100);
3270
+ return [
3271
+ chapterName,
3272
+ "Payment receipt",
3273
+ `Invoice: ${receipt.invoiceId}`,
3274
+ `Paid: ${new Intl.DateTimeFormat("en-US", { dateStyle: "long", timeZone: "UTC" }).format(receipt.paidAt)}`,
3275
+ receipt.merchant,
3276
+ `${receipt.tierName}: ${money(receipt.membershipCents)}`,
3277
+ ...receipt.namedSeat ? [`Additional member \u2014 ${receipt.namedSeat.recipientName}: ${receipt.namedSeat.amountCents === 0 ? "Included \u2014 " : ""}${money(receipt.namedSeat.amountCents)}`] : [],
3278
+ `Total paid: ${money(receipt.totalCents)}`
3279
+ ].filter(Boolean).join("\n");
3280
+ }
3281
+
3074
3282
  // src/payment-notifications.ts
3075
3283
  var groupFor = async (db, app) => {
3076
3284
  const result = await db.query({ groups: { $: { where: { id: String(app.groupId ?? "") }, limit: 1 } } });
@@ -3264,10 +3472,10 @@ async function redeemGiftClaim(db, claim, beneficiary, now) {
3264
3472
  tierId: target.tierId,
3265
3473
  ...typeof existing?.clerkUserId === "string" ? { clerkUserId: existing.clerkUserId } : {}
3266
3474
  };
3267
- const id6 = existing ? String(existing.id) : `gift-application:${String(claim.id)}`;
3475
+ const id7 = existing ? String(existing.id) : `gift-application:${String(claim.id)}`;
3268
3476
  const status2 = existing && existing.status !== "submitted" ? String(existing.status) : "paid_pending_vetting";
3269
- ops.push({ t: "update", ns: "applications", id: id6, attrs: {
3270
- id: id6,
3477
+ ops.push({ t: "update", ns: "applications", id: id7, attrs: {
3478
+ id: id7,
3271
3479
  groupId: chapterId,
3272
3480
  email: beneficiary.email,
3273
3481
  emailNormalized,
@@ -3278,12 +3486,12 @@ async function redeemGiftClaim(db, claim, beneficiary, now) {
3278
3486
  clerkUserId: beneficiary.userId,
3279
3487
  ...existing ? {} : { createdAt: now }
3280
3488
  } });
3281
- result = { kind: "application", id: id6 };
3489
+ result = { kind: "application", id: id7 };
3282
3490
  } else {
3283
3491
  if (beneficiary.applicationId) return null;
3284
- const id6 = `gift-entitlement:${String(claim.id)}`;
3285
- ops.push({ t: "update", ns: "giftEntitlements", id: id6, attrs: {
3286
- id: id6,
3492
+ const id7 = `gift-entitlement:${String(claim.id)}`;
3493
+ ops.push({ t: "update", ns: "giftEntitlements", id: id7, attrs: {
3494
+ id: id7,
3287
3495
  claimId: String(claim.id),
3288
3496
  orderId: String(claim.orderId),
3289
3497
  chapterId,
@@ -3293,7 +3501,7 @@ async function redeemGiftClaim(db, claim, beneficiary, now) {
3293
3501
  active: true,
3294
3502
  createdAt: now
3295
3503
  } });
3296
- result = { kind: "entitlement", id: id6 };
3504
+ result = { kind: "entitlement", id: id7 };
3297
3505
  }
3298
3506
  ops.unshift({ t: "update", ns: "giftClaims", id: String(claim.id), attrs: {
3299
3507
  status: "redeemed",
@@ -3467,9 +3675,9 @@ async function storeStripeReceipt(db, input) {
3467
3675
  return "stored";
3468
3676
  }
3469
3677
  if (existing) return existing.digest === input.digest || existing.digest === input.legacyDigest ? "replayed" : "collision";
3470
- const id6 = `stripe-event:${input.eventId}`;
3471
- const write = await db.transact([{ t: "update", ns: "stripeEventReceipts", id: id6, attrs: {
3472
- id: id6,
3678
+ const id7 = `stripe-event:${input.eventId}`;
3679
+ const write = await db.transact([{ t: "update", ns: "stripeEventReceipts", id: id7, attrs: {
3680
+ id: id7,
3473
3681
  eventId: input.eventId,
3474
3682
  digest: input.digest,
3475
3683
  disposition: input.disposition,
@@ -3633,6 +3841,7 @@ async function reconcileChapterStripeEvent(input, env, ctx) {
3633
3841
  } catch {
3634
3842
  return json({ error: "network membership payment reconciliation pending" }, 503);
3635
3843
  }
3844
+ if (intent) await retainPaymentReceipt(db, intent, rawBody);
3636
3845
  }
3637
3846
  }
3638
3847
  const invoiceEvent = event2.kind === "first_payment" || event2.kind === "renewal" || event2.kind === "renewal_failed";
@@ -3701,9 +3910,9 @@ var EVENT_ID = /^evt_[A-Za-z0-9_]{1,150}$/;
3701
3910
  var fail3 = (message, code) => Object.assign(new Error(message), { code });
3702
3911
  async function resolveNetworkCommercialCollision(db, sources, input, now = Date.now(), alerts) {
3703
3912
  if (!EVENT_ID.test(input.eventId)) throw fail3("invalid event reference", "invalid_event");
3704
- const id6 = exceptionId(input.eventId, "collision"), reviewId = `resolve:${input.mutationId}`;
3913
+ const id7 = exceptionId(input.eventId, "collision"), reviewId = `resolve:${input.mutationId}`;
3705
3914
  const found = await db.query({
3706
- networkCommercialExceptions: { $: { where: { id: id6 }, limit: 1 } },
3915
+ networkCommercialExceptions: { $: { where: { id: id7 }, limit: 1 } },
3707
3916
  networkCommercialEvents: { $: { where: { eventId: input.eventId }, limit: 1 } }
3708
3917
  });
3709
3918
  const exception = found.networkCommercialExceptions?.[0], stored = found.networkCommercialEvents?.[0];
@@ -3726,7 +3935,7 @@ async function resolveNetworkCommercialCollision(db, sources, input, now = Date.
3726
3935
  action: "resolve",
3727
3936
  reason: input.reason
3728
3937
  } },
3729
- { t: "update", ns: "networkCommercialExceptions", id: id6, attrs: {
3938
+ { t: "update", ns: "networkCommercialExceptions", id: id7, attrs: {
3730
3939
  status: "resolved",
3731
3940
  resolvedDigest: stored.digest,
3732
3941
  ...dismissed ? { reviewedDigest: dismissed } : {},
@@ -3735,7 +3944,7 @@ async function resolveNetworkCommercialCollision(db, sources, input, now = Date.
3735
3944
  updatedAt: now
3736
3945
  } }
3737
3946
  ], { mutationId: input.mutationId, guards: [
3738
- { ns: "networkCommercialExceptions", id: id6, exists: true, equals: { status: "active", updatedAt: exception.updatedAt } }
3947
+ { ns: "networkCommercialExceptions", id: id7, exists: true, equals: { status: "active", updatedAt: exception.updatedAt } }
3739
3948
  ] });
3740
3949
  if (write.duplicate) throw fail3("mutationId was already used for another change", "mutation_reused");
3741
3950
  const projection = await rebuildNetworkCommercialProjection(db, sources, `${input.mutationId}:rebuild`, now, alerts);
@@ -3850,10 +4059,10 @@ var handlePaymentRecovery = async (req, url, env, ctx) => {
3850
4059
  const provider = await readStripeRecoveryEvent(db, eventId);
3851
4060
  const event2 = normalizeWebhookEvent(provider.event, { requireProviderPayment: Boolean(ctx.membershipAuthority) });
3852
4061
  if (provider.event.livemode !== (env.ODLA_ENV === "prod") || classifyMemberStripeEvent(stripeEventIdentity(event2), target, false) !== "apply") return json({ error: "provider event belongs to another or unknown runtime" }, 409);
3853
- const id6 = crypto.randomUUID();
4062
+ const id7 = crypto.randomUUID();
3854
4063
  await db.transact([
3855
- { t: "update", ns: "paymentRecoveryReviews", id: id6, attrs: {
3856
- id: id6,
4064
+ { t: "update", ns: "paymentRecoveryReviews", id: id7, attrs: {
4065
+ id: id7,
3857
4066
  eventId,
3858
4067
  actorUserId: user.userId,
3859
4068
  reviewedAt: Date.now(),
@@ -3862,8 +4071,8 @@ var handlePaymentRecovery = async (req, url, env, ctx) => {
3862
4071
  scope: `${target.appId}:${target.environment}:${target.runtime}`,
3863
4072
  previousState: row2
3864
4073
  } },
3865
- { t: "update", ns: "stripeEventReceipts", id: String(row2.id), attrs: { retryReady: true, reviewedDigest: provider.digest, reviewId: id6 } }
3866
- ], { mutationId: `payment-recovery:${id6}`, guards: [{
4074
+ { t: "update", ns: "stripeEventReceipts", id: String(row2.id), attrs: { retryReady: true, reviewedDigest: provider.digest, reviewId: id7 } }
4075
+ ], { mutationId: `payment-recovery:${id7}`, guards: [{
3867
4076
  ns: "stripeEventReceipts",
3868
4077
  id: String(row2.id),
3869
4078
  exists: true,
@@ -3932,43 +4141,121 @@ async function closeRecipientApplication(db, env, ctx, seatId) {
3932
4141
  }
3933
4142
 
3934
4143
  // src/membership-seat-refund-stripe.ts
4144
+ var import_stripe7 = require("@odla-ai/stripe");
4145
+
4146
+ // src/membership-checkout-seat-refund.ts
3935
4147
  var import_stripe6 = require("@odla-ai/stripe");
3936
4148
  var fail4 = (code = "named_seat_refund_mismatch") => {
3937
4149
  throw Object.assign(new Error(code), { code });
3938
4150
  };
4151
+ var id2 = (value) => typeof value === "string" ? value : value?.id ?? "";
4152
+ async function refundCheckoutNamedSeat(secretKey, seat) {
4153
+ const invoiceRef = seat.combinedInvoice;
4154
+ 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();
4155
+ const call = async (method, path, params, key3) => {
4156
+ const result = await (0, import_stripe6.stripeRequest)({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
4157
+ if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
4158
+ return result.body;
4159
+ };
4160
+ const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId };
4161
+ const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
4162
+ 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();
4163
+ const invoice = await call("GET", `/v1/invoices/${encodeURIComponent(invoiceRef.invoiceId)}`);
4164
+ 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();
4165
+ let chargeId = id2(invoice.charge), paymentId = id2(invoice.payment_intent);
4166
+ if (!chargeId && !paymentId) {
4167
+ const payments = await call("GET", "/v1/invoice_payments", { invoice: invoice.id, status: "paid", limit: 100 });
4168
+ if (!Array.isArray(payments.data) || payments.data.length !== 1 || payments.has_more !== false) fail4();
4169
+ const payment = payments.data[0];
4170
+ if (id2(payment.invoice) !== invoice.id || payment.status !== "paid" || payment.payment?.type !== "payment_intent") fail4();
4171
+ paymentId = id2(payment.payment.payment_intent);
4172
+ }
4173
+ if (!chargeId) {
4174
+ if (!/^pi_[A-Za-z0-9_]+$/.test(paymentId)) fail4();
4175
+ const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(paymentId)}`);
4176
+ 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();
4177
+ chargeId = id2(payment.latest_charge);
4178
+ }
4179
+ if (!/^ch_[A-Za-z0-9_]+$/.test(chargeId)) fail4();
4180
+ const charge = await call("GET", `/v1/charges/${encodeURIComponent(chargeId)}`);
4181
+ 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();
4182
+ const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
4183
+ if (items.length > 1) fail4();
4184
+ if (items.length && sub.status !== "canceled") {
4185
+ const item = items[0];
4186
+ if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.quantity !== 1 || item.price?.id !== seat.priceId) fail4();
4187
+ const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
4188
+ if (removed.id !== item.id || removed.deleted !== true) fail4("named_seat_renewal_removal_unconfirmed");
4189
+ }
4190
+ if (charge.amount_refunded === charge.amount && charge.refunded === true) return { refundedCents: seat.amountCents, refundIds: [] };
4191
+ const list = await call("GET", "/v1/refunds", { charge: chargeId, limit: 100 });
4192
+ if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail4("named_seat_refund_history_incomplete");
4193
+ let refundedCents = 0;
4194
+ const refundIds = [];
4195
+ for (const refund of list.data) {
4196
+ 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();
4197
+ if (["pending", "requires_action"].includes(refund.status)) fail4("named_seat_refund_pending");
4198
+ if (refund.status === "succeeded") {
4199
+ if (refund.metadata?.seatId !== seat.id || refund.metadata?.membershipId !== seat.primaryMembershipId) fail4("named_seat_refund_allocation_ambiguous");
4200
+ refundedCents += refund.amount;
4201
+ refundIds.push(refund.id);
4202
+ }
4203
+ }
4204
+ if (refundedCents !== charge.amount_refunded || refundedCents > seat.amountCents) fail4();
4205
+ if (refundedCents < seat.amountCents) {
4206
+ const remaining = seat.amountCents - refundedCents;
4207
+ const refund = await call("POST", "/v1/refunds", {
4208
+ charge: chargeId,
4209
+ amount: remaining,
4210
+ reason: "requested_by_customer",
4211
+ metadata: { ...metadata, membershipId: seat.primaryMembershipId, seatId: seat.id }
4212
+ }, `combined-seat-refund:${seat.id}:${remaining}`);
4213
+ if (id2(refund.charge) !== chargeId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail4();
4214
+ if (refund.status !== "succeeded") fail4("named_seat_refund_pending");
4215
+ refundedCents += remaining;
4216
+ refundIds.push(refund.id);
4217
+ }
4218
+ return { refundedCents, refundIds };
4219
+ }
4220
+
4221
+ // src/membership-seat-refund-stripe.ts
4222
+ var fail5 = (code = "named_seat_refund_mismatch") => {
4223
+ throw Object.assign(new Error(code), { code });
4224
+ };
3939
4225
  async function refundNamedSeatStripePayment(secretKey, seat) {
3940
- if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail4();
4226
+ if (seat.combinedInvoice) return refundCheckoutNamedSeat(secretKey, seat);
4227
+ if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail5();
3941
4228
  const call = async (method, path, params, idempotencyKey) => {
3942
- const result = await (0, import_stripe6.stripeRequest)({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
3943
- if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
4229
+ const result = await (0, import_stripe7.stripeRequest)({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
4230
+ if (!result.ok || !result.body) return fail5("named_seat_refund_provider_unavailable");
3944
4231
  return result.body;
3945
4232
  };
3946
4233
  const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
3947
4234
  const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`);
3948
- if (payment.id !== seat.providerPaymentIntentId || payment.customer !== seat.providerCustomerId || payment.amount !== seat.amountCents || payment.amount_received !== seat.amountCents || payment.currency !== seat.currency || payment.livemode !== (seat.environment === "prod") || payment.status !== "succeeded" || !Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === value)) fail4();
4235
+ if (payment.id !== seat.providerPaymentIntentId || payment.customer !== seat.providerCustomerId || payment.amount !== seat.amountCents || payment.amount_received !== seat.amountCents || payment.currency !== seat.currency || payment.livemode !== (seat.environment === "prod") || payment.status !== "succeeded" || !Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === value)) fail5();
3949
4236
  const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
3950
- if (sub.id !== seat.providerSubscriptionId || sub.customer !== seat.providerCustomerId || sub.livemode !== (seat.environment === "prod") || !Object.entries({ ...stripePartitionMetadata(seat), applicationId: seat.applicationId }).every(([key3, value]) => sub.metadata?.[key3] === value) || sub.items?.has_more !== false || !Array.isArray(sub.items?.data)) fail4();
4237
+ if (sub.id !== seat.providerSubscriptionId || sub.customer !== seat.providerCustomerId || sub.livemode !== (seat.environment === "prod") || !Object.entries({ ...stripePartitionMetadata(seat), applicationId: seat.applicationId }).every(([key3, value]) => sub.metadata?.[key3] === value) || sub.items?.has_more !== false || !Array.isArray(sub.items?.data)) fail5();
3951
4238
  const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
3952
- if (items.length > 1) fail4();
4239
+ if (items.length > 1) fail5();
3953
4240
  if (items.length && sub.status !== "canceled") {
3954
4241
  const item = items[0];
3955
- if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail4();
4242
+ if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail5();
3956
4243
  const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
3957
- if (removed.id !== item.id || removed.deleted !== true) fail4("named_seat_renewal_removal_unconfirmed");
4244
+ if (removed.id !== item.id || removed.deleted !== true) fail5("named_seat_renewal_removal_unconfirmed");
3958
4245
  }
3959
4246
  const list = await call("GET", "/v1/refunds", { payment_intent: seat.providerPaymentIntentId, limit: 100 });
3960
- if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail4("named_seat_refund_history_incomplete");
4247
+ if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail5("named_seat_refund_history_incomplete");
3961
4248
  let refundedCents = 0, pendingCents = 0;
3962
4249
  const refundIds = [];
3963
4250
  for (const refund of list.data) {
3964
- if (!/^re_[A-Za-z0-9_]+$/.test(refund.id) || refund.payment_intent !== seat.providerPaymentIntentId || refund.currency !== seat.currency || !Number.isSafeInteger(refund.amount) || refund.amount <= 0 || !["succeeded", "pending", "requires_action", "failed", "canceled"].includes(refund.status)) fail4();
4251
+ if (!/^re_[A-Za-z0-9_]+$/.test(refund.id) || refund.payment_intent !== seat.providerPaymentIntentId || refund.currency !== seat.currency || !Number.isSafeInteger(refund.amount) || refund.amount <= 0 || !["succeeded", "pending", "requires_action", "failed", "canceled"].includes(refund.status)) fail5();
3965
4252
  if (refund.status === "succeeded") {
3966
4253
  refundedCents += refund.amount;
3967
4254
  refundIds.push(refund.id);
3968
4255
  } else if (refund.status === "pending" || refund.status === "requires_action") pendingCents += refund.amount;
3969
4256
  }
3970
- if (refundedCents + pendingCents > seat.amountCents) fail4();
3971
- if (pendingCents) fail4("named_seat_refund_pending");
4257
+ if (refundedCents + pendingCents > seat.amountCents) fail5();
4258
+ if (pendingCents) fail5("named_seat_refund_pending");
3972
4259
  if (refundedCents < seat.amountCents) {
3973
4260
  const remaining = seat.amountCents - refundedCents;
3974
4261
  const refund = await call("POST", "/v1/refunds", {
@@ -3977,8 +4264,8 @@ async function refundNamedSeatStripePayment(secretKey, seat) {
3977
4264
  reason: "requested_by_customer",
3978
4265
  metadata
3979
4266
  }, `named-seat-refund:${seat.id}:${remaining}`);
3980
- if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail4();
3981
- if (refund.status !== "succeeded") fail4("named_seat_refund_pending");
4267
+ if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail5();
4268
+ if (refund.status !== "succeeded") fail5("named_seat_refund_pending");
3982
4269
  refundedCents += remaining;
3983
4270
  refundIds.push(refund.id);
3984
4271
  }
@@ -4000,8 +4287,8 @@ async function reserveNamedMembershipSeat(db, input, now) {
4000
4287
  const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
4001
4288
  requireMembershipScope(member, input);
4002
4289
  if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
4003
- const id6 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
4004
- let seat = await membershipLoad(db, "networkSeat", id6);
4290
+ const id7 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
4291
+ let seat = await membershipLoad(db, "networkSeat", id7);
4005
4292
  if (!seat) {
4006
4293
  const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
4007
4294
  const purchaseEndsAt = seatPurchaseDeadline(offering, member);
@@ -4009,7 +4296,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4009
4296
  membershipFail("named_seat_purchase_unavailable");
4010
4297
  }
4011
4298
  const attrs = {
4012
- id: id6,
4299
+ id: id7,
4013
4300
  ...membershipScope(input),
4014
4301
  primaryMembershipId: memberId,
4015
4302
  recipientName: name,
@@ -4034,20 +4321,20 @@ async function reserveNamedMembershipSeat(db, input, now) {
4034
4321
  };
4035
4322
  try {
4036
4323
  await db.transact([
4037
- membershipUpdate("networkSeat", id6, attrs),
4324
+ membershipUpdate("networkSeat", id7, attrs),
4038
4325
  membershipUpdate("networkSeatRecipient", claimId, {
4039
4326
  id: claimId,
4040
4327
  appId: input.appId,
4041
4328
  chapterId: input.chapterId,
4042
4329
  environment: input.environment,
4043
4330
  runtime: input.runtime,
4044
- seatId: id6,
4331
+ seatId: id7,
4045
4332
  recipientEmailNormalized: email,
4046
4333
  createdAt: now
4047
4334
  }),
4048
- membershipUpdate("networkMembership", memberId, { namedSeatId: id6, revision: Number(member.revision) + 1, updatedAt: now })
4049
- ], { mutationId: `named-seat-reserve:${id6}`, guards: [
4050
- { ns: "networkSeat", id: id6, exists: false },
4335
+ membershipUpdate("networkMembership", memberId, { namedSeatId: id7, revision: Number(member.revision) + 1, updatedAt: now })
4336
+ ], { mutationId: `named-seat-reserve:${id7}`, guards: [
4337
+ { ns: "networkSeat", id: id7, exists: false },
4051
4338
  { ns: "networkSeatRecipient", id: claimId, exists: false },
4052
4339
  { ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
4053
4340
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
@@ -4055,7 +4342,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4055
4342
  } catch (error) {
4056
4343
  if (error.code !== "transact_guard_failed") throw error;
4057
4344
  }
4058
- seat = await membershipLoad(db, "networkSeat", id6);
4345
+ seat = await membershipLoad(db, "networkSeat", id7);
4059
4346
  if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
4060
4347
  }
4061
4348
  requireMembershipScope(seat, input);
@@ -4065,7 +4352,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4065
4352
 
4066
4353
  // src/membership-seat-included.ts
4067
4354
  var namedSeatIncluded = (seat) => Number(seat.amountCents) === 0;
4068
- var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean(seat.providerPaymentIntentId && seat.providerSubscriptionItemId);
4355
+ var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean((seat.providerPaymentIntentId || seat.combinedInvoice) && seat.providerSubscriptionItemId);
4069
4356
  function namedSeatRecipientState(seat) {
4070
4357
  switch (String(seat.status)) {
4071
4358
  case "renewal_pending":
@@ -4169,7 +4456,7 @@ async function requestNamedSeatRefund(db, input, now) {
4169
4456
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
4170
4457
  requireMembershipScope(member, input);
4171
4458
  if (seat.status === "refunded" || seat.status === "refund_pending") return;
4172
- if (!seat.purchasedAt || !seat.providerPaymentIntentId && !namedSeatIncluded(seat) || input.reason === "declined" && seat.journeyApprovedAt !== void 0 || input.reason === "primary_closed" && !["refunded", "declined"].includes(String(member.status))) membershipFail("named_seat_refund_not_eligible");
4459
+ if (!seat.purchasedAt || !seat.providerPaymentIntentId && !seat.combinedInvoice && !namedSeatIncluded(seat) || input.reason === "declined" && seat.journeyApprovedAt !== void 0 || input.reason === "primary_closed" && !["refunded", "declined"].includes(String(member.status))) membershipFail("named_seat_refund_not_eligible");
4173
4460
  const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
4174
4461
  await db.transact([
4175
4462
  membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
@@ -4282,8 +4569,8 @@ async function lowerSeatPrice(db, scope2, now, limit = 100) {
4282
4569
  const receipt = { seatPriceCents: target, repriced: 0, skipped: [], more: rows5.length > limit };
4283
4570
  for (const member of rows5.slice(0, limit)) {
4284
4571
  if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) throw new Error("membership reprice scope mismatch");
4285
- const id6 = String(member.id), skip = (reason) => {
4286
- receipt.skipped.push({ membershipId: id6, reason });
4572
+ const id7 = String(member.id), skip = (reason) => {
4573
+ receipt.skipped.push({ membershipId: id7, reason });
4287
4574
  };
4288
4575
  const offering = membershipOfferingVersion(member.offeringSnapshot);
4289
4576
  if (offering.seatPriceCents <= target) {
@@ -4299,16 +4586,16 @@ async function lowerSeatPrice(db, scope2, now, limit = 100) {
4299
4586
  skip(`seat is ${String(seat.status)}`);
4300
4587
  continue;
4301
4588
  }
4302
- const ops = [membershipUpdate("networkMembership", id6, { offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(member.revision) + 1, updatedAt: now })];
4589
+ const ops = [membershipUpdate("networkMembership", id7, { offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(member.revision) + 1, updatedAt: now })];
4303
4590
  const guards = [
4304
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
4591
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
4305
4592
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative" } }
4306
4593
  ];
4307
4594
  if (seat) {
4308
4595
  ops.push(membershipUpdate("networkSeat", String(seat.id), { amountCents: target, offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(seat.revision) + 1, updatedAt: now }));
4309
4596
  guards.push({ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision, status: "payment_pending" } });
4310
4597
  }
4311
- await db.transact(ops, { mutationId: `seat-reprice:${id6}:${member.revision}`, guards });
4598
+ await db.transact(ops, { mutationId: `seat-reprice:${id7}:${member.revision}`, guards });
4312
4599
  receipt.repriced++;
4313
4600
  }
4314
4601
  return receipt;
@@ -4509,25 +4796,28 @@ var handleAdminMembershipPolicyVersions = async (req, url, env, ctx) => {
4509
4796
  };
4510
4797
 
4511
4798
  // src/membership-discount-reterm.ts
4512
- var import_stripe8 = require("@odla-ai/stripe");
4799
+ var import_stripe9 = require("@odla-ai/stripe");
4513
4800
 
4514
4801
  // src/subscription-provider-contract.ts
4515
- var import_stripe7 = require("@odla-ai/stripe");
4802
+ var import_stripe8 = require("@odla-ai/stripe");
4516
4803
  function subscriptionContractMismatch() {
4517
4804
  throw Object.assign(new Error("Stripe subscription does not match the checkout contract"), { code: "stripe_contract_mismatch" });
4518
4805
  }
4519
4806
  async function verifySubscriptionPrice(secretKey, input, resume = false) {
4520
4807
  if (!/^price_[A-Za-z0-9]+$/.test(input.priceId) || !Number.isSafeInteger(input.priceCents) || input.priceCents <= 0 || !/^[a-z]{3}$/.test(input.currency) || !["month", "year"].includes(input.interval)) subscriptionContractMismatch();
4521
- const result = await (0, import_stripe7.stripeRequest)({ secretKey }, "GET", `/v1/prices/${encodeURIComponent(input.priceId)}`);
4808
+ const result = await (0, import_stripe8.stripeRequest)({ secretKey }, "GET", `/v1/prices/${encodeURIComponent(input.priceId)}`);
4522
4809
  const price = result.body;
4523
4810
  if (!result.ok || !price || price.id !== input.priceId || !resume && price.active !== true || price.unit_amount !== input.priceCents || price.currency !== input.currency || price.type !== "recurring" || price.billing_scheme !== "per_unit" || price.custom_unit_amount || price.transform_quantity || price.recurring?.interval !== input.interval || price.recurring?.interval_count !== 1 || price.recurring?.usage_type !== "licensed" || price.livemode !== (input.environment === "prod")) subscriptionContractMismatch();
4524
4811
  return typeof price.product === "string" ? price.product : void 0;
4525
4812
  }
4526
4813
  function verifySubscriptionResult(sub, input, customerId, metadata) {
4527
4814
  const items = sub?.items;
4528
- if (!sub || !/^sub_[A-Za-z0-9]+$/.test(sub.id) || sub.customer !== customerId || input.existingSubscriptionId && sub.id !== input.existingSubscriptionId || !(input.billingTerms?.initialTermEndsAt ? ["incomplete", "active", "trialing"] : ["incomplete", "active"]).includes(sub.status) || sub.livemode !== (input.environment === "prod") || !Object.entries(metadata).every(([key3, value]) => sub.metadata?.[key3] === value) || !Array.isArray(items?.data) || items.has_more !== false || items.data.length !== 1 || items.data[0]?.price?.id !== input.priceId || items.data[0]?.quantity !== 1) subscriptionContractMismatch();
4815
+ const seat = input.namedSeat?.amountCents ? input.namedSeat : void 0;
4816
+ const primary = items?.data?.find((item) => item.price?.id === input.priceId);
4817
+ const seatItem = seat && items?.data?.find((item) => item.price?.id === seat.priceId);
4818
+ if (!sub || !/^sub_[A-Za-z0-9]+$/.test(sub.id) || sub.customer !== customerId || input.existingSubscriptionId && sub.id !== input.existingSubscriptionId || !(input.billingTerms?.initialTermEndsAt ? ["incomplete", "active", "trialing"] : ["incomplete", "active"]).includes(sub.status) || sub.livemode !== (input.environment === "prod") || !Object.entries(metadata).every(([key3, value]) => sub.metadata?.[key3] === value) || !Array.isArray(items?.data) || items.has_more !== false || items.data.length !== (seat ? 2 : 1) || primary?.quantity !== 1 || seat && (seatItem?.quantity !== 1 || seatItem.metadata?.seatId !== seat.seatId || seatItem.metadata?.membershipId !== seat.membershipId)) subscriptionContractMismatch();
4529
4819
  const invoice = sub.latest_invoice;
4530
- if (!invoice || typeof invoice !== "object" || invoice.customer !== customerId || invoice.currency !== input.currency || invoice.total !== subscriptionAmountDue(input) || invoice.billing_reason !== "subscription_create") subscriptionContractMismatch();
4820
+ if (!invoice || typeof invoice !== "object" || invoice.customer !== customerId || invoice.currency !== input.currency || invoice.total !== subscriptionAmountDue(input) + (seat?.amountCents ?? 0) || invoice.billing_reason !== "subscription_create") subscriptionContractMismatch();
4531
4821
  verifySubscriptionBillingResult(sub, input);
4532
4822
  }
4533
4823
 
@@ -4539,8 +4829,8 @@ var couponOf = (discount) => {
4539
4829
  const ref = discount?.source?.coupon ?? discount?.coupon;
4540
4830
  return typeof ref === "string" ? ref : typeof ref?.id === "string" ? ref.id : void 0;
4541
4831
  };
4542
- async function retermPolicy(db, scope2, id6) {
4543
- const row2 = await membershipLoad(db, "discountPolicyVersion", id6);
4832
+ async function retermPolicy(db, scope2, id7) {
4833
+ const row2 = await membershipLoad(db, "discountPolicyVersion", id7);
4544
4834
  if (!row2) membershipFail("membership_reterm_policy_unknown");
4545
4835
  let policy2;
4546
4836
  try {
@@ -4548,12 +4838,12 @@ async function retermPolicy(db, scope2, id6) {
4548
4838
  } catch {
4549
4839
  return membershipFail("membership_reterm_policy_ineligible");
4550
4840
  }
4551
- if (policy2.id !== id6 || policy2.chapterId !== scope2.chapterId || policy2.status !== "published" || policy2.benefitDuration.kind !== "while_active" || policy2.percentOffBps <= 0 || policy2.discountEligibleSkuIds.length === 0) membershipFail("membership_reterm_policy_ineligible");
4841
+ if (policy2.id !== id7 || policy2.chapterId !== scope2.chapterId || policy2.status !== "published" || policy2.benefitDuration.kind !== "while_active" || policy2.percentOffBps <= 0 || policy2.discountEligibleSkuIds.length === 0) membershipFail("membership_reterm_policy_ineligible");
4552
4842
  return policy2;
4553
4843
  }
4554
4844
  async function attachProviderDiscount(sk, scope2, member, policy2, couponId, productId) {
4555
4845
  const subId = String(member.providerSubscriptionId), path = `/v1/subscriptions/${encodeURIComponent(subId)}`;
4556
- const read2 = await (0, import_stripe8.stripeRequest)({ secretKey: sk }, "GET", path, { "expand[0]": "discounts" });
4846
+ const read2 = await (0, import_stripe9.stripeRequest)({ secretKey: sk }, "GET", path, { "expand[0]": "discounts" });
4557
4847
  const sub = read2.body, items = sub?.items?.data;
4558
4848
  const metadata = { ...stripePartitionMetadata(scope2), applicationId: String(member.applicationId) };
4559
4849
  if (!read2.ok || sub.id !== subId || sub.customer !== member.providerCustomerId || sub.livemode !== (scope2.environment === "prod") || !PROVIDER_LIVE.has(sub.status) || !Object.entries(metadata).every(([key3, value]) => sub.metadata?.[key3] === value) || sub.items?.has_more !== false || !Array.isArray(items) || items.length < 1 || items.length > 2 || items.filter((item) => item.price?.id === member.providerPriceId && item.quantity === 1 && item.price?.product === productId).length !== 1) {
@@ -4564,7 +4854,7 @@ async function attachProviderDiscount(sk, scope2, member, policy2, couponId, pro
4564
4854
  const present = discounts.map(couponOf);
4565
4855
  if (present.length === 1 && present[0] === couponId) return false;
4566
4856
  if (present.length > 0) throw Object.assign(new Error("provider subscription carries an unrelated discount"), { code: "provider_mismatch" });
4567
- const written = await (0, import_stripe8.stripeRequest)({ secretKey: sk }, "POST", path, {
4857
+ const written = await (0, import_stripe9.stripeRequest)({ secretKey: sk }, "POST", path, {
4568
4858
  "discounts[0][coupon]": couponId,
4569
4859
  "expand[0]": "discounts",
4570
4860
  "metadata[discountPolicyVersionId]": policy2.id
@@ -4580,7 +4870,7 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4580
4870
  const limit = input.limit ?? 100;
4581
4871
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 200) throw new TypeError("invalid reterm limit");
4582
4872
  if (typeof input.policyVersionId !== "string" || !SAFE.test(input.policyVersionId)) throw new TypeError("invalid policy version id");
4583
- if (input.membershipIds !== void 0 && (!Array.isArray(input.membershipIds) || input.membershipIds.length === 0 || input.membershipIds.length > limit || input.membershipIds.some((id6) => typeof id6 !== "string" || !SAFE.test(id6)))) throw new TypeError("invalid membership ids");
4873
+ if (input.membershipIds !== void 0 && (!Array.isArray(input.membershipIds) || input.membershipIds.length === 0 || input.membershipIds.length > limit || input.membershipIds.some((id7) => typeof id7 !== "string" || !SAFE.test(id7)))) throw new TypeError("invalid membership ids");
4584
4874
  const state = await loadMembershipAuthorityState(db, scope2);
4585
4875
  const policy2 = await retermPolicy(db, scope2, input.policyVersionId);
4586
4876
  const receipt = { policyVersionId: policy2.id, retermed: [], unchanged: [], skipped: [], more: false };
@@ -4590,10 +4880,10 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4590
4880
  let rows5;
4591
4881
  if (input.membershipIds) {
4592
4882
  rows5 = [];
4593
- for (const id6 of input.membershipIds) {
4594
- const member = await membershipLoad(db, "networkMembership", id6);
4595
- if (!member) skip(id6, "not_found");
4596
- else if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) skip(id6, "scope_mismatch");
4883
+ for (const id7 of input.membershipIds) {
4884
+ const member = await membershipLoad(db, "networkMembership", id7);
4885
+ if (!member) skip(id7, "not_found");
4886
+ else if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) skip(id7, "scope_mismatch");
4597
4887
  else rows5.push(member);
4598
4888
  }
4599
4889
  } else {
@@ -4603,32 +4893,32 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4603
4893
  rows5 = found.slice(0, limit);
4604
4894
  }
4605
4895
  for (const member of rows5) {
4606
- const id6 = String(member.id);
4896
+ const id7 = String(member.id);
4607
4897
  if (member.discountPolicyVersionId === policy2.id) {
4608
- receipt.unchanged.push(id6);
4898
+ receipt.unchanged.push(id7);
4609
4899
  continue;
4610
4900
  }
4611
4901
  if (!RETERMABLE.has(String(member.status))) {
4612
- skip(id6, "status");
4902
+ skip(id7, "status");
4613
4903
  continue;
4614
4904
  }
4615
4905
  if (member.founding !== true) {
4616
- skip(id6, "not_founding");
4906
+ skip(id7, "not_founding");
4617
4907
  continue;
4618
4908
  }
4619
4909
  if (member.cohort !== policy2.cohortLabel) {
4620
- skip(id6, "cohort_mismatch");
4910
+ skip(id7, "cohort_mismatch");
4621
4911
  continue;
4622
4912
  }
4623
4913
  const skuId = String(member.baseSkuId);
4624
4914
  if (!policy2.discountEligibleSkuIds.includes(skuId)) {
4625
- skip(id6, "sku_not_discounted");
4915
+ skip(id7, "sku_not_discounted");
4626
4916
  continue;
4627
4917
  }
4628
4918
  const base = Number(member.baseAmountCents), current = Number(member.renewalAmountCents);
4629
4919
  const renewal = Math.round(base * (1e4 - policy2.percentOffBps) / 1e4);
4630
4920
  if (!Number.isSafeInteger(base) || base <= 0 || !Number.isSafeInteger(current) || renewal > current) {
4631
- skip(id6, "would_not_widen");
4921
+ skip(id7, "would_not_widen");
4632
4922
  continue;
4633
4923
  }
4634
4924
  const couponId = await membershipDiscountCouponId({ ...scope2, skuId }, policy2.id);
@@ -4656,22 +4946,22 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4656
4946
  } catch (error) {
4657
4947
  const code = error.code;
4658
4948
  if (code === "provider_mismatch" || code === "stripe_contract_mismatch") {
4659
- skip(id6, "provider_mismatch");
4949
+ skip(id7, "provider_mismatch");
4660
4950
  continue;
4661
4951
  }
4662
4952
  throw error;
4663
4953
  }
4664
4954
  try {
4665
- await db.transact([membershipUpdate("networkMembership", id6, {
4955
+ await db.transact([membershipUpdate("networkMembership", id7, {
4666
4956
  discountPolicyVersionId: policy2.id,
4667
4957
  discountSnapshot: policy2,
4668
4958
  renewalAmountCents: renewal,
4669
4959
  revision: Number(member.revision) + 1,
4670
4960
  updatedAt: now
4671
4961
  })], {
4672
- mutationId: `membership-reterm:${id6}:${policy2.id}:${member.revision}`,
4962
+ mutationId: `membership-reterm:${id7}:${policy2.id}:${member.revision}`,
4673
4963
  guards: [
4674
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision, status: member.status } },
4964
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision, status: member.status } },
4675
4965
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative" } },
4676
4966
  { ns: "discountPolicyVersion", id: policy2.id, exists: true }
4677
4967
  ]
@@ -4681,7 +4971,7 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4681
4971
  return membershipFail("membership_reterm_retry_required");
4682
4972
  }
4683
4973
  receipt.retermed.push({
4684
- membershipId: id6,
4974
+ membershipId: id7,
4685
4975
  ...typeof member.discountPolicyVersionId === "string" ? { previousPolicyVersionId: member.discountPolicyVersionId } : {},
4686
4976
  previousRenewalAmountCents: current,
4687
4977
  renewalAmountCents: renewal,
@@ -4717,7 +5007,7 @@ var handleAdminMembershipReterm = async (req, url, env, ctx) => {
4717
5007
  return json({ error: BAD }, 400);
4718
5008
  }
4719
5009
  const appId = body.appId ?? body.chapterId, runtime = body.runtime ?? env.ODLA_RUNTIME;
4720
- if (typeof body.chapterId !== "string" || !SCOPE3.test(body.chapterId) || typeof appId !== "string" || !SCOPE3.test(appId) || typeof runtime !== "string" || !/^[a-z0-9]{2,12}$/.test(runtime) || typeof body.policyVersionId !== "string" || !ID.test(body.policyVersionId) || body.membershipIds !== void 0 && (!Array.isArray(body.membershipIds) || body.membershipIds.length === 0 || body.membershipIds.length > 200 || body.membershipIds.some((id6) => typeof id6 !== "string" || !ID.test(id6))) || body.limit !== void 0 && (!Number.isSafeInteger(body.limit) || Number(body.limit) < 1 || Number(body.limit) > 200)) {
5010
+ if (typeof body.chapterId !== "string" || !SCOPE3.test(body.chapterId) || typeof appId !== "string" || !SCOPE3.test(appId) || typeof runtime !== "string" || !/^[a-z0-9]{2,12}$/.test(runtime) || typeof body.policyVersionId !== "string" || !ID.test(body.policyVersionId) || body.membershipIds !== void 0 && (!Array.isArray(body.membershipIds) || body.membershipIds.length === 0 || body.membershipIds.length > 200 || body.membershipIds.some((id7) => typeof id7 !== "string" || !ID.test(id7))) || body.limit !== void 0 && (!Number.isSafeInteger(body.limit) || Number(body.limit) < 1 || Number(body.limit) > 200)) {
4721
5011
  return json({ error: BAD }, 400);
4722
5012
  }
4723
5013
  const secret = await getVaultSecret(db, operator.stripeSecretName ?? "stripe_secret_key");
@@ -4992,12 +5282,12 @@ var handleMembershipRestart = async (req, url, env, ctx) => {
4992
5282
  if (!app || app.clerkUserId !== user.userId || app.namedSeatId || app.giftClaimId) return json({ error: "membership is not eligible to restart" }, 409);
4993
5283
  if (app.membershipRestartId && app.status === "submitted") return json({ applicationId: app.id });
4994
5284
  try {
4995
- const id6 = String(app.id), intent = await membershipLoad(db, "subscriptionCheckoutIntents", id6);
5285
+ const id7 = String(app.id), intent = await membershipLoad(db, "subscriptionCheckoutIntents", id7);
4996
5286
  if (!intent || !app.stripeSubscriptionId || intent.providerSubscriptionId !== app.stripeSubscriptionId) return json({ error: "membership history needs reconciliation" }, 409);
4997
5287
  const target = await membershipTargetForIntent(intent, app, env, ctx);
4998
5288
  const restart = await ctx.membershipAuthority.restart({ ...target, previousSubscriptionId: String(app.stripeSubscriptionId) }, env);
4999
- if (restart.status !== "ready" || restart.applicationId !== id6 || restart.membershipId !== await networkMembershipId(target) || !/^restart_[a-f0-9]{64}$/.test(restart.restartId) || !Number.isSafeInteger(restart.memberNumber) || restart.memberNumber < 1) throw new Error("invalid restart receipt");
5000
- const now = Date.now(), quote = await membershipLoad(db, "membershipQuoteProjections", id6);
5289
+ 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");
5290
+ const now = Date.now(), quote = await membershipLoad(db, "membershipQuoteProjections", id7);
5001
5291
  const attrs = {
5002
5292
  status: "submitted",
5003
5293
  membershipRestartId: restart.restartId,
@@ -5017,8 +5307,8 @@ var handleMembershipRestart = async (req, url, env, ctx) => {
5017
5307
  quoteSnapshot: quote ?? {},
5018
5308
  archivedAt: now
5019
5309
  } },
5020
- { t: "delete", ns: "subscriptionCheckoutIntents", id: id6 },
5021
- { t: "retract", ns: "applications", id: id6, attrs: [
5310
+ { t: "delete", ns: "subscriptionCheckoutIntents", id: id7 },
5311
+ { t: "retract", ns: "applications", id: id7, attrs: [
5022
5312
  "stripeSubscriptionId",
5023
5313
  "canceled",
5024
5314
  "renewalAt",
@@ -5029,28 +5319,28 @@ var handleMembershipRestart = async (req, url, env, ctx) => {
5029
5319
  "approvalEffectsOrigin",
5030
5320
  "approvalEffectsLastAttemptAt"
5031
5321
  ] },
5032
- { t: "update", ns: "applications", id: id6, attrs },
5033
- ...quote ? [{ t: "update", ns: "membershipQuoteProjections", id: id6, attrs: { status: "restart", updatedAt: now } }] : []
5322
+ { t: "update", ns: "applications", id: id7, attrs },
5323
+ ...quote ? [{ t: "update", ns: "membershipQuoteProjections", id: id7, attrs: { status: "restart", updatedAt: now } }] : []
5034
5324
  ];
5035
5325
  try {
5036
5326
  await db.transact(ops, { mutationId: `membership-restart-application:${restart.restartId}`, guards: [
5037
- { ns: "applications", id: id6, exists: true, equals: {
5327
+ { ns: "applications", id: id7, exists: true, equals: {
5038
5328
  clerkUserId: user.userId,
5039
5329
  groupId: ctx.chapter.id,
5040
5330
  status: app.status,
5041
5331
  stripeSubscriptionId: app.stripeSubscriptionId
5042
5332
  } },
5043
- { ns: "subscriptionCheckoutIntents", id: id6, exists: true, equals: { providerSubscriptionId: app.stripeSubscriptionId } },
5333
+ { ns: "subscriptionCheckoutIntents", id: id7, exists: true, equals: { providerSubscriptionId: app.stripeSubscriptionId } },
5044
5334
  { ns: "membershipCheckoutHistory", id: restart.restartId, exists: false },
5045
- { ns: "membershipQuoteProjections", id: id6, exists: Boolean(quote), ...quote ? { equals: { revision: quote.revision, status: quote.status } } : {} }
5335
+ { ns: "membershipQuoteProjections", id: id7, exists: Boolean(quote), ...quote ? { equals: { revision: quote.revision, status: quote.status } } : {} }
5046
5336
  ] });
5047
5337
  } catch (error) {
5048
5338
  if (error.code !== "transact_guard_failed") throw error;
5049
5339
  }
5050
- const saved = await membershipLoad(db, "applications", id6);
5340
+ const saved = await membershipLoad(db, "applications", id7);
5051
5341
  if (!saved || saved.clerkUserId !== user.userId || saved.membershipRestartId !== restart.restartId || saved.groupId !== ctx.chapter.id || saved.stripeRuntime !== target.runtime) throw new Error("restart persistence pending");
5052
5342
  await syncApplicationToCrm(crmDeps(db, ctx), { app: saved, stage: String(saved.status) }).catch(() => void 0);
5053
- return json({ applicationId: id6 });
5343
+ return json({ applicationId: id7 });
5054
5344
  } catch {
5055
5345
  return json({ error: "We could not prepare your new subscription. Your account is saved. Please retry." }, 503);
5056
5346
  }
@@ -5181,8 +5471,8 @@ async function submitApplication(db, chapter, fields, opts) {
5181
5471
  if (app.requireDisclaimerAck && !acked) {
5182
5472
  return { ok: false, error: "disclaimerAck is required" };
5183
5473
  }
5184
- const id6 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
5185
- const row2 = { id: id6, status: chapter.pipeline.initial, createdAt: opts.now };
5474
+ const id7 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
5475
+ const row2 = { id: id7, status: chapter.pipeline.initial, createdAt: opts.now };
5186
5476
  for (const f of [...app.required, ...app.optional]) {
5187
5477
  if (fieldStates[f]?.visible === false) continue;
5188
5478
  if (typeof fields[f] === "string") row2[f] = fields[f].trim();
@@ -5216,12 +5506,12 @@ async function submitApplication(db, chapter, fields, opts) {
5216
5506
  }
5217
5507
  if (acked) row2.disclaimerAckAt = opts.now;
5218
5508
  const ops = [
5219
- { t: "update", ns: "applications", id: id6, attrs: row2 }
5509
+ { t: "update", ns: "applications", id: id7, attrs: row2 }
5220
5510
  ];
5221
5511
  if (opts.admissionGrant?.decision === "waive_interview") {
5222
5512
  ops.push({ t: "update", ns: "admissionGrants", id: opts.admissionGrant.id, attrs: {
5223
5513
  consumedAt: opts.now,
5224
- consumedApplicationId: id6
5514
+ consumedApplicationId: id7
5225
5515
  } });
5226
5516
  }
5227
5517
  try {
@@ -5229,7 +5519,7 @@ async function submitApplication(db, chapter, fields, opts) {
5229
5519
  ops,
5230
5520
  opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
5231
5521
  );
5232
- return { ok: true, id: id6, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
5522
+ return { ok: true, id: id7, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
5233
5523
  } catch (error) {
5234
5524
  if (error.code === "unique_violation") return {
5235
5525
  ok: false,
@@ -5338,7 +5628,7 @@ async function authorizeCrmDiscussionReference(req, env, fetcher = fetch) {
5338
5628
  }
5339
5629
 
5340
5630
  // src/join-founding-preview.ts
5341
- async function foundingPreviews(tiers, env, ctx) {
5631
+ async function foundingPreviews(tiers, env, ctx, seats = /* @__PURE__ */ new Map()) {
5342
5632
  const result = /* @__PURE__ */ new Map();
5343
5633
  const preview = ctx.membershipAuthority?.previewOffer;
5344
5634
  if (!preview) return result;
@@ -5346,6 +5636,10 @@ async function foundingPreviews(tiers, env, ctx) {
5346
5636
  await Promise.all(tiers.filter((tier) => !tierIsFree(tier) && tier.stripePriceId).map(async (tier) => {
5347
5637
  try {
5348
5638
  const offer = await preview({ ...scope2, skuId: tier.id }, env);
5639
+ if (offer.skuId !== tier.id || offer.basePriceCents !== tier.priceCents) return;
5640
+ if (ctx.membershipAuthority?.namedSeats && Number.isSafeInteger(offer.seatPriceCents) && Number(offer.seatPriceCents) >= 0) {
5641
+ seats.set(tier.id, { amountCents: Number(offer.seatPriceCents) });
5642
+ }
5349
5643
  if (!offer.founding || !offer.cohortLabel || offer.basePriceCents !== tier.priceCents || offer.dueTodayCents >= offer.basePriceCents) return;
5350
5644
  result.set(tier.id, {
5351
5645
  cohortLabel: offer.cohortLabel,
@@ -5435,14 +5729,14 @@ function validateAdmissionGrantDocument(input) {
5435
5729
  const seenEmails = /* @__PURE__ */ new Set();
5436
5730
  for (const [index2, raw2] of rawGrants.slice(0, MAX_GRANTS).entries()) {
5437
5731
  const grant = raw2 && typeof raw2 === "object" ? raw2 : {};
5438
- const id6 = typeof grant.id === "string" ? grant.id : "";
5732
+ const id7 = typeof grant.id === "string" ? grant.id : "";
5439
5733
  const email = typeof grant.email === "string" ? grant.email.trim() : "";
5440
5734
  const normalized = normalizeAdmissionEmail(email);
5441
5735
  const decision = grant.decision;
5442
5736
  const note = typeof grant.note === "string" ? grant.note.trim() : void 0;
5443
5737
  const expiresAt = grant.expiresAt === void 0 ? void 0 : Number(grant.expiresAt);
5444
- if (!ID2.test(id6)) errors.push(`document.grants[${index2}].id must be an identifier`);
5445
- if (seenIds.has(id6)) errors.push(`document.grants repeats id "${id6}"`);
5738
+ if (!ID2.test(id7)) errors.push(`document.grants[${index2}].id must be an identifier`);
5739
+ if (seenIds.has(id7)) errors.push(`document.grants repeats id "${id7}"`);
5446
5740
  if (!isAdmissionEmail(normalized)) errors.push(`document.grants[${index2}].email must be valid`);
5447
5741
  if (seenEmails.has(normalized)) errors.push(`document.grants repeats email "${normalized}"`);
5448
5742
  if (decision !== "waive_interview" && decision !== "require_interview") {
@@ -5452,10 +5746,10 @@ function validateAdmissionGrantDocument(input) {
5452
5746
  if (expiresAt !== void 0 && (!Number.isFinite(expiresAt) || expiresAt <= 0)) {
5453
5747
  errors.push(`document.grants[${index2}].expiresAt must be epoch milliseconds`);
5454
5748
  }
5455
- seenIds.add(id6);
5749
+ seenIds.add(id7);
5456
5750
  seenEmails.add(normalized);
5457
5751
  grants.push({
5458
- id: id6,
5752
+ id: id7,
5459
5753
  email,
5460
5754
  decision,
5461
5755
  ...note ? { note } : {},
@@ -5527,8 +5821,8 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
5527
5821
  const incomingIds = new Set(document.grants.map((grant) => `admission:parent:${document.source.appId}:${grant.id}`));
5528
5822
  const ops = [];
5529
5823
  for (const row2 of current) {
5530
- const id6 = String(row2.id ?? "");
5531
- if (id6 && !incomingIds.has(id6)) ops.push({ t: "update", ns: "admissionGrants", id: id6, attrs: {
5824
+ const id7 = String(row2.id ?? "");
5825
+ if (id7 && !incomingIds.has(id7)) ops.push({ t: "update", ns: "admissionGrants", id: id7, attrs: {
5532
5826
  active: false,
5533
5827
  revokedAt: receivedAt,
5534
5828
  revokedBy: document.source.actor,
@@ -5537,10 +5831,10 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
5537
5831
  } });
5538
5832
  }
5539
5833
  for (const grant of document.grants) {
5540
- const id6 = `admission:parent:${document.source.appId}:${grant.id}`;
5541
- const existing = current.find((row2) => row2.id === id6);
5542
- ops.push({ t: "update", ns: "admissionGrants", id: id6, attrs: {
5543
- id: id6,
5834
+ const id7 = `admission:parent:${document.source.appId}:${grant.id}`;
5835
+ const existing = current.find((row2) => row2.id === id7);
5836
+ ops.push({ t: "update", ns: "admissionGrants", id: id7, attrs: {
5837
+ id: id7,
5544
5838
  sourceGrantId: grant.id,
5545
5839
  sourceScope: "parent",
5546
5840
  sourceId: document.source.appId,
@@ -5561,7 +5855,7 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
5561
5855
  ...!grant.note && existing?.note !== void 0 ? ["note"] : [],
5562
5856
  ...grant.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
5563
5857
  ];
5564
- if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id6, attrs: retract });
5858
+ if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id7, attrs: retract });
5565
5859
  }
5566
5860
  if (previous?.active) ops.push({ t: "update", ns: "admissionGrantRevisions", id: previous.id, attrs: { active: false } });
5567
5861
  const revisionId = `admission-revision:${document.source.appId}:${document.target.environment}:${document.revision}`;
@@ -5592,9 +5886,9 @@ var APPLICATION_LABELS = {
5592
5886
  phone: "Phone",
5593
5887
  state: "State"
5594
5888
  };
5595
- function humanize(id6) {
5596
- const text8 = id6.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
5597
- return text8 ? `${text8[0].toUpperCase()}${text8.slice(1)}` : id6;
5889
+ function humanize(id7) {
5890
+ const text8 = id7.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
5891
+ return text8 ? `${text8[0].toUpperCase()}${text8.slice(1)}` : id7;
5598
5892
  }
5599
5893
  function applicationDefs(chapter) {
5600
5894
  try {
@@ -5603,23 +5897,23 @@ function applicationDefs(chapter) {
5603
5897
  return {};
5604
5898
  }
5605
5899
  }
5606
- function applicationType(id6, def) {
5900
+ function applicationType(id7, def) {
5607
5901
  if (def && ["string", "email", "url", "enum"].includes(def.type)) return def.type;
5608
- return id6 === "email" ? "email" : id6 === "linkedin" ? "url" : "string";
5902
+ return id7 === "email" ? "email" : id7 === "linkedin" ? "url" : "string";
5609
5903
  }
5610
5904
  function applicationFlowFields(chapter, application = chapter.application) {
5611
5905
  const defs = applicationDefs(chapter);
5612
- return [...application.required, ...application.optional].map((id6, order2) => {
5613
- const def = defs[id6];
5614
- const conditions = application.conditions[id6];
5615
- const type = applicationType(id6, def);
5906
+ return [...application.required, ...application.optional].map((id7, order2) => {
5907
+ const def = defs[id7];
5908
+ const conditions = application.conditions[id7];
5909
+ const type = applicationType(id7, def);
5616
5910
  return {
5617
- id: id6,
5618
- label: def?.label ?? APPLICATION_LABELS[id6] ?? humanize(id6),
5911
+ id: id7,
5912
+ label: def?.label ?? APPLICATION_LABELS[id7] ?? humanize(id7),
5619
5913
  type,
5620
- required: application.required.includes(id6),
5914
+ required: application.required.includes(id7),
5621
5915
  order: order2,
5622
- maxLength: application.maxLen[id6] ?? application.defaultMaxLen,
5916
+ maxLength: application.maxLen[id7] ?? application.defaultMaxLen,
5623
5917
  ...type === "enum" && def?.options ? { options: [...def.options] } : {},
5624
5918
  ...conditions ? { conditions: { ...conditions } } : {},
5625
5919
  ...type === "email" ? { emailIdentity: "literal" } : {}
@@ -5629,17 +5923,17 @@ function applicationFlowFields(chapter, application = chapter.application) {
5629
5923
  function resolveFormationFlowFields(crm, formation) {
5630
5924
  if (!formation.enabled) return [];
5631
5925
  const def = crm.type(formation.type);
5632
- return [...formation.required, ...formation.optional].map((id6, order2) => {
5633
- const field = def.fields[id6];
5926
+ return [...formation.required, ...formation.optional].map((id7, order2) => {
5927
+ const field = def.fields[id7];
5634
5928
  return {
5635
- id: id6,
5636
- label: field.label ?? humanize(id6),
5929
+ id: id7,
5930
+ label: field.label ?? humanize(id7),
5637
5931
  type: field.type,
5638
- required: formation.required.includes(id6),
5932
+ required: formation.required.includes(id7),
5639
5933
  order: order2,
5640
- maxLength: formation.maxLen[id6] ?? formation.defaultMaxLen,
5934
+ maxLength: formation.maxLen[id7] ?? formation.defaultMaxLen,
5641
5935
  ...field.options ? { options: [...field.options] } : {},
5642
- ...id6 === def.emailField || field.type === "email" ? { emailIdentity: "literal" } : {}
5936
+ ...id7 === def.emailField || field.type === "email" ? { emailIdentity: "literal" } : {}
5643
5937
  };
5644
5938
  });
5645
5939
  }
@@ -5689,10 +5983,10 @@ function validateGiftOfferCatalog(input, tierIds) {
5689
5983
  }
5690
5984
  const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
5691
5985
  for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
5692
- const id6 = text3(item.id, `${path}.id`, errors, 80);
5693
- if (id6 && !ID3.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
5694
- if (ids2.has(id6)) errors.push(`${path}.id duplicates "${id6}"`);
5695
- ids2.add(id6);
5986
+ const id7 = text3(item.id, `${path}.id`, errors, 80);
5987
+ if (id7 && !ID3.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
5988
+ if (ids2.has(id7)) errors.push(`${path}.id duplicates "${id7}"`);
5989
+ ids2.add(id7);
5696
5990
  const targetRaw = record(item.target);
5697
5991
  let target = { type: "entitlement", entitlementKey: "invalid" };
5698
5992
  if (targetRaw?.type === "tier") {
@@ -5718,7 +6012,7 @@ function validateGiftOfferCatalog(input, tierIds) {
5718
6012
  if (activeUntil !== void 0 && (!Number.isSafeInteger(activeUntil) || activeUntil < 1)) errors.push(`${path}.activeUntil must be epoch milliseconds`);
5719
6013
  if (activeFrom !== void 0 && activeUntil !== void 0 && activeUntil <= activeFrom) errors.push(`${path}.activeUntil must follow activeFrom`);
5720
6014
  offers.push({
5721
- id: id6,
6015
+ id: id7,
5722
6016
  target,
5723
6017
  billingMode: item.billingMode,
5724
6018
  stripePriceId,
@@ -5818,9 +6112,9 @@ async function materializeSignupControl(db, envelope, previous, receivedAt) {
5818
6112
  const ops = [];
5819
6113
  if (previous?.active) ops.push({ t: "update", ns: "signupControls", id: previous.id, attrs: { active: false } });
5820
6114
  for (const row2 of currentRows) {
5821
- const id7 = String(row2.id ?? "");
5822
- if (id7 && !incoming.has(id7)) {
5823
- ops.push({ t: "update", ns: "tiers", id: byPublicId("tiers", id7), attrs: { active: false } });
6115
+ const id8 = String(row2.id ?? "");
6116
+ if (id8 && !incoming.has(id8)) {
6117
+ ops.push({ t: "update", ns: "tiers", id: byPublicId("tiers", id8), attrs: { active: false } });
5824
6118
  }
5825
6119
  }
5826
6120
  for (const tier of document.tiers) {
@@ -5855,9 +6149,9 @@ async function materializeSignupControl(db, envelope, previous, receivedAt) {
5855
6149
  if (!("stripePriceId" in attrs)) {
5856
6150
  ops.push({ t: "retract", ns: "groups", id: groupRef, attrs: ["stripePriceId"] });
5857
6151
  }
5858
- const id6 = `signup-control:${document.target.environment}:${document.revision}`;
5859
- ops.push({ t: "update", ns: "signupControls", id: id6, attrs: {
5860
- id: id6,
6152
+ const id7 = `signup-control:${document.target.environment}:${document.revision}`;
6153
+ ops.push({ t: "update", ns: "signupControls", id: id7, attrs: {
6154
+ id: id7,
5861
6155
  sourceId: document.source.appId,
5862
6156
  targetId: document.target.chapterId,
5863
6157
  environment: document.target.environment,
@@ -5896,6 +6190,8 @@ function foundingOf(offer) {
5896
6190
  async function createPaymentQuote(input) {
5897
6191
  const { group, tier, policy: policy2 } = input;
5898
6192
  const terms2 = input.billingTerms ? subscriptionBillingTerms(input.billingTerms) : void 0;
6193
+ if (input.seatOffer && (!terms2 || !Number.isSafeInteger(input.seatOffer.amountCents) || input.seatOffer.amountCents < 0) || input.namedSeat && !input.seatOffer) throw new Error("invalid checkout seat offer");
6194
+ const seatCents = input.namedSeat ? input.seatOffer.amountCents : 0;
5899
6195
  const compareAt = input.presentation?.compareAtPriceCents;
5900
6196
  const presentation = Number.isSafeInteger(compareAt) && Number(compareAt) > tier.priceCents ? { compareAtPriceCents: Number(compareAt) } : void 0;
5901
6197
  const dueToday = terms2 ? subscriptionAmountDue({ priceCents: tier.priceCents, billingTerms: terms2 }) : void 0;
@@ -5904,6 +6200,7 @@ async function createPaymentQuote(input) {
5904
6200
  discountCents: tier.priceCents - dueToday,
5905
6201
  dueTodayCents: dueToday
5906
6202
  };
6203
+ if (!Number.isSafeInteger(lines.dueTodayCents + seatCents) || !Number.isSafeInteger(tier.priceCents + seatCents)) throw new Error("invalid checkout total");
5907
6204
  const quote = {
5908
6205
  applicationId: input.applicationId,
5909
6206
  chapterId: input.chapterId,
@@ -5911,11 +6208,17 @@ async function createPaymentQuote(input) {
5911
6208
  tierName: tier.name,
5912
6209
  currency: tier.currency ?? String(group.currency ?? "usd"),
5913
6210
  interval: tier.interval ?? (group.interval === "month" ? "month" : "year"),
5914
- lineItems: presentation ? {
5915
- standardCents: presentation.compareAtPriceCents,
5916
- discountCents: presentation.compareAtPriceCents - lines.dueTodayCents,
5917
- dueTodayCents: lines.dueTodayCents
5918
- } : lines,
6211
+ lineItems: {
6212
+ ...presentation ? {
6213
+ standardCents: presentation.compareAtPriceCents,
6214
+ discountCents: presentation.compareAtPriceCents - lines.dueTodayCents,
6215
+ dueTodayCents: lines.dueTodayCents
6216
+ } : lines,
6217
+ dueTodayCents: lines.dueTodayCents + seatCents,
6218
+ ...input.namedSeat ? { namedSeatCents: seatCents } : {}
6219
+ },
6220
+ ...input.seatOffer ? { seatOffer: input.seatOffer } : {},
6221
+ ...input.namedSeat ? { namedSeat: input.namedSeat } : {},
5919
6222
  ...presentation ? { presentation } : {},
5920
6223
  refundPolicyText: policy2?.refundPolicyText ?? String(group.refundPolicyText ?? ""),
5921
6224
  merchantDisclosureText: String(group.merchantDisclosureText ?? ""),
@@ -5927,7 +6230,7 @@ async function createPaymentQuote(input) {
5927
6230
  discountDuration: terms2.discount.duration === "once" ? "first_term" : "while_active"
5928
6231
  } : {},
5929
6232
  ...terms2.initialTermEndsAt !== void 0 ? { initialTermEndsAt: terms2.initialTermEndsAt } : {},
5930
- renewalCents: terms2.discount?.duration === "forever" ? dueToday : tier.priceCents,
6233
+ renewalCents: (terms2.discount?.duration === "forever" ? dueToday : tier.priceCents) + seatCents,
5931
6234
  autoRenew: true
5932
6235
  } } : {}
5933
6236
  };
@@ -5947,22 +6250,23 @@ async function createPaymentQuote(input) {
5947
6250
  // src/membership-quote-projection.ts
5948
6251
  var MEMBERSHIP_QUOTE_NAMESPACE = "membershipQuoteProjections";
5949
6252
  var keys2 = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId"];
5950
- var fail5 = (code) => {
6253
+ var fail6 = (code) => {
5951
6254
  throw Object.assign(new Error(code), { code });
5952
6255
  };
5953
- var load3 = async (db, ns, id6) => (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
6256
+ var load3 = async (db, ns, id7) => (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
5954
6257
  async function restore2(row2, target, now, allowExpired = false) {
5955
- if (!row2 || keys2.some((key3) => row2[key3] !== target[key3]) || !Number.isSafeInteger(row2.revision) || Number(row2.revision) < 1 || typeof row2.requestId !== "string") return fail5("membership_quote_unavailable");
5956
- if (row2.status !== "ready") return fail5("membership_quote_pending");
6258
+ 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");
6259
+ if (row2.status !== "ready") return fail6("membership_quote_pending");
5957
6260
  const offer = await verifyIssuedMembershipOffer(row2.offer, { ...target, offerId: await membershipOfferIdForRequest({
5958
6261
  ...target,
5959
6262
  requestId: row2.requestId
5960
6263
  }), now, lifetimeMs: 6e4 });
5961
- if (offer.issuedAt > now + 3e4 || !allowExpired && offer.expiresAt <= now) return fail5("membership_quote_expired");
6264
+ if (offer.issuedAt > now + 3e4 || !allowExpired && offer.expiresAt <= now) return fail6("membership_quote_expired");
5962
6265
  const tier = row2.tier, quote = row2.quote;
5963
6266
  const offering = offer.contract.offering;
6267
+ if (quote?.seatOffer && quote.seatOffer.amountCents !== offering.seatPriceCents) return fail6("membership_quote_conflict");
5964
6268
  if (!tier || tier.id !== target.skuId || tier.groupId !== target.chapterId || !tier.stripePriceId || tier.priceCents !== offering.basePriceCents || tier.currency !== offering.currency || tier.interval !== offering.interval || !quote) {
5965
- return fail5("membership_catalog_mismatch");
6269
+ return fail6("membership_catalog_mismatch");
5966
6270
  }
5967
6271
  const expected = await createPaymentQuote({
5968
6272
  ...target,
@@ -5971,19 +6275,21 @@ async function restore2(row2, target, now, allowExpired = false) {
5971
6275
  policy: { refundPolicyText: quote.refundPolicyText, trustCopy: quote.trustCopy },
5972
6276
  billingTerms: offer.contract.billingTerms,
5973
6277
  presentation: quote.presentation,
5974
- founding: foundingOf(offer)
6278
+ founding: foundingOf(offer),
6279
+ seatOffer: quote.seatOffer,
6280
+ namedSeat: quote.namedSeat
5975
6281
  });
5976
- if (quote.digest !== expected.digest) return fail5("membership_quote_conflict");
6282
+ if (quote.digest !== expected.digest) return fail6("membership_quote_conflict");
5977
6283
  return { tier, quote: expected, billingTerms: offer.contract.billingTerms, membershipOffer: offer, projectionRevision: Number(row2.revision) };
5978
6284
  }
5979
6285
  async function readMembershipQuote(db, target, now) {
5980
6286
  return restore2(await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, target.applicationId), target, now);
5981
6287
  }
5982
6288
  async function prepareMembershipQuote(db, input, issue) {
5983
- const { applicationId: id6, now, app } = input;
5984
- if (app.id !== id6 || app.groupId !== input.chapterId || app.tierId !== input.skuId || app.stripeRuntime !== input.runtime || app.status !== "submitted" || app.canceled === true || app.giftOfferId || app.giftClaimId || app.namedSeatId || app.namedSeatClaimPending) return fail5("membership_quote_unavailable");
5985
- let row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
5986
- if (row2 && keys2.some((key3) => row2[key3] !== input[key3])) return fail5("membership_quote_conflict");
6289
+ const { applicationId: id7, now, app } = input;
6290
+ 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");
6291
+ let row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7);
6292
+ if (row2 && keys2.some((key3) => row2[key3] !== input[key3])) return fail6("membership_quote_conflict");
5987
6293
  if (row2?.status === "ready") {
5988
6294
  try {
5989
6295
  return await restore2(row2, input, now);
@@ -5994,15 +6300,15 @@ async function prepareMembershipQuote(db, input, issue) {
5994
6300
  const identity2 = ["groupId", "tierId", "stripeRuntime", "email", "emailNormalized", "status", "canceled", "membershipRestartId", "namedSeatId", "namedSeatClaimPending", "giftClaimId", "giftOfferId"];
5995
6301
  const guards = [{
5996
6302
  ns: "applications",
5997
- id: id6,
6303
+ id: id7,
5998
6304
  exists: true,
5999
6305
  equals: Object.fromEntries(identity2.filter((key3) => app[key3] !== void 0).map((key3) => [key3, app[key3]])),
6000
6306
  absent: identity2.filter((key3) => app[key3] === void 0)
6001
- }, { ns: "subscriptionCheckoutIntents", id: id6, exists: false }];
6307
+ }, { ns: "subscriptionCheckoutIntents", id: id7, exists: false }];
6002
6308
  if (!row2 || row2.status === "ready" || row2.status === "restart") {
6003
6309
  const revision = Number(row2?.revision ?? 0) + 1;
6004
6310
  const pending = {
6005
- id: id6,
6311
+ id: id7,
6006
6312
  ...Object.fromEntries(keys2.map((key3) => [key3, input[key3]])),
6007
6313
  revision,
6008
6314
  status: "pending",
@@ -6010,25 +6316,25 @@ async function prepareMembershipQuote(db, input, issue) {
6010
6316
  updatedAt: now
6011
6317
  };
6012
6318
  try {
6013
- await db.transact([{ t: "update", ns: MEMBERSHIP_QUOTE_NAMESPACE, id: id6, attrs: pending }], {
6014
- mutationId: `membership-quote:${id6}:${revision}`,
6319
+ await db.transact([{ t: "update", ns: MEMBERSHIP_QUOTE_NAMESPACE, id: id7, attrs: pending }], {
6320
+ mutationId: `membership-quote:${id7}:${revision}`,
6015
6321
  guards: [...guards, {
6016
6322
  ns: MEMBERSHIP_QUOTE_NAMESPACE,
6017
- id: id6,
6323
+ id: id7,
6018
6324
  exists: Boolean(row2),
6019
6325
  ...row2 ? { equals: { revision: row2.revision, status: row2.status } } : {}
6020
6326
  }]
6021
6327
  });
6022
6328
  } catch (error) {
6023
6329
  if (error.code !== "transact_guard_failed") throw error;
6024
- const winner = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
6330
+ const winner = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7);
6025
6331
  if (!winner || winner.revision !== revision) throw error;
6026
6332
  }
6027
- row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
6333
+ row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7);
6028
6334
  }
6029
6335
  if (row2?.status === "ready") return restore2(row2, input, now);
6030
6336
  if (!row2 || row2.status !== "pending" || keys2.some((key3) => row2[key3] !== input[key3]) || typeof row2.requestId !== "string") {
6031
- return fail5("membership_quote_pending");
6337
+ return fail6("membership_quote_pending");
6032
6338
  }
6033
6339
  const target = Object.fromEntries(keys2.map((key3) => [key3, input[key3]]));
6034
6340
  const offer = await verifyIssuedMembershipOffer(await issue({ ...target, requestId: row2.requestId }), {
@@ -6049,7 +6355,8 @@ async function prepareMembershipQuote(db, input, issue) {
6049
6355
  policy: input.policy,
6050
6356
  billingTerms: offer.contract.billingTerms,
6051
6357
  presentation: input.presentation,
6052
- founding: foundingOf(offer)
6358
+ founding: foundingOf(offer),
6359
+ ...input.namedSeatsEnabled ? { seatOffer: { amountCents: offer.contract.offering.seatPriceCents } } : {}
6053
6360
  });
6054
6361
  const ready = { ...row2, offer, tier, quote, status: "ready", updatedAt: now };
6055
6362
  await restore2(ready, target, now, true);
@@ -6057,23 +6364,100 @@ async function prepareMembershipQuote(db, input, issue) {
6057
6364
  await db.transact([{
6058
6365
  t: "update",
6059
6366
  ns: MEMBERSHIP_QUOTE_NAMESPACE,
6060
- id: id6,
6367
+ id: id7,
6061
6368
  attrs: { offer, offerDigest: offer.digest, tier, quote, status: "ready", updatedAt: now }
6062
6369
  }], {
6063
- mutationId: `membership-quote-ready:${id6}:${row2.revision}`,
6370
+ mutationId: `membership-quote-ready:${id7}:${row2.revision}`,
6064
6371
  guards: [...guards, {
6065
6372
  ns: MEMBERSHIP_QUOTE_NAMESPACE,
6066
- id: id6,
6373
+ id: id7,
6067
6374
  exists: true,
6068
6375
  equals: { status: "pending", revision: row2.revision, requestId: row2.requestId }
6069
6376
  }]
6070
6377
  });
6071
6378
  } catch (error) {
6072
6379
  if (error.code !== "transact_guard_failed") throw error;
6073
- const winner = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
6380
+ const winner = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7);
6074
6381
  if (winner?.status !== "ready" || winner.requestId !== row2.requestId || winner.offerDigest !== offer.digest) throw error;
6075
6382
  }
6076
- return restore2(await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6), target, now);
6383
+ return restore2(await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7), target, now);
6384
+ }
6385
+ async function selectCheckoutNamedSeat(db, target, input) {
6386
+ if (!input.enabled) return fail6("named_seat_purchase_unavailable");
6387
+ const row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, target.applicationId);
6388
+ const current = await restore2(row2, target, input.now);
6389
+ const namedSeat = input.selection === null ? void 0 : checkoutNamedSeatSelection(input.selection, input.purchaserEmail);
6390
+ const quote = await createPaymentQuote({
6391
+ ...target,
6392
+ tier: current.tier,
6393
+ group: { merchantDisclosureText: current.quote.merchantDisclosureText },
6394
+ policy: { refundPolicyText: current.quote.refundPolicyText, trustCopy: current.quote.trustCopy },
6395
+ billingTerms: current.billingTerms,
6396
+ presentation: current.quote.presentation,
6397
+ founding: current.quote.founding,
6398
+ seatOffer: { amountCents: current.membershipOffer.contract.offering.seatPriceCents },
6399
+ namedSeat
6400
+ });
6401
+ if (quote.digest === current.quote.digest) return current;
6402
+ await db.transact([{
6403
+ t: "update",
6404
+ ns: MEMBERSHIP_QUOTE_NAMESPACE,
6405
+ id: target.applicationId,
6406
+ attrs: { quote, revision: current.projectionRevision + 1, updatedAt: input.now }
6407
+ }], {
6408
+ mutationId: `membership-quote-selection:${target.applicationId}:${current.projectionRevision}:${quote.digest}`,
6409
+ guards: [
6410
+ {
6411
+ ns: MEMBERSHIP_QUOTE_NAMESPACE,
6412
+ id: target.applicationId,
6413
+ exists: true,
6414
+ equals: { revision: current.projectionRevision, status: "ready" }
6415
+ },
6416
+ { ns: "subscriptionCheckoutIntents", id: target.applicationId, exists: false }
6417
+ ]
6418
+ });
6419
+ return restore2(await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, target.applicationId), target, input.now);
6420
+ }
6421
+
6422
+ // src/bounded-request-text.ts
6423
+ async function readBoundedRequestText(req, maxBytes) {
6424
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) throw new TypeError("invalid request body cap");
6425
+ const reader = req.body?.getReader();
6426
+ const cancel = () => {
6427
+ void reader?.cancel().catch(() => {
6428
+ });
6429
+ };
6430
+ const invalid2 = () => json({ error: "invalid JSON body" }, 400);
6431
+ let complete = false;
6432
+ req.signal.addEventListener("abort", cancel, { once: true });
6433
+ try {
6434
+ if (req.signal.aborted) return invalid2();
6435
+ const declared = req.headers.get("content-length");
6436
+ if (declared && /^\d+$/.test(declared) && Number(declared) > maxBytes) {
6437
+ return json({ error: "request body too large" }, 413);
6438
+ }
6439
+ if (!reader) return "";
6440
+ const decoder = new TextDecoder();
6441
+ let bytes = 0;
6442
+ let text8 = "";
6443
+ for (; ; ) {
6444
+ const { done, value } = await reader.read();
6445
+ if (req.signal.aborted) return invalid2();
6446
+ if (done) {
6447
+ complete = true;
6448
+ return text8 + decoder.decode();
6449
+ }
6450
+ bytes += value.byteLength;
6451
+ if (bytes > maxBytes) return json({ error: "request body too large" }, 413);
6452
+ text8 += decoder.decode(value, { stream: true });
6453
+ }
6454
+ } catch {
6455
+ return invalid2();
6456
+ } finally {
6457
+ req.signal.removeEventListener("abort", cancel);
6458
+ if (!complete) cancel();
6459
+ reader?.releaseLock();
6460
+ }
6077
6461
  }
6078
6462
 
6079
6463
  // src/payment-quote-route.ts
@@ -6107,8 +6491,11 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
6107
6491
  policy: { refundPolicyText: quote2.refundPolicyText, trustCopy: quote2.trustCopy },
6108
6492
  billingTerms,
6109
6493
  presentation: quote2.presentation,
6110
- founding: foundingOf(intent.membershipOffer)
6494
+ founding: foundingOf(intent.membershipOffer),
6495
+ seatOffer: quote2.seatOffer,
6496
+ namedSeat: quote2.namedSeat
6111
6497
  });
6498
+ if (quote2.seatOffer && quote2.seatOffer.amountCents !== intent.membershipOffer?.contract.offering.seatPriceCents) throw new Error("invalid seat snapshot");
6112
6499
  if (expected.digest !== quote2.digest) throw new Error("invalid commercial snapshot");
6113
6500
  return { tier: tier2, quote: expected, billingTerms, membershipOffer: intent.membershipOffer };
6114
6501
  }
@@ -6137,7 +6524,16 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
6137
6524
  const presentation = tierPresentationFor(control3?.document, tier.id);
6138
6525
  if (ctx.membershipAuthority) return prepareMembershipQuote(
6139
6526
  db,
6140
- { ...target, app, group, tier, policy: policy2, presentation, now: Date.now() },
6527
+ {
6528
+ ...target,
6529
+ app,
6530
+ group,
6531
+ tier,
6532
+ policy: policy2,
6533
+ presentation,
6534
+ namedSeatsEnabled: Boolean(ctx.membershipAuthority.namedSeats) && !app.membershipRestartId,
6535
+ now: Date.now()
6536
+ },
6141
6537
  (input) => ctx.membershipAuthority.issueOffer(input, env)
6142
6538
  );
6143
6539
  const quote = await createPaymentQuote({
@@ -6153,20 +6549,54 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
6153
6549
  });
6154
6550
  return { tier, quote, billingTerms: void 0 };
6155
6551
  }
6156
- async function paymentQuoteRoute(url, env, ctx, prepare = false) {
6157
- const id6 = url.searchParams.get("application");
6158
- if (!id6 || id6.length > 160) return json({ error: "application required" }, 400);
6552
+ async function paymentQuoteRoute(url, env, ctx, prepare = false, request) {
6553
+ let selection;
6554
+ if (request?.body) {
6555
+ try {
6556
+ const text8 = await readBoundedRequestText(request, 2048);
6557
+ if (text8 instanceof Response) return text8;
6558
+ const body = JSON.parse(text8);
6559
+ if (!body || typeof body !== "object" || Array.isArray(body) || Object.keys(body).some((key3) => key3 !== "namedSeat")) throw new Error("invalid body");
6560
+ selection = body.namedSeat;
6561
+ } catch {
6562
+ return json({ error: "invalid seat selection" }, 400);
6563
+ }
6564
+ }
6565
+ const id7 = url.searchParams.get("application");
6566
+ if (!id7 || id7.length > 160) return json({ error: "application required" }, 400);
6159
6567
  const db = ctx.makeDb(env);
6160
- const app = (await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } })).applications?.[0];
6568
+ const app = (await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } })).applications?.[0];
6161
6569
  if (!app || String(app.groupId ?? ctx.chapter.id) !== ctx.chapter.id) return json({ error: "not found" }, 404);
6162
6570
  if (typeof app.stripeRuntime === "string" && app.stripeRuntime !== stripeRuntimeIdentity(env.ODLA_RUNTIME)) return json({ error: "not found" }, 404);
6163
6571
  if (app.status !== "submitted" || app.giftOfferId || app.namedSeatId || app.namedSeatClaimPending || app.giftClaimId || app.canceled === true) return json({ error: "membership checkout unavailable" }, 409);
6164
6572
  const group = (await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups?.[0];
6165
6573
  if (!group) return json({ error: "payments not configured" }, 503);
6166
6574
  try {
6167
- const { quote } = await checkoutQuoteForApplication(db, app, group, env, ctx, prepare);
6168
- return new Response(JSON.stringify(quote), { headers: { "content-type": "application/json", "cache-control": "no-store" } });
6575
+ let { quote } = await checkoutQuoteForApplication(db, app, group, env, ctx, prepare);
6576
+ const intent = (await db.query({ subscriptionCheckoutIntents: { $: { where: { id: id7 }, limit: 1 } } })).subscriptionCheckoutIntents?.[0];
6577
+ if (prepare && !intent && !quote.seatOffer && ctx.membershipAuthority?.namedSeats && !app.membershipRestartId && selection === void 0) selection = null;
6578
+ if (selection !== void 0) {
6579
+ if (intent) return json({ error: "checkout has already started", code: "checkout_intent_mismatch" }, 409);
6580
+ ({ quote } = await selectCheckoutNamedSeat(
6581
+ db,
6582
+ {
6583
+ appId: env.ODLA_APP_ID,
6584
+ chapterId: ctx.chapter.id,
6585
+ environment: env.ODLA_ENV,
6586
+ runtime: stripeRuntimeIdentity(env.ODLA_RUNTIME),
6587
+ applicationId: id7,
6588
+ skuId: String(app.tierId)
6589
+ },
6590
+ { selection, purchaserEmail: String(app.email ?? ""), now: Date.now(), enabled: Boolean(ctx.membershipAuthority?.namedSeats) && !app.membershipRestartId }
6591
+ ));
6592
+ }
6593
+ return new Response(JSON.stringify(quote), { headers: {
6594
+ "content-type": "application/json",
6595
+ "cache-control": "no-store",
6596
+ "x-chapter-checkout-started": intent ? "true" : "false"
6597
+ } });
6169
6598
  } catch (error) {
6599
+ if (error.code === "invalid_named_seat_recipient") return json({ error: "Enter a different recipient's name and email." }, 400);
6170
6600
  const expired = error.code === "membership_quote_expired";
6171
6601
  return json({ error: "payment quote unavailable", ...expired ? { code: "payment_quote_expired" } : {} }, expired ? 409 : 503);
6172
6602
  }
@@ -6272,47 +6702,6 @@ async function resumeJoin(url, env, ctx) {
6272
6702
  return json({ error: "Application continuation is unavailable. Retry when its payment or membership configuration is restored." }, 409);
6273
6703
  }
6274
6704
 
6275
- // src/bounded-request-text.ts
6276
- async function readBoundedRequestText(req, maxBytes) {
6277
- if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) throw new TypeError("invalid request body cap");
6278
- const reader = req.body?.getReader();
6279
- const cancel = () => {
6280
- void reader?.cancel().catch(() => {
6281
- });
6282
- };
6283
- const invalid2 = () => json({ error: "invalid JSON body" }, 400);
6284
- let complete = false;
6285
- req.signal.addEventListener("abort", cancel, { once: true });
6286
- try {
6287
- if (req.signal.aborted) return invalid2();
6288
- const declared = req.headers.get("content-length");
6289
- if (declared && /^\d+$/.test(declared) && Number(declared) > maxBytes) {
6290
- return json({ error: "request body too large" }, 413);
6291
- }
6292
- if (!reader) return "";
6293
- const decoder = new TextDecoder();
6294
- let bytes = 0;
6295
- let text8 = "";
6296
- for (; ; ) {
6297
- const { done, value } = await reader.read();
6298
- if (req.signal.aborted) return invalid2();
6299
- if (done) {
6300
- complete = true;
6301
- return text8 + decoder.decode();
6302
- }
6303
- bytes += value.byteLength;
6304
- if (bytes > maxBytes) return json({ error: "request body too large" }, 413);
6305
- text8 += decoder.decode(value, { stream: true });
6306
- }
6307
- } catch {
6308
- return invalid2();
6309
- } finally {
6310
- req.signal.removeEventListener("abort", cancel);
6311
- if (!complete) cancel();
6312
- reader?.releaseLock();
6313
- }
6314
- }
6315
-
6316
6705
  // src/named-seat-application.ts
6317
6706
  async function namedSeatPurchaserName(db, applicationId, chapterId, runtime) {
6318
6707
  const app = await membershipLoad(db, "applications", applicationId);
@@ -6827,11 +7216,12 @@ var handleMember = async (req, url, env, ctx) => {
6827
7216
  group,
6828
7217
  Boolean(stripeKey)
6829
7218
  );
6830
- const founding = await foundingPreviews(offered, env, ctx);
7219
+ const seats = /* @__PURE__ */ new Map();
7220
+ const founding = await foundingPreviews(offered, env, ctx, seats);
6831
7221
  const activeControl = chapter.signupControl ? await loadActiveSignupControl(db, chapter.id, env.ODLA_ENV) : null;
6832
7222
  const application = controlledApplication(chapter.application, activeControl?.document);
6833
7223
  return json({
6834
- ...joinConfig(group, paymentsReady, joinConfigTiers(offered, { currency: typeof group.currency === "string" ? group.currency : void 0, interval: group.interval === "month" ? "month" : "year" }, founding), application.conditions),
7224
+ ...joinConfig(group, paymentsReady, joinConfigTiers(offered, { currency: typeof group.currency === "string" ? group.currency : void 0, interval: group.interval === "month" ? "month" : "year" }, founding, seats), application.conditions),
6835
7225
  fieldContract: chapterFlowFieldContract(chapter, "join", application),
6836
7226
  copy: { ...chapter.copy.join, ...activeControl?.document.application?.copy ?? {} },
6837
7227
  ...activeControl ? { signupControl: {
@@ -7207,10 +7597,10 @@ function tiersOf(value, errors) {
7207
7597
  return [];
7208
7598
  }
7209
7599
  unknownKeys(item, TIER_KEYS2, path, errors);
7210
- const id6 = text4(item.id, `${path}.id`, errors, 80);
7211
- if (id6 && !TIER.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7212
- if (seen.has(id6)) errors.push(`${path}.id duplicates "${id6}"`);
7213
- seen.add(id6);
7600
+ const id7 = text4(item.id, `${path}.id`, errors, 80);
7601
+ if (id7 && !TIER.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7602
+ if (seen.has(id7)) errors.push(`${path}.id duplicates "${id7}"`);
7603
+ seen.add(id7);
7214
7604
  const name = text4(item.name, `${path}.name`, errors, 160);
7215
7605
  if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
7216
7606
  if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
@@ -7231,7 +7621,7 @@ function tiersOf(value, errors) {
7231
7621
  }
7232
7622
  const blurb = item.blurb === void 0 ? void 0 : text4(item.blurb, `${path}.blurb`, errors, 4e3);
7233
7623
  return [{
7234
- id: id6,
7624
+ id: id7,
7235
7625
  name,
7236
7626
  available: item.available === true,
7237
7627
  sortOrder: Number(item.sortOrder) || 0,
@@ -7293,15 +7683,15 @@ function policyOf(value, errors, tiers) {
7293
7683
  if (input.tierPolicies !== void 0) {
7294
7684
  const overrides = recordOf(input.tierPolicies);
7295
7685
  if (!overrides || Object.keys(overrides).length > 20) errors.push("document.policy.tierPolicies must be a bounded tier map");
7296
- else for (const [id6, raw2] of Object.entries(overrides)) {
7297
- const path = `document.policy.tierPolicies.${id6}`;
7686
+ else for (const [id7, raw2] of Object.entries(overrides)) {
7687
+ const path = `document.policy.tierPolicies.${id7}`;
7298
7688
  const policy2 = recordOf(raw2);
7299
- if (!tiers.some((tier) => tier.id === id6) || !policy2) {
7689
+ if (!tiers.some((tier) => tier.id === id7) || !policy2) {
7300
7690
  errors.push(`${path} must name a declared tier and policy`);
7301
7691
  continue;
7302
7692
  }
7303
7693
  unknownKeys(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
7304
- tierPolicies[id6] = {
7694
+ tierPolicies[id7] = {
7305
7695
  refundPolicyText: text4(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
7306
7696
  ...policy2.trustCopy !== void 0 ? { trustCopy: text4(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
7307
7697
  };
@@ -7594,14 +7984,14 @@ function validateMemberContentDocument(input, options = {}) {
7594
7984
  continue;
7595
7985
  }
7596
7986
  unknownKeys2(raw2, PAGE_KEYS, path, errors);
7597
- const id6 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
7987
+ const id7 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
7598
7988
  const slug = textValue(raw2.slug, `${path}.slug`, errors, 120, true) ?? "";
7599
7989
  const title = textValue(raw2.title, `${path}.title`, errors, MAX_TEXT, true) ?? "";
7600
- if (id6 && !ID4.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7990
+ if (id7 && !ID4.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7601
7991
  if (slug && !SLUG2.test(slug)) errors.push(`${path}.slug must be a lowercase URL slug`);
7602
- if (ids2.has(id6) || knownParentIds.has(id6)) errors.push(`${path}.id duplicates page "${id6}"`);
7992
+ if (ids2.has(id7) || knownParentIds.has(id7)) errors.push(`${path}.id duplicates page "${id7}"`);
7603
7993
  if (slugs.has(slug) || (options.parentPages ?? []).some((page) => page.slug === slug)) errors.push(`${path}.slug duplicates "${slug}"`);
7604
- ids2.add(id6);
7994
+ ids2.add(id7);
7605
7995
  slugs.add(slug);
7606
7996
  const navRaw = record2(raw2.navigation);
7607
7997
  let navigation = { section: "General", order: index2 };
@@ -7657,7 +8047,7 @@ function validateMemberContentDocument(input, options = {}) {
7657
8047
  else if (owner === "chapter") upsell = { owner, ...offer ? { offer } : {} };
7658
8048
  }
7659
8049
  }
7660
- pages.push({ id: id6, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
8050
+ pages.push({ id: id7, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
7661
8051
  }
7662
8052
  const allIds = /* @__PURE__ */ new Set([...knownParentIds, ...ids2]);
7663
8053
  for (const [index2, page] of pages.entries()) {
@@ -7800,20 +8190,20 @@ async function loadLatestMemberContentControl(db, targetId, environment) {
7800
8190
  return control(Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
7801
8191
  }
7802
8192
  async function loadLocalMemberContent(db, chapterId, environment) {
7803
- const id6 = `member-content-local:${environment}`;
7804
- const rows5 = (await db.query({ memberContentLocal: { $: { where: { id: id6 }, limit: 1 } } })).memberContentLocal;
8193
+ const id7 = `member-content-local:${environment}`;
8194
+ const rows5 = (await db.query({ memberContentLocal: { $: { where: { id: id7 }, limit: 1 } } })).memberContentLocal;
7805
8195
  return local2(Array.isArray(rows5) ? rows5[0] : void 0);
7806
8196
  }
7807
8197
  async function materializeMemberContentControl(db, envelope, previous, receivedAt) {
7808
8198
  const { document, digest } = envelope;
7809
8199
  const target = document.target;
7810
- const id6 = `member-content-control:${target.environment}:${document.revision}`;
8200
+ const id7 = `member-content-control:${target.environment}:${document.revision}`;
7811
8201
  const ops = [];
7812
8202
  if (previous?.active) {
7813
8203
  ops.push({ t: "update", ns: "memberContentControls", id: previous.id, attrs: { active: false } });
7814
8204
  }
7815
- ops.push({ t: "update", ns: "memberContentControls", id: id6, attrs: {
7816
- id: id6,
8205
+ ops.push({ t: "update", ns: "memberContentControls", id: id7, attrs: {
8206
+ id: id7,
7817
8207
  sourceId: document.source.appId,
7818
8208
  targetId: target.chapterId,
7819
8209
  environment: target.environment,
@@ -7826,11 +8216,11 @@ async function materializeMemberContentControl(db, envelope, previous, receivedA
7826
8216
  await db.transact(ops, { mutationId: `member-content-control:${target.environment}:${document.revision}:${digest}` });
7827
8217
  }
7828
8218
  async function saveLocalMemberContent(db, document, environment, expectedRevision, updatedAt, updatedBy) {
7829
- const id6 = `member-content-local:${environment}`;
8219
+ const id7 = `member-content-local:${environment}`;
7830
8220
  const current = await loadLocalMemberContent(db, document.source.appId, environment);
7831
8221
  if ((current?.revision ?? 0) !== expectedRevision) throw new Error("member_content_revision_conflict");
7832
8222
  const row2 = {
7833
- id: id6,
8223
+ id: id7,
7834
8224
  chapterId: document.source.appId,
7835
8225
  environment,
7836
8226
  revision: document.revision,
@@ -7838,9 +8228,9 @@ async function saveLocalMemberContent(db, document, environment, expectedRevisio
7838
8228
  updatedAt,
7839
8229
  ...updatedBy ? { updatedBy } : {}
7840
8230
  };
7841
- await db.transact([{ t: "update", ns: "memberContentLocal", id: id6, attrs: row2 }], {
8231
+ await db.transact([{ t: "update", ns: "memberContentLocal", id: id7, attrs: row2 }], {
7842
8232
  mutationId: `member-content-local:${environment}:${document.revision}`,
7843
- guards: current ? [{ ns: "memberContentLocal", id: id6, exists: true, equals: { revision: expectedRevision } }] : [{ ns: "memberContentLocal", id: id6, exists: false }]
8233
+ guards: current ? [{ ns: "memberContentLocal", id: id7, exists: true, equals: { revision: expectedRevision } }] : [{ ns: "memberContentLocal", id: id7, exists: false }]
7844
8234
  });
7845
8235
  return row2;
7846
8236
  }
@@ -7977,7 +8367,7 @@ var handleAdminMemberContent = async (req, url, env, ctx) => {
7977
8367
  parent: parent ? { revision: parent.revision, digest: parent.digest, document: parent.document } : null,
7978
8368
  chapter: chapter ? { revision: chapter.revision, document: chapter.document } : null,
7979
8369
  effective,
7980
- tiers: tiers.map(({ id: id6, name, priceCents, active }) => ({ id: id6, name, priceCents, active })),
8370
+ tiers: tiers.map(({ id: id7, name, priceCents, active }) => ({ id: id7, name, priceCents, active })),
7981
8371
  localAuthoring: ctx.chapter.memberContent.localAuthoring
7982
8372
  });
7983
8373
  }
@@ -8135,11 +8525,11 @@ var handleAdminAdmissionGrants = async (req, url, env, ctx) => {
8135
8525
  return json({ error: "expiresAt must be a future epoch timestamp" }, 400);
8136
8526
  }
8137
8527
  const sourceGrantId = crypto.randomUUID();
8138
- const id6 = `admission:local:${ctx.chapter.id}:${sourceGrantId}`;
8528
+ const id7 = `admission:local:${ctx.chapter.id}:${sourceGrantId}`;
8139
8529
  const createdAt = Date.now();
8140
8530
  const actor = auth.user.email ?? auth.user.userId;
8141
- await auth.db.transact([{ t: "update", ns: "admissionGrants", id: id6, attrs: {
8142
- id: id6,
8531
+ await auth.db.transact([{ t: "update", ns: "admissionGrants", id: id7, attrs: {
8532
+ id: id7,
8143
8533
  sourceGrantId,
8144
8534
  sourceScope: "local",
8145
8535
  sourceId: ctx.chapter.id,
@@ -8155,7 +8545,7 @@ var handleAdminAdmissionGrants = async (req, url, env, ctx) => {
8155
8545
  updatedAt: createdAt,
8156
8546
  updatedBy: actor
8157
8547
  } }], { mutationId: `admission-local-create:${sourceGrantId}` });
8158
- return json({ grant: { id: id6, sourceGrantId, sourceScope: "local", email, decision, active: true, createdAt, ...note ? { note } : {}, ...expiresAt ? { expiresAt } : {} } }, 201);
8548
+ return json({ grant: { id: id7, sourceGrantId, sourceScope: "local", email, decision, active: true, createdAt, ...note ? { note } : {}, ...expiresAt ? { expiresAt } : {} } }, 201);
8159
8549
  }
8160
8550
  const prefix = "/api/admin/admission-grants/";
8161
8551
  let revokeId = "";
@@ -8169,20 +8559,20 @@ var handleAdminAdmissionGrants = async (req, url, env, ctx) => {
8169
8559
  if (/^admission:local:[a-z0-9-]+:[0-9a-f-]+$/.test(revokeId) && req.method === "DELETE") {
8170
8560
  const auth = await gate(req, env, ctx);
8171
8561
  if (auth instanceof Response) return auth;
8172
- const id6 = revokeId;
8173
- const found = await auth.db.query({ admissionGrants: { $: { where: { id: id6 }, limit: 1 } } });
8562
+ const id7 = revokeId;
8563
+ const found = await auth.db.query({ admissionGrants: { $: { where: { id: id7 }, limit: 1 } } });
8174
8564
  const row2 = Array.isArray(found.admissionGrants) ? found.admissionGrants[0] : void 0;
8175
8565
  if (!row2 || row2.targetId !== ctx.chapter.id || row2.sourceScope !== "local") return json({ error: "not found" }, 404);
8176
8566
  if (row2.active !== true) return json({ ok: true, replayed: true });
8177
8567
  const revokedAt = Date.now();
8178
8568
  const actor = auth.user.email ?? auth.user.userId;
8179
- await auth.db.transact([{ t: "update", ns: "admissionGrants", id: id6, attrs: {
8569
+ await auth.db.transact([{ t: "update", ns: "admissionGrants", id: id7, attrs: {
8180
8570
  active: false,
8181
8571
  revokedAt,
8182
8572
  revokedBy: actor,
8183
8573
  updatedAt: revokedAt,
8184
8574
  updatedBy: actor
8185
- } }], { mutationId: `admission-local-revoke:${id6}` });
8575
+ } }], { mutationId: `admission-local-revoke:${id7}` });
8186
8576
  return json({ ok: true, replayed: false });
8187
8577
  }
8188
8578
  return null;
@@ -8244,8 +8634,8 @@ async function loadLeaderAdmissionAssignments(db, targetId) {
8244
8634
  return await readAdmissionRows(db, "leaderAdmissionAssignments", { targetId }, MAX_ASSIGNMENTS);
8245
8635
  }
8246
8636
  async function loadLeaderAdmissionDelivery(db, targetId, environment) {
8247
- const id6 = deliveryId(targetId, environment);
8248
- const result = await db.query({ leaderAdmissionDeliveries: { $: { where: { id: id6 }, limit: 1 } } });
8637
+ const id7 = deliveryId(targetId, environment);
8638
+ const result = await db.query({ leaderAdmissionDeliveries: { $: { where: { id: id7 }, limit: 1 } } });
8249
8639
  return admissionRows(result.leaderAdmissionDeliveries)[0] ?? null;
8250
8640
  }
8251
8641
  function personAdmissionEmail(ctx, record4) {
@@ -8273,8 +8663,8 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8273
8663
  const now = Date.now();
8274
8664
  const previous = await loadLeaderAdmissionDelivery(opts.db, opts.target.id, opts.env.ODLA_ENV);
8275
8665
  const current = await loadLeaderAdmissionAssignments(opts.db, opts.target.id);
8276
- const id6 = assignmentId(opts.target.id, opts.personId);
8277
- const existing = current.find((row2) => row2.id === id6);
8666
+ const id7 = assignmentId(opts.target.id, opts.personId);
8667
+ const existing = current.find((row2) => row2.id === id7);
8278
8668
  const digest = await signupControlDigest({
8279
8669
  targetId: opts.target.id,
8280
8670
  personId: opts.personId,
@@ -8304,7 +8694,7 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8304
8694
  return { delivery: saved, duplicate: true };
8305
8695
  }
8306
8696
  const assignment = {
8307
- id: id6,
8697
+ id: id7,
8308
8698
  targetId: opts.target.id,
8309
8699
  personId: opts.personId,
8310
8700
  grantId: existing?.grantId ?? crypto.randomUUID().replaceAll("-", ""),
@@ -8319,7 +8709,7 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8319
8709
  updatedAt: now,
8320
8710
  updatedBy: opts.actor
8321
8711
  };
8322
- const desired = current.filter((row2) => row2.id !== id6 && row2.active).concat(assignment.active ? [assignment] : []);
8712
+ const desired = current.filter((row2) => row2.id !== id7 && row2.active).concat(assignment.active ? [assignment] : []);
8323
8713
  const grants = await Promise.all(desired.map((row2) => grantInput(opts.db, opts.ctx, row2, opts.resolveEmail)));
8324
8714
  const revision = (previous?.revision ?? 0) + 1;
8325
8715
  const envelope = await createAdmissionGrantEnvelope({
@@ -8342,7 +8732,7 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8342
8732
  updatedAt: now
8343
8733
  };
8344
8734
  const ops = [
8345
- { t: "update", ns: "leaderAdmissionAssignments", id: id6, attrs: assignment },
8735
+ { t: "update", ns: "leaderAdmissionAssignments", id: id7, attrs: assignment },
8346
8736
  { t: "update", ns: "leaderAdmissionDeliveries", id: delivery.id, attrs: delivery },
8347
8737
  { t: "update", ns: "leaderAdmissionMutations", id: mutationReceiptId, attrs: {
8348
8738
  id: mutationReceiptId,
@@ -8357,7 +8747,7 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8357
8747
  ...!opts.note && existing?.note !== void 0 ? ["note"] : [],
8358
8748
  ...opts.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
8359
8749
  ];
8360
- if (retract.length) ops.push({ t: "retract", ns: "leaderAdmissionAssignments", id: id6, attrs: retract });
8750
+ if (retract.length) ops.push({ t: "retract", ns: "leaderAdmissionAssignments", id: id7, attrs: retract });
8361
8751
  const tx = await opts.db.transact(ops, {
8362
8752
  mutationId: `leader-admission:${opts.target.id}:${opts.mutationId}`,
8363
8753
  guards: previous ? [{ ns: "leaderAdmissionDeliveries", id: previous.id, exists: true, equals: { revision: previous.revision } }] : [{ ns: "leaderAdmissionDeliveries", id: delivery.id, exists: false }]
@@ -8417,11 +8807,11 @@ function projectRecord(record4, allowed2, editable, stagesEnabled, crm) {
8417
8807
  allowed2.flatMap((name) => Object.prototype.hasOwnProperty.call(record4.fields, name) ? [[name, record4.fields[name]]] : [])
8418
8808
  );
8419
8809
  const def = crm.type(record4.type);
8420
- const editFields = editable.map((id6) => {
8421
- const field = def.fields[id6];
8810
+ const editFields = editable.map((id7) => {
8811
+ const field = def.fields[id7];
8422
8812
  return {
8423
- id: id6,
8424
- label: field.label ?? id6,
8813
+ id: id7,
8814
+ label: field.label ?? id7,
8425
8815
  type: field.type,
8426
8816
  required: field.required ?? false,
8427
8817
  ...field.options ? { options: field.options } : {}
@@ -8538,8 +8928,8 @@ async function gate2(req, env, ctx) {
8538
8928
  if (!await networkWriteAllowed(req, db, user, ctx, true)) return { response: reject6(403, "super_admin_required", "super-admin required") };
8539
8929
  return { db, user };
8540
8930
  }
8541
- function targetFor(ctx, id6) {
8542
- return ctx.chapter.network.targets.find((target) => target.id === id6 && target.admissionGrants);
8931
+ function targetFor(ctx, id7) {
8932
+ return ctx.chapter.network.targets.find((target) => target.id === id7 && target.admissionGrants);
8543
8933
  }
8544
8934
  async function deliverSaved(db, env, ctx, target, delivery) {
8545
8935
  if (delivery.status === "acknowledged") return delivery;
@@ -8643,7 +9033,7 @@ var handleAdminNetworkAdmissions = async (req, url, env, ctx) => {
8643
9033
  if (!personId || !MUTATION_ID2.test(mutationId)) {
8644
9034
  return reject6(422, "invalid_request", "personId and a stable mutationId are required");
8645
9035
  }
8646
- const resolveEmail = (id6) => resolveAdmissionPersonEmail(db, env, ctx, target, id6);
9036
+ const resolveEmail = (id7) => resolveAdmissionPersonEmail(db, env, ctx, target, id7);
8647
9037
  let email;
8648
9038
  try {
8649
9039
  email = await resolveEmail(personId);
@@ -8882,28 +9272,28 @@ var handleSchedule = async (req, url, env, ctx) => {
8882
9272
  };
8883
9273
 
8884
9274
  // src/payments-stripe.ts
8885
- var import_stripe11 = require("@odla-ai/stripe");
9275
+ var import_stripe12 = require("@odla-ai/stripe");
8886
9276
 
8887
9277
  // src/payments-refund.ts
8888
- var import_stripe9 = require("@odla-ai/stripe");
9278
+ var import_stripe10 = require("@odla-ai/stripe");
8889
9279
  var row = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
8890
- var id2 = (value) => typeof value === "string" ? value : String(row(value).id ?? "");
9280
+ var id3 = (value) => typeof value === "string" ? value : String(row(value).id ?? "");
8891
9281
  var providerId2 = (value, prefix) => new RegExp(`^${prefix}_[A-Za-z0-9]+$`).test(value);
8892
- function fail6(code = "refund_target_mismatch") {
9282
+ function fail7(code = "refund_target_mismatch") {
8893
9283
  throw Object.assign(new Error(code), { code });
8894
9284
  }
8895
9285
  async function refundSubscriptionPayment(secretKey, input) {
8896
- if (!providerId2(input.customerId, "cus") || !providerId2(input.subscriptionId, "sub")) fail6();
9286
+ if (!providerId2(input.customerId, "cus") || !providerId2(input.subscriptionId, "sub")) fail7();
8897
9287
  const call = async (method, path, params, key3) => {
8898
- const result = await (0, import_stripe9.stripeRequest)({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
8899
- if (!result.ok) fail6("stripe_error");
8900
- if (!result.body || typeof result.body !== "object" || Array.isArray(result.body)) fail6("stripe_invalid_response");
9288
+ const result = await (0, import_stripe10.stripeRequest)({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
9289
+ if (!result.ok) fail7("stripe_error");
9290
+ if (!result.body || typeof result.body !== "object" || Array.isArray(result.body)) fail7("stripe_invalid_response");
8901
9291
  return result.body;
8902
9292
  };
8903
9293
  const sub = await call("GET", `/v1/subscriptions/${input.subscriptionId}`);
8904
- if (id2(sub) !== input.subscriptionId || id2(sub.customer) !== input.customerId) fail6();
9294
+ if (id3(sub) !== input.subscriptionId || id3(sub.customer) !== input.customerId) fail7();
8905
9295
  for (const [key3, value] of Object.entries(input.metadata ?? {})) {
8906
- if (row(sub.metadata)[key3] !== value) fail6();
9296
+ if (row(sub.metadata)[key3] !== value) fail7();
8907
9297
  }
8908
9298
  let cursor;
8909
9299
  let earliest;
@@ -8916,79 +9306,79 @@ async function refundSubscriptionPayment(secretKey, input) {
8916
9306
  limit: 100,
8917
9307
  ...cursor ? { starting_after: cursor } : {}
8918
9308
  });
8919
- if (!Array.isArray(list.data) || typeof list.has_more !== "boolean") fail6("stripe_invalid_response");
9309
+ if (!Array.isArray(list.data) || typeof list.has_more !== "boolean") fail7("stripe_invalid_response");
8920
9310
  for (const value of list.data) {
8921
9311
  const invoice2 = row(value);
8922
9312
  const subscription = invoice2.subscription ?? row(row(invoice2.parent).subscription_details).subscription;
8923
- if (!providerId2(id2(invoice2), "in") || seen.has(id2(invoice2)) || id2(subscription) !== input.subscriptionId || id2(invoice2.customer) !== input.customerId || invoice2.status !== "paid" || !Number.isSafeInteger(invoice2.created) || !Number.isSafeInteger(invoice2.amount_paid) || Number(invoice2.amount_paid) < 0) fail6();
8924
- seen.add(id2(invoice2));
9313
+ 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();
9314
+ seen.add(id3(invoice2));
8925
9315
  if (Number(invoice2.amount_paid) > 0 && (!earliest || Number(invoice2.created) < Number(earliest.created))) earliest = invoice2;
8926
9316
  }
8927
9317
  if (!list.has_more) break;
8928
- if (!list.data.length || page === 9) fail6("refund_history_incomplete");
8929
- cursor = id2(list.data[list.data.length - 1]);
9318
+ if (!list.data.length || page === 9) fail7("refund_history_incomplete");
9319
+ cursor = id3(list.data[list.data.length - 1]);
8930
9320
  }
8931
- if (!earliest) fail6("no_charge");
9321
+ if (!earliest) fail7("no_charge");
8932
9322
  const invoice = earliest;
8933
- if (input.expectedInvoiceId !== void 0 && id2(invoice) !== input.expectedInvoiceId || input.expectedAmountCents !== void 0 && invoice.amount_paid !== input.expectedAmountCents) fail6();
8934
- let chargeId = id2(invoice.charge);
8935
- let paymentIntentId = id2(invoice.payment_intent);
9323
+ if (input.expectedInvoiceId !== void 0 && id3(invoice) !== input.expectedInvoiceId || input.expectedAmountCents !== void 0 && invoice.amount_paid !== input.expectedAmountCents) fail7();
9324
+ let chargeId = id3(invoice.charge);
9325
+ let paymentIntentId = id3(invoice.payment_intent);
8936
9326
  if (!chargeId && !paymentIntentId) {
8937
- const payments = await call("GET", "/v1/invoice_payments", { invoice: id2(invoice), status: "paid", limit: 100 });
8938
- if (payments.has_more !== false || !Array.isArray(payments.data) || payments.data.length !== 1) fail6("refund_payment_ambiguous");
9327
+ const payments = await call("GET", "/v1/invoice_payments", { invoice: id3(invoice), status: "paid", limit: 100 });
9328
+ if (payments.has_more !== false || !Array.isArray(payments.data) || payments.data.length !== 1) fail7("refund_payment_ambiguous");
8939
9329
  const payment = row(payments.data[0]);
8940
- if (id2(payment.invoice) !== id2(invoice) || payment.status !== "paid" || row(payment.payment).type !== "payment_intent") fail6();
8941
- paymentIntentId = id2(row(payment.payment).payment_intent);
9330
+ if (id3(payment.invoice) !== id3(invoice) || payment.status !== "paid" || row(payment.payment).type !== "payment_intent") fail7();
9331
+ paymentIntentId = id3(row(payment.payment).payment_intent);
8942
9332
  }
8943
9333
  if (!chargeId) {
8944
- if (!providerId2(paymentIntentId, "pi")) fail6();
9334
+ if (!providerId2(paymentIntentId, "pi")) fail7();
8945
9335
  const payment = await call("GET", `/v1/payment_intents/${paymentIntentId}`);
8946
- if (id2(payment) !== paymentIntentId || id2(payment.customer) !== input.customerId || payment.status !== "succeeded") fail6();
8947
- chargeId = id2(payment.latest_charge);
9336
+ if (id3(payment) !== paymentIntentId || id3(payment.customer) !== input.customerId || payment.status !== "succeeded") fail7();
9337
+ chargeId = id3(payment.latest_charge);
8948
9338
  }
8949
- if (!providerId2(chargeId, "ch")) fail6();
9339
+ if (!providerId2(chargeId, "ch")) fail7();
8950
9340
  const charge = await call("GET", `/v1/charges/${chargeId}`);
8951
- if (id2(charge) !== chargeId || id2(charge.customer) !== input.customerId || charge.status !== "succeeded" || charge.invoice != null && id2(charge.invoice) !== id2(invoice) || paymentIntentId && id2(charge.payment_intent) !== paymentIntentId || !Number.isSafeInteger(charge.amount) || Number(charge.amount) <= 0 || !Number.isSafeInteger(charge.amount_refunded) || Number(charge.amount_refunded) < 0 || Number(charge.amount_refunded) > Number(charge.amount) || charge.currency !== invoice.currency || Number(charge.amount) !== Number(invoice.amount_paid)) fail6();
8952
- if (input.alreadyRefundedChargeId !== void 0 && (chargeId !== input.alreadyRefundedChargeId || charge.amount_refunded !== charge.amount || charge.refunded !== true)) fail6();
9341
+ 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();
9342
+ if (input.alreadyRefundedChargeId !== void 0 && (chargeId !== input.alreadyRefundedChargeId || charge.amount_refunded !== charge.amount || charge.refunded !== true)) fail7();
8953
9343
  if (charge.amount_refunded !== charge.amount) {
8954
9344
  const refund = await call("POST", "/v1/refunds", {
8955
9345
  charge: chargeId,
8956
9346
  ...input.metadata ? { metadata: input.metadata } : {}
8957
9347
  }, `chapter-initial-refund:${input.subscriptionId}:${chargeId}`);
8958
- if (id2(refund.charge) !== chargeId || !["succeeded", "pending"].includes(String(refund.status)) || !Number.isSafeInteger(refund.amount) || Number(refund.amount) !== Number(charge.amount) - Number(charge.amount_refunded)) fail6("refund_not_confirmed");
9348
+ if (id3(refund.charge) !== chargeId || !["succeeded", "pending"].includes(String(refund.status)) || !Number.isSafeInteger(refund.amount) || Number(refund.amount) !== Number(charge.amount) - Number(charge.amount_refunded)) fail7("refund_not_confirmed");
8959
9349
  }
8960
9350
  let subscriptionCanceled = sub.status === "canceled";
8961
9351
  if (input.cancelSubscription !== false && !subscriptionCanceled) {
8962
9352
  const canceled = await call("DELETE", `/v1/subscriptions/${input.subscriptionId}`);
8963
- if (id2(canceled) !== input.subscriptionId || canceled.status !== "canceled") fail6("refund_cancel_incomplete");
9353
+ if (id3(canceled) !== input.subscriptionId || canceled.status !== "canceled") fail7("refund_cancel_incomplete");
8964
9354
  subscriptionCanceled = true;
8965
9355
  }
8966
9356
  const confirmed = await call("GET", `/v1/charges/${chargeId}`);
8967
- if (id2(confirmed) !== chargeId || id2(confirmed.customer) !== input.customerId || confirmed.amount !== charge.amount || confirmed.currency !== charge.currency || confirmed.amount_refunded !== charge.amount || confirmed.refunded !== true) fail6("refund_not_confirmed");
9357
+ if (id3(confirmed) !== chargeId || id3(confirmed.customer) !== input.customerId || confirmed.amount !== charge.amount || confirmed.currency !== charge.currency || confirmed.amount_refunded !== charge.amount || confirmed.refunded !== true) fail7("refund_not_confirmed");
8968
9358
  return { refundedCents: Number(charge.amount), subscriptionCanceled };
8969
9359
  }
8970
9360
 
8971
9361
  // src/subscription-payment-identity.ts
8972
- var import_stripe10 = require("@odla-ai/stripe");
8973
- var id3 = (value) => typeof value === "string" ? value : String(value?.id ?? "");
9362
+ var import_stripe11 = require("@odla-ai/stripe");
9363
+ var id4 = (value) => typeof value === "string" ? value : String(value?.id ?? "");
8974
9364
  async function tagSubscriptionPayment(secretKey, invoice, customerId, metadata, clientSecret2, applicationId) {
8975
- let paymentId = id3(invoice.payment_intent);
9365
+ let paymentId = id4(invoice.payment_intent);
8976
9366
  if (!paymentId) {
8977
- if (!/^in_[A-Za-z0-9]+$/.test(id3(invoice))) subscriptionContractMismatch();
8978
- const result2 = await (0, import_stripe10.stripeRequest)({ secretKey }, "GET", "/v1/invoice_payments", { invoice: id3(invoice), limit: 100 });
9367
+ if (!/^in_[A-Za-z0-9]+$/.test(id4(invoice))) subscriptionContractMismatch();
9368
+ const result2 = await (0, import_stripe11.stripeRequest)({ secretKey }, "GET", "/v1/invoice_payments", { invoice: id4(invoice), limit: 100 });
8979
9369
  const list = result2.body;
8980
9370
  if (!result2.ok || list?.has_more !== false || !Array.isArray(list.data) || list.data.length !== 1) subscriptionContractMismatch();
8981
9371
  const link = list.data[0];
8982
- if (id3(link.invoice) !== id3(invoice) || !["open", "paid"].includes(link.status) || link.payment?.type !== "payment_intent") subscriptionContractMismatch();
8983
- paymentId = id3(link.payment.payment_intent);
9372
+ if (id4(link.invoice) !== id4(invoice) || !["open", "paid"].includes(link.status) || link.payment?.type !== "payment_intent") subscriptionContractMismatch();
9373
+ paymentId = id4(link.payment.payment_intent);
8984
9374
  }
8985
9375
  if (!/^pi_[A-Za-z0-9]+$/.test(paymentId)) subscriptionContractMismatch();
8986
9376
  const path = `/v1/payment_intents/${paymentId}`;
8987
- const result = await (0, import_stripe10.stripeRequest)({ secretKey }, "GET", path);
9377
+ const result = await (0, import_stripe11.stripeRequest)({ secretKey }, "GET", path);
8988
9378
  const payment = result.body;
8989
- if (!result.ok || payment?.id !== paymentId || id3(payment.customer) !== customerId || payment.amount !== invoice.total || payment.currency !== invoice.currency || payment.client_secret !== clientSecret2 || payment.livemode !== (metadata.odlaEnvironment === "prod") || !Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === void 0 || payment.metadata[key3] === value)) subscriptionContractMismatch();
9379
+ if (!result.ok || payment?.id !== paymentId || id4(payment.customer) !== customerId || payment.amount !== invoice.total || payment.currency !== invoice.currency || payment.client_secret !== clientSecret2 || payment.livemode !== (metadata.odlaEnvironment === "prod") || !Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === void 0 || payment.metadata[key3] === value)) subscriptionContractMismatch();
8990
9380
  if (Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === value)) return;
8991
- const tagged = await (0, import_stripe10.stripeRequest)(
9381
+ const tagged = await (0, import_stripe11.stripeRequest)(
8992
9382
  { secretKey },
8993
9383
  "POST",
8994
9384
  path,
@@ -9000,7 +9390,7 @@ async function tagSubscriptionPayment(secretKey, invoice, customerId, metadata,
9000
9390
 
9001
9391
  // src/payments-stripe.ts
9002
9392
  async function stripeCall(sk, method, path, params, idempotencyKey) {
9003
- return (0, import_stripe11.stripeRequest)(
9393
+ return (0, import_stripe12.stripeRequest)(
9004
9394
  { secretKey: sk },
9005
9395
  method,
9006
9396
  path,
@@ -9008,7 +9398,7 @@ async function stripeCall(sk, method, path, params, idempotencyKey) {
9008
9398
  idempotencyKey ? { idempotencyKey } : {}
9009
9399
  );
9010
9400
  }
9011
- function fail7(op, r) {
9401
+ function fail8(op, r) {
9012
9402
  const err = new Error(`stripe ${op} failed: ${r.status}`);
9013
9403
  err.code = "stripe_error";
9014
9404
  throw err;
@@ -9047,6 +9437,15 @@ function createStripeProvider(config) {
9047
9437
  let customerId = input.existingCustomerId;
9048
9438
  if (customerId && !/^cus_[A-Za-z0-9]+$/.test(customerId) || input.existingSubscriptionId && (!customerId || !/^sub_[A-Za-z0-9]+$/.test(input.existingSubscriptionId))) subscriptionContractMismatch();
9049
9439
  const productId = await verifySubscriptionPrice(sk, input, Boolean(input.existingSubscriptionId));
9440
+ const seat = input.namedSeat?.amountCents ? input.namedSeat : void 0;
9441
+ const seatProductId = seat ? await verifySubscriptionPrice(sk, {
9442
+ ...input,
9443
+ priceId: seat.priceId,
9444
+ priceCents: seat.amountCents,
9445
+ billingTerms: void 0,
9446
+ namedSeat: void 0
9447
+ }, Boolean(input.existingSubscriptionId)) : void 0;
9448
+ if (seat && (!seatProductId || !productId || seatProductId === productId)) subscriptionContractMismatch();
9050
9449
  if (input.billingTerms?.discount && !productId) subscriptionContractMismatch();
9051
9450
  await prepareSubscriptionBillingTerms(sk, { ...input, discountProductId: productId }, Boolean(input.existingSubscriptionId));
9052
9451
  const billingParams = subscriptionBillingParams(input, productId);
@@ -9058,7 +9457,7 @@ function createStripeProvider(config) {
9058
9457
  { email: input.email, name: input.name, metadata: meta },
9059
9458
  `cus:${input.applicationId}`
9060
9459
  );
9061
- if (!cust.ok) fail7("customer create", cust);
9460
+ if (!cust.ok) fail8("customer create", cust);
9062
9461
  if (typeof cust.body.id !== "string" || !/^cus_[A-Za-z0-9]+$/.test(cust.body.id)) subscriptionContractMismatch();
9063
9462
  customerId = cust.body.id;
9064
9463
  }
@@ -9078,19 +9477,32 @@ function createStripeProvider(config) {
9078
9477
  {
9079
9478
  customer: customerId,
9080
9479
  "items[0][price]": input.priceId,
9480
+ ...seat ? {
9481
+ "items[1][price]": seat.priceId,
9482
+ "items[1][quantity]": 1,
9483
+ "items[1][metadata][seatId]": seat.seatId,
9484
+ "items[1][metadata][membershipId]": seat.membershipId
9485
+ } : {},
9081
9486
  payment_behavior: "default_incomplete",
9082
9487
  "payment_settings[save_default_payment_method]": "on_subscription",
9083
9488
  "payment_settings[payment_method_types][0]": "card",
9084
9489
  "expand[0]": "latest_invoice.confirmation_secret",
9085
9490
  "expand[1]": "latest_invoice.payment_intent",
9086
9491
  metadata: meta,
9087
- ...billingParams
9492
+ ...billingParams,
9493
+ ...seat && input.billingTerms?.initialTermEndsAt ? {
9494
+ "add_invoice_items[1][price_data][product]": seatProductId,
9495
+ "add_invoice_items[1][price_data][currency]": input.currency,
9496
+ "add_invoice_items[1][price_data][unit_amount]": seat.amountCents,
9497
+ "add_invoice_items[1][quantity]": 1,
9498
+ "add_invoice_items[1][discountable]": false
9499
+ } : {}
9088
9500
  },
9089
9501
  // Hardening: one subscription per application, so a client
9090
9502
  // retry between create and the db write can't orphan a second one.
9091
9503
  `sub:${input.applicationId}${input.purchaseAttemptId ? `:${input.purchaseAttemptId}` : ""}`
9092
9504
  );
9093
- if (!sub.ok) fail7("subscription create", sub);
9505
+ if (!sub.ok) fail8("subscription create", sub);
9094
9506
  if (input.billingTerms?.discount?.duration === "once" && Array.isArray(sub.body.discounts) && sub.body.discounts.length === 0) {
9095
9507
  const subscriptionId = sub.body.id;
9096
9508
  if (typeof subscriptionId !== "string" || !/^sub_[A-Za-z0-9]+$/.test(subscriptionId)) subscriptionContractMismatch();
@@ -9100,14 +9512,14 @@ function createStripeProvider(config) {
9100
9512
  "expand[2]": "discounts",
9101
9513
  "expand[3]": "latest_invoice.discounts"
9102
9514
  });
9103
- if (!expanded.ok) fail7("subscription invoice evidence", expanded);
9515
+ if (!expanded.ok) fail8("subscription invoice evidence", expanded);
9104
9516
  if (expanded.body.id !== subscriptionId) subscriptionContractMismatch();
9105
9517
  sub = expanded;
9106
9518
  }
9107
9519
  verifySubscriptionResult(sub.body, input, customerId, meta);
9108
9520
  const invoice = sub.body.latest_invoice;
9109
9521
  const clientSecret2 = clientSecretOf(sub.body);
9110
- if (!clientSecret2) fail7("subscription confirmation-secret missing", sub);
9522
+ if (!clientSecret2) fail8("subscription confirmation-secret missing", sub);
9111
9523
  await tagSubscriptionPayment(
9112
9524
  sk,
9113
9525
  invoice,
@@ -9119,7 +9531,7 @@ function createStripeProvider(config) {
9119
9531
  return { customerId, subscriptionId: String(sub.body.id), clientSecret: clientSecret2 };
9120
9532
  },
9121
9533
  async ingestWebhook(rawBody, sigHeader) {
9122
- if (!webhookSecret || !await (0, import_stripe11.verifyStripeSignature)(rawBody, sigHeader, webhookSecret)) {
9534
+ if (!webhookSecret || !await (0, import_stripe12.verifyStripeSignature)(rawBody, sigHeader, webhookSecret)) {
9123
9535
  return { ok: false, reason: "bad_signature" };
9124
9536
  }
9125
9537
  const event2 = JSON.parse(rawBody);
@@ -9319,9 +9731,9 @@ function matchesIntent(stored, expected) {
9319
9731
  "purchaseAttemptId"
9320
9732
  ].every((key3) => stored[key3] === expected[key3]) && customerMatches && JSON.stringify(storedTerms) === JSON.stringify(expected.billingTerms) && stored.membershipOffer?.digest === expected.membershipOffer?.digest && stored.paymentQuote?.digest === expected.paymentQuote?.digest;
9321
9733
  }
9322
- async function loadIntent(db, id6) {
9734
+ async function loadIntent(db, id7) {
9323
9735
  return first3((await db.query({
9324
- subscriptionCheckoutIntents: { $: { where: { id: id6 }, limit: 1 } }
9736
+ subscriptionCheckoutIntents: { $: { where: { id: id7 }, limit: 1 } }
9325
9737
  })).subscriptionCheckoutIntents);
9326
9738
  }
9327
9739
  async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCheckout) {
@@ -9342,16 +9754,22 @@ async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCh
9342
9754
  if (input.purchaseAttemptId !== application?.membershipRestartId) mismatch();
9343
9755
  if (application?.namedSeatId || application?.namedSeatClaimPending || application?.giftClaimId) mismatch();
9344
9756
  if (input.membershipOffer && (!application || application.canceled === true || application.status !== "submitted")) mismatch();
9757
+ if (input.paymentQuote?.namedSeat && application?.email !== input.email) mismatch();
9345
9758
  const applicationGuard = {
9346
9759
  ns: "applications",
9347
9760
  id: input.applicationId,
9348
9761
  exists: true,
9349
- equals: { status: "submitted", ...input.purchaseAttemptId ? { membershipRestartId: input.purchaseAttemptId } : {}, ...input.membershipOffer ? {
9350
- groupId: input.groupId,
9351
- tierId: input.tierId,
9352
- stripeRuntime: stripeRuntimeIdentity(input.runtime),
9353
- ...application?.canceled !== void 0 ? { canceled: application.canceled } : {}
9354
- } : {} },
9762
+ equals: {
9763
+ status: "submitted",
9764
+ ...input.paymentQuote?.namedSeat ? { email: input.email } : {},
9765
+ ...input.purchaseAttemptId ? { membershipRestartId: input.purchaseAttemptId } : {},
9766
+ ...input.membershipOffer ? {
9767
+ groupId: input.groupId,
9768
+ tierId: input.tierId,
9769
+ stripeRuntime: stripeRuntimeIdentity(input.runtime),
9770
+ ...application?.canceled !== void 0 ? { canceled: application.canceled } : {}
9771
+ } : {}
9772
+ },
9355
9773
  absent: ["namedSeatId", "namedSeatClaimPending", "giftClaimId", ...!input.purchaseAttemptId ? ["membershipRestartId"] : [], ...input.membershipOffer && application?.canceled === void 0 ? ["canceled"] : []]
9356
9774
  };
9357
9775
  let stored = await loadIntent(db, input.applicationId);
@@ -9375,7 +9793,11 @@ async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCh
9375
9793
  ns: MEMBERSHIP_QUOTE_NAMESPACE,
9376
9794
  id: input.applicationId,
9377
9795
  exists: true,
9378
- equals: { status: "ready", offerDigest: input.membershipOffer.digest }
9796
+ equals: {
9797
+ status: "ready",
9798
+ offerDigest: input.membershipOffer.digest,
9799
+ ...input.projectionRevision !== void 0 ? { revision: input.projectionRevision } : {}
9800
+ }
9379
9801
  }
9380
9802
  ] : []
9381
9803
  ]
@@ -9390,7 +9812,8 @@ async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCh
9390
9812
  if (!stored.providerSubscriptionId && input.now - Number(stored.createdAt) >= 23 * 60 * 6e4) {
9391
9813
  throw Object.assign(new Error("checkout requires provider reconciliation"), { code: "checkout_recovery_required" });
9392
9814
  }
9393
- if (input.membershipOffer) await authorizeNetworkCheckout();
9815
+ const binding = input.membershipOffer ? await authorizeNetworkCheckout() : void 0;
9816
+ if (input.paymentQuote?.namedSeat && (!binding?.namedSeat || binding.namedSeat.amountCents !== input.paymentQuote.lineItems.namedSeatCents || binding.namedSeat.recipientEmail !== input.paymentQuote.namedSeat.recipientEmail || binding.namedSeat.recipientName !== input.paymentQuote.namedSeat.recipientName || binding.namedSeat.purchaserEmail !== input.emailNormalized)) mismatch();
9394
9817
  const result = await provider.createSubscription({
9395
9818
  appId: input.appId,
9396
9819
  chapterId: input.chapterId,
@@ -9405,6 +9828,7 @@ async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCh
9405
9828
  priceCents: input.priceCents,
9406
9829
  currency: input.currency,
9407
9830
  interval: input.interval,
9831
+ ...binding?.namedSeat ? { namedSeat: binding.namedSeat } : {},
9408
9832
  ...stored.billingTerms ? { billingTerms: stored.billingTerms } : {},
9409
9833
  ...typeof stored.providerSubscriptionId === "string" ? { existingSubscriptionId: stored.providerSubscriptionId } : {},
9410
9834
  existingCustomerId: typeof stored.providerCustomerId === "string" ? stored.providerCustomerId : input.existingCustomerId
@@ -9521,6 +9945,7 @@ async function startSubscription(req, env, ctx) {
9521
9945
  return json({ error: "payment quote unavailable", ...expired ? { code: "payment_quote_changed" } : {} }, expired ? 409 : 503);
9522
9946
  }
9523
9947
  const { tier, quote, billingTerms } = checkout2;
9948
+ if (quote.namedSeat && body.seatTermsAck !== true) return json({ error: "seatTermsAck required" }, 400);
9524
9949
  const membershipOffer = "membershipOffer" in checkout2 ? checkout2.membershipOffer : void 0;
9525
9950
  if (ctx.membershipAuthority && !membershipOffer) return json({ error: "checkout requires reconciliation", code: "checkout_recovery_required" }, 409);
9526
9951
  if (!tier) return json({ error: "tier not found", code: "tier_unresolved" }, 400);
@@ -9545,6 +9970,7 @@ async function startSubscription(req, env, ctx) {
9545
9970
  priceCents: billingTerms ? tier.priceCents : quote.lineItems.dueTodayCents,
9546
9971
  ...billingTerms ? { billingTerms } : {},
9547
9972
  ...membershipOffer ? { membershipOffer } : {},
9973
+ ..."projectionRevision" in checkout2 ? { projectionRevision: checkout2.projectionRevision } : {},
9548
9974
  ...ctx.requirePaymentQuote || ctx.membershipAuthority || body.quoteDigest ? { paymentQuote: quote } : {},
9549
9975
  currency: tier.currency ?? String(group.currency ?? "usd"),
9550
9976
  interval: tier.interval ?? (group.interval === "month" ? "month" : "year"),
@@ -9566,9 +9992,10 @@ async function startSubscription(req, env, ctx) {
9566
9992
  offerId: membershipOffer.id,
9567
9993
  offerDigest: membershipOffer.digest,
9568
9994
  quoteDigest: quote.digest,
9569
- priceId: String(priceId)
9995
+ priceId: String(priceId),
9996
+ ...quote.namedSeat ? { namedSeat: { ...quote.namedSeat, purchaserEmail: normalizeChapterEmail(String(app.email ?? "")) } } : {}
9570
9997
  };
9571
- await verifyMembershipCheckoutBinding(await ctx.membershipAuthority.bindCheckout(binding, env), binding);
9998
+ return verifyMembershipCheckoutBinding(await ctx.membershipAuthority.bindCheckout(binding, env), binding);
9572
9999
  } : void 0);
9573
10000
  } catch (err) {
9574
10001
  const code = paymentErrorCode(err);
@@ -9577,6 +10004,58 @@ async function startSubscription(req, env, ctx) {
9577
10004
  return json({ clientSecret: result.clientSecret, publishableKey: group.stripePublishableKey ?? null, lineItems: quote.lineItems });
9578
10005
  }
9579
10006
 
10007
+ // src/worker-payment-receipt.ts
10008
+ var json2 = (value, status2 = 200) => Response.json(value, { status: status2, headers: { "cache-control": "no-store", "referrer-policy": "no-referrer" } });
10009
+ var escape = (text8) => text8.replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[char]);
10010
+ async function paymentReceiptRoute(req, url, env, ctx) {
10011
+ const id7 = url.searchParams.get("application");
10012
+ if (!id7 || id7.length > 160) return json2({ error: "application required" }, 400);
10013
+ if (req.method === "POST" && req.headers.has("origin") && req.headers.get("origin") !== url.origin) return json2({ error: "forbidden" }, 403);
10014
+ const db = ctx.makeDb(env);
10015
+ const rows5 = await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } }, subscriptionCheckoutIntents: { $: { where: { id: id7 }, limit: 1 } } });
10016
+ const app = rows5.applications?.[0], intent = rows5.subscriptionCheckoutIntents?.[0];
10017
+ if (!app || app.groupId !== ctx.chapter.id || !intent || intent.applicationId !== id7 || intent.appId !== env.ODLA_APP_ID || intent.chapterId !== ctx.chapter.id || intent.environment !== env.ODLA_ENV || intent.runtime !== stripeRuntimeIdentity(env.ODLA_RUNTIME) || app.stripeRuntime !== intent.runtime || app.stripeSubscriptionId !== intent.providerSubscriptionId) return json2({ error: "receipt not found" }, 404);
10018
+ const receipt = intent.paymentReceipt;
10019
+ if (!receipt) return json2({ pending: true }, 202);
10020
+ const text8 = paymentReceiptText(receipt, ctx.chapter.name);
10021
+ if (req.method === "POST") {
10022
+ if (!env.SEND_EMAIL || !env.EMAIL_FROM) return json2({ error: "Email is unavailable. You can print or save your receipt." }, 503);
10023
+ const group = (await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups?.[0];
10024
+ if (!group) return json2({ error: "Receipt email is unavailable." }, 503);
10025
+ const config = emailGroupFrom(group);
10026
+ const debugEmail = (env.ODLA_DEBUG_EMAIL ?? config.debugEmail)?.trim();
10027
+ if (env.ODLA_ENV !== "prod" && (!debugEmail || !/^[^\s@,;<>]+@[^\s@,;<>]+\.[^\s@,;<>]+$/.test(debugEmail))) {
10028
+ return json2({ error: "A test inbox is required to email receipts in this environment." }, 503);
10029
+ }
10030
+ const result = await sendTemplated(lifecycleNotifyDeps(db, env), {
10031
+ group: { ...config, emailTemplates: {
10032
+ ...config.emailTemplates,
10033
+ paymentReceipt: { enabled: true, subject: "Your {{chapterName}} payment receipt", text: "{{receiptText}}" }
10034
+ } },
10035
+ template: "paymentReceipt",
10036
+ to: String(intent.purchaserEmailNormalized),
10037
+ applicationId: id7,
10038
+ vars: { chapterName: ctx.chapter.name, receiptText: text8 },
10039
+ dedupeKey: `requested-payment-receipt:${receipt.invoiceId}`,
10040
+ event: { label: "Payment receipt", trigger: "Purchaser requests their receipt", audience: "Purchaser", variables: ["chapterName", "receiptText"] }
10041
+ });
10042
+ return result.sent ? json2({ sent: true, preview: env.ODLA_ENV !== "prod" }) : json2({ error: "We could not confirm email delivery. You can print or save your receipt." }, 503);
10043
+ }
10044
+ if (url.searchParams.get("format") !== "html") return json2({ receipt, chapterName: ctx.chapter.name });
10045
+ const nonce = crypto.randomUUID();
10046
+ return new Response(`<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Payment receipt</title>
10047
+ <style>body{font:16px/1.6 system-ui;max-width:680px;margin:48px auto;padding:24px;color:#17252d}pre{font:inherit;white-space:pre-wrap}button{font:inherit;padding:10px 16px}@media print{button{display:none}body{margin:0}}</style>
10048
+ <h1>Payment receipt</h1><pre>${escape(text8)}</pre><button id="print">Print / save PDF</button>
10049
+ <script nonce="${nonce}">document.getElementById('print').addEventListener('click',()=>window.print())</script></html>`, {
10050
+ headers: {
10051
+ "content-type": "text/html; charset=utf-8",
10052
+ "cache-control": "no-store",
10053
+ "referrer-policy": "no-referrer",
10054
+ "content-security-policy": `default-src 'none'; style-src 'unsafe-inline'; script-src 'nonce-${nonce}'; frame-ancestors 'none'; base-uri 'none'`
10055
+ }
10056
+ });
10057
+ }
10058
+
9580
10059
  // src/worker-routes-payments.ts
9581
10060
  async function ingestWebhook(req, env, ctx) {
9582
10061
  const db = ctx.makeDb(env);
@@ -9589,8 +10068,9 @@ async function ingestWebhook(req, env, ctx) {
9589
10068
  }
9590
10069
  var handlePayments = async (req, url, env, ctx) => {
9591
10070
  if (ctx.chapter.mode !== "chapter") return null;
10071
+ if (["GET", "POST"].includes(req.method) && url.pathname === "/api/payments/receipt") return paymentReceiptRoute(req, url, env, ctx);
9592
10072
  if (req.method === "GET" && url.pathname === "/api/payments/quote") return paymentQuoteRoute(url, env, ctx);
9593
- if (req.method === "POST" && url.pathname === "/api/payments/quote") return paymentQuoteRoute(url, env, ctx, true);
10073
+ if (req.method === "POST" && url.pathname === "/api/payments/quote") return paymentQuoteRoute(url, env, ctx, true, req);
9594
10074
  if (req.method === "POST" && url.pathname === "/api/payments/subscription") return startSubscription(req, env, ctx);
9595
10075
  if (req.method === "POST" && url.pathname === "/api/webhooks/stripe") return ingestWebhook(req, env, ctx);
9596
10076
  return null;
@@ -9604,16 +10084,16 @@ var userFor = async (req, env, ctx) => {
9604
10084
  const user = await ctx.verifyUser(req, env);
9605
10085
  return user?.email ? user : null;
9606
10086
  };
9607
- var loadClaim = async (db, id6) => first4((await db.query({ giftClaims: { $: { where: { id: id6 }, limit: 1 } } })).giftClaims);
10087
+ var loadClaim = async (db, id7) => first4((await db.query({ giftClaims: { $: { where: { id: id7 }, limit: 1 } } })).giftClaims);
9608
10088
  async function consumeRateLimit(db, chapterId, requester, now) {
9609
10089
  const bucket = await giftRequesterBucket(chapterId, requester);
9610
- const id6 = `gift-rate:${chapterId}:${bucket}`;
9611
- const current = first4((await db.query({ giftRedemptionAttempts: { $: { where: { id: id6 }, limit: 1 } } })).giftRedemptionAttempts);
10090
+ const id7 = `gift-rate:${chapterId}:${bucket}`;
10091
+ const current = first4((await db.query({ giftRedemptionAttempts: { $: { where: { id: id7 }, limit: 1 } } })).giftRedemptionAttempts);
9612
10092
  const within = Boolean(current && now - Number(current.windowStartedAt) < 15 * 6e4);
9613
10093
  const attemptCount = within ? Number(current?.attemptCount ?? 0) + 1 : 1;
9614
10094
  if (attemptCount > 10) return false;
9615
- await db.transact([{ t: "update", ns: "giftRedemptionAttempts", id: id6, attrs: {
9616
- id: id6,
10095
+ await db.transact([{ t: "update", ns: "giftRedemptionAttempts", id: id7, attrs: {
10096
+ id: id7,
9617
10097
  chapterId,
9618
10098
  bucket,
9619
10099
  attemptCount,
@@ -9787,14 +10267,14 @@ var at = (value) => Number.isSafeInteger(value) ? Number(value) : void 0;
9787
10267
  async function readNamedSeatOffer(db, input, now) {
9788
10268
  membershipClock(now);
9789
10269
  await loadMembershipAuthorityState(db, input);
9790
- const id6 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id6);
10270
+ const id7 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id7);
9791
10271
  requireMembershipScope(member, input);
9792
10272
  const offering = membershipOfferingVersion(member.offeringSnapshot);
9793
10273
  const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
9794
10274
  if (seat) requireMembershipScope(seat, input);
9795
10275
  const purchaseEndsAt = seatPurchaseDeadline(offering, member);
9796
10276
  const offer = {
9797
- membershipId: id6,
10277
+ membershipId: id7,
9798
10278
  amountCents: offering.seatPriceCents,
9799
10279
  currency: offering.currency,
9800
10280
  interval: offering.interval,
@@ -10163,18 +10643,18 @@ var handleEmailDeliveryResolution = async (req, url, env, ctx) => {
10163
10643
  if (!user) return json({ error: "unauthorized" }, 401);
10164
10644
  if (!await ctx.isAdmin(db, user)) return json({ error: "forbidden" }, 403);
10165
10645
  try {
10166
- const id6 = decodeURIComponent(match[1]);
10646
+ const id7 = decodeURIComponent(match[1]);
10167
10647
  const body = await namedSeatRequest(req);
10168
10648
  if (Object.keys(body).some((key3) => !["expectedAttempt", "expectedState", "decision", "reason", "messageId"].includes(key3)) || body.messageId !== void 0 && (typeof body.messageId !== "string" || body.messageId.length > 255 || /[\r\n]/.test(body.messageId))) {
10169
10649
  return json({ error: "invalid delivery evidence" }, 400);
10170
10650
  }
10171
- const chapterDb = db, row2 = await membershipLoad(chapterDb, "emailLog", id6);
10651
+ const chapterDb = db, row2 = await membershipLoad(chapterDb, "emailLog", id7);
10172
10652
  if (!row2 || row2.groupId !== ctx.chapter.id) return json({ error: "not found" }, 404);
10173
10653
  if (row2.applicationId) {
10174
10654
  const app = await membershipLoad(chapterDb, "applications", String(row2.applicationId));
10175
10655
  if (!app || !chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME)) return json({ error: "not found" }, 404);
10176
10656
  }
10177
- await reconcileLifecycleDelivery(chapterDb, { ...body, id: id6, groupId: ctx.chapter.id, actorId: user.userId }, Date.now());
10657
+ await reconcileLifecycleDelivery(chapterDb, { ...body, id: id7, groupId: ctx.chapter.id, actorId: user.userId }, Date.now());
10178
10658
  return json({ ok: true, sent: false });
10179
10659
  } catch {
10180
10660
  return json({ error: "delivery evidence is invalid, stale, or still in progress; reload the exact attempt" }, 409);
@@ -10277,13 +10757,13 @@ var handlePeopleProfile = async (req, url, env, ctx) => {
10277
10757
  const user = await ctx.verifyUser(req, env);
10278
10758
  if (!user) return json({ error: "unauthorized" }, 401);
10279
10759
  if (!await ctx.isAdmin(db, user)) return json({ error: "forbidden" }, 403);
10280
- let id6;
10760
+ let id7;
10281
10761
  try {
10282
- id6 = dedicated ? url.searchParams.get("recordId") ?? "" : decodeURIComponent(match[1]);
10762
+ id7 = dedicated ? url.searchParams.get("recordId") ?? "" : decodeURIComponent(match[1]);
10283
10763
  } catch {
10284
10764
  return json({ error: "invalid record id" }, 422);
10285
10765
  }
10286
- const record4 = await (0, import_crm13.getRecord)({ crm: ctx.chapter.crm, db }, id6);
10766
+ const record4 = await (0, import_crm13.getRecord)({ crm: ctx.chapter.crm, db }, id7);
10287
10767
  if (!record4) return json({ error: "not found" }, 404);
10288
10768
  if (record4.type !== "person") return dedicated ? json({ error: "person required" }, 422) : null;
10289
10769
  if (match?.[2]) {
@@ -10298,9 +10778,9 @@ var handlePeopleProfile = async (req, url, env, ctx) => {
10298
10778
  }
10299
10779
  if (req.method === "GET" && dedicated) {
10300
10780
  const excluded = profileReadOnlyFields(record4, ctx);
10301
- const fields = Object.entries(ctx.chapter.crm.type(record4.type).fields).filter(([id7]) => !excluded.includes(id7)).map(([id7, field]) => ({
10302
- id: id7,
10303
- label: field.label ?? id7,
10781
+ const fields = Object.entries(ctx.chapter.crm.type(record4.type).fields).filter(([id8]) => !excluded.includes(id8)).map(([id8, field]) => ({
10782
+ id: id8,
10783
+ label: field.label ?? id8,
10304
10784
  type: field.type,
10305
10785
  required: field.required ?? false,
10306
10786
  ...field.options ? { options: field.options } : {}
@@ -11071,12 +11551,12 @@ var readJson = async (req) => {
11071
11551
  return null;
11072
11552
  }
11073
11553
  };
11074
- async function loadApp(db, id6) {
11075
- const { applications } = await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } });
11554
+ async function loadApp(db, id7) {
11555
+ const { applications } = await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } });
11076
11556
  return applications?.[0] ?? null;
11077
11557
  }
11078
- async function loadGroup(db, id6) {
11079
- const { groups } = await db.query({ groups: { $: { where: { id: id6 }, limit: 1 } } });
11558
+ async function loadGroup(db, id7) {
11559
+ const { groups } = await db.query({ groups: { $: { where: { id: id7 }, limit: 1 } } });
11080
11560
  return groups?.[0] ?? null;
11081
11561
  }
11082
11562
  var handleAdminApplicationActions = async (req, url, env, ctx) => {
@@ -11162,8 +11642,8 @@ var handleAdminApprove = async (req, url, env, ctx) => {
11162
11642
  const got = await gate4(req, env, ctx);
11163
11643
  if (got instanceof Response) return got;
11164
11644
  const db = got;
11165
- const id6 = m[1];
11166
- const app = await loadApp(db, id6);
11645
+ const id7 = m[1];
11646
+ const app = await loadApp(db, id7);
11167
11647
  if (!app) return json({ error: "not found" }, 404);
11168
11648
  const result = await approveApplication({ db, env, ctx, app, origin: url.origin, reason: "manual" });
11169
11649
  if (!result.ok) return json({ error: result.error }, result.status);
@@ -11192,10 +11672,10 @@ var handleAdminApplicationPatch = async (req, url, env, ctx) => {
11192
11672
  const got = await gate4(req, env, ctx);
11193
11673
  if (got instanceof Response) return got;
11194
11674
  const db = got;
11195
- const id6 = m[1];
11675
+ const id7 = m[1];
11196
11676
  const body = await readJson(req);
11197
11677
  if (!body) return json({ error: "invalid JSON body" }, 400);
11198
- const app = await loadApp(db, id6);
11678
+ const app = await loadApp(db, id7);
11199
11679
  if (!app) return json({ error: "not found" }, 404);
11200
11680
  return patchApplication(db, env, ctx, app, body);
11201
11681
  };
@@ -11305,10 +11785,10 @@ async function updateEmailSettings(db, ctx, env, body) {
11305
11785
  const missing = Object.fromEntries(Object.keys(events).filter((key3) => !group?.emailTemplates?.[key3]).map((key3) => [key3, { subject: "", text: "", enabled: false }]));
11306
11786
  await (0, import_crm16.saveConfig)({ db, crm: ctx.chapter.crm }, { emailTemplates: { ...missing, ...clean }, notificationEmail, replyTo, debugEmail });
11307
11787
  } else {
11308
- const id6 = { ns: "groups", attr: "id", value: ctx.chapter.id };
11788
+ const id7 = { ns: "groups", attr: "id", value: ctx.chapter.id };
11309
11789
  await db.transact([
11310
- { t: "merge", ns: "groups", id: id6, attrs: { emailTemplates: clean } },
11311
- { t: "update", ns: "groups", id: id6, attrs: { commitmentText, normsText, notificationEmail, replyTo, debugEmail, fromName } }
11790
+ { t: "merge", ns: "groups", id: id7, attrs: { emailTemplates: clean } },
11791
+ { t: "update", ns: "groups", id: id7, attrs: { commitmentText, normsText, notificationEmail, replyTo, debugEmail, fromName } }
11312
11792
  ]);
11313
11793
  }
11314
11794
  return json({ ok: true });
@@ -11323,8 +11803,8 @@ async function gate5(req, env, ctx) {
11323
11803
  if (!await ctx.isAdmin(rawDb, u)) return json({ error: "forbidden" }, 403);
11324
11804
  return rawDb;
11325
11805
  }
11326
- async function loadGroup2(db, id6) {
11327
- const { groups } = await db.query({ groups: { $: { where: { id: id6 }, limit: 1 } } });
11806
+ async function loadGroup2(db, id7) {
11807
+ const { groups } = await db.query({ groups: { $: { where: { id: id7 }, limit: 1 } } });
11328
11808
  return groups?.[0] ?? null;
11329
11809
  }
11330
11810
  var str3 = (v, d = "") => typeof v === "string" ? v : d;
@@ -11578,7 +12058,7 @@ var handleAdminNetworkTargets = async (req, url, env, ctx) => {
11578
12058
  if ("response" in got) return got.response;
11579
12059
  return json({
11580
12060
  targets: ctx.chapter.network.targets.map(({
11581
- id: id6,
12061
+ id: id7,
11582
12062
  name,
11583
12063
  url: targetUrl,
11584
12064
  fields,
@@ -11587,7 +12067,7 @@ var handleAdminNetworkTargets = async (req, url, env, ctx) => {
11587
12067
  stageTransitions,
11588
12068
  commercialParity
11589
12069
  }) => ({
11590
- id: id6,
12070
+ id: id7,
11591
12071
  name,
11592
12072
  url: targetUrl,
11593
12073
  types: Object.keys(fields ?? DEFAULT_SHARE_FIELDS),
@@ -11749,7 +12229,7 @@ var handleAdminNetworkPush = async (req, url, env, ctx) => {
11749
12229
  if (typeof body.recordId !== "string" || !Array.isArray(body.targetIds) || body.targetIds.length === 0) {
11750
12230
  return json({ error: "recordId and a non-empty targetIds array are required" }, 400);
11751
12231
  }
11752
- const requested = new Set(body.targetIds.filter((id6) => typeof id6 === "string"));
12232
+ const requested = new Set(body.targetIds.filter((id7) => typeof id7 === "string"));
11753
12233
  if (requested.size !== body.targetIds.length) return json({ error: "targetIds must contain unique strings" }, 400);
11754
12234
  const targets = ctx.chapter.network.targets.filter((target) => requested.has(target.id));
11755
12235
  if (targets.length !== requested.size) return json({ error: "one or more targetIds are not configured" }, 400);
@@ -11877,7 +12357,7 @@ async function operationalReaderRecord(record4, reader, db, env, ctx) {
11877
12357
  const appId = record4.fields.applicationId;
11878
12358
  const app = typeof appId === "string" ? (await db.query({ applications: { $: { where: { id: appId }, limit: 1 } } })).applications?.[0] : void 0;
11879
12359
  if (reader.stageTransitions.includes(record4.type)) {
11880
- result.edit.allowedStages = app ? applicationActionStages(app, env, ctx).map((id6) => ({ id: id6, label: id6.replaceAll("_", " ") })) : result.edit.allowedStages.filter((stage) => !isApplicationStage(ctx, stage.id));
12360
+ result.edit.allowedStages = app ? applicationActionStages(app, env, ctx).map((id7) => ({ id: id7, label: id7.replaceAll("_", " ") })) : result.edit.allowedStages.filter((stage) => !isApplicationStage(ctx, stage.id));
11881
12361
  const lock = (await db.query({ crm_activity: { $: { where: { id: `network-action-lock:${record4.id}` }, limit: 1 } } })).crm_activity?.[0];
11882
12362
  const meta = lock?.meta;
11883
12363
  if (meta?.state === "pending" && typeof meta.activityId === "string") {
@@ -11982,9 +12462,9 @@ async function listReaderRecords(db, type, allowed2, emailField, window) {
11982
12462
  // src/network-mutation-receipt.ts
11983
12463
  var import_crm22 = require("@odla-ai/crm");
11984
12464
  var NETWORK_MUTATION_META = "chapterNetworkMutation";
11985
- async function priorMutation(db, id6) {
12465
+ async function priorMutation(db, id7) {
11986
12466
  const result = await db.query({
11987
- [import_crm22.CRM_NS.activity]: { $: { where: { id: id6 }, limit: 1 } }
12467
+ [import_crm22.CRM_NS.activity]: { $: { where: { id: id7 }, limit: 1 } }
11988
12468
  });
11989
12469
  const row2 = (result[import_crm22.CRM_NS.activity] ?? [])[0];
11990
12470
  const meta = row2?.meta;
@@ -12244,8 +12724,8 @@ async function gate7(req, env, ctx) {
12244
12724
  if (!await networkWriteAllowed(req, db, user, ctx)) return { response: json({ error: "super-admin required", code: "super_admin_required" }, 403) };
12245
12725
  return { db, user };
12246
12726
  }
12247
- function targetFor2(ctx, id6) {
12248
- return ctx.chapter.network.targets.find((target) => target.id === id6);
12727
+ function targetFor2(ctx, id7) {
12728
+ return ctx.chapter.network.targets.find((target) => target.id === id7);
12249
12729
  }
12250
12730
  async function readTargetRecords(db, env, ctx, target, query) {
12251
12731
  const secret = await getVaultSecret(db, target.secretName);
@@ -12380,7 +12860,7 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
12380
12860
  if (ctx.chapter.network.requireSuperAdminWrites && !await ctx.isSuperAdminEmail(got.db, got.user.email, got.user.userId)) {
12381
12861
  return json({ ...result.page, records: result.page.records.map(({ edit: _edit, ...record4 }) => ({
12382
12862
  ...record4,
12383
- fields: Object.fromEntries(Object.entries(record4.fields).filter(([id6]) => id6 === "name" || id6 === "email"))
12863
+ fields: Object.fromEntries(Object.entries(record4.fields).filter(([id7]) => id7 === "name" || id7 === "email"))
12384
12864
  })) });
12385
12865
  }
12386
12866
  return json(result.page);
@@ -12450,9 +12930,9 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
12450
12930
  }
12451
12931
  const body = typeof input.body === "string" ? input.body.trim() : "";
12452
12932
  if (!body || body.length > MAX_NOTE3) return json({ error: `note must be 1\u2013${MAX_NOTE3} characters` }, 400);
12453
- const id6 = crypto.randomUUID();
12933
+ const id7 = crypto.randomUUID();
12454
12934
  const note = {
12455
- id: id6,
12935
+ id: id7,
12456
12936
  targetId: identity2.targetId,
12457
12937
  recordType: identity2.recordType,
12458
12938
  recordId: identity2.recordId,
@@ -12464,7 +12944,7 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
12464
12944
  await got.db.transact([{
12465
12945
  t: "update",
12466
12946
  ns: "networkNotes",
12467
- id: id6,
12947
+ id: id7,
12468
12948
  attrs: { ...note, recordKey: identity2.recordKey }
12469
12949
  }]);
12470
12950
  return json({ note }, 201);
@@ -12564,13 +13044,13 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
12564
13044
  return json({ error: `body must be 1\u2013${MAX_NOTE4} characters and mutationId must be valid` }, 400);
12565
13045
  }
12566
13046
  const createdAt = Date.now();
12567
- const id6 = `network:${verified.sender}:${mutationId}`;
13047
+ const id7 = `network:${verified.sender}:${mutationId}`;
12568
13048
  const result = await (0, import_crm24.addActivity)(
12569
13049
  {
12570
13050
  crm: ctx.chapter.crm,
12571
13051
  db,
12572
13052
  now: () => createdAt,
12573
- newId: () => id6
13053
+ newId: () => id7
12574
13054
  },
12575
13055
  {
12576
13056
  recordId,
@@ -12585,11 +13065,11 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
12585
13065
  body
12586
13066
  }
12587
13067
  },
12588
- mutationId: id6
13068
+ mutationId: id7
12589
13069
  }
12590
13070
  );
12591
13071
  const note = {
12592
- id: id6,
13072
+ id: id7,
12593
13073
  sourceId: verified.sender,
12594
13074
  recordType,
12595
13075
  recordId,
@@ -12598,8 +13078,8 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
12598
13078
  };
12599
13079
  return json({ version: 1, site: { id: ctx.chapter.id, name: ctx.chapter.name }, note, duplicate: result.duplicate }, 201);
12600
13080
  };
12601
- function targetFor3(ctx, id6) {
12602
- return ctx.chapter.network.targets.find((target) => target.id === id6);
13081
+ function targetFor3(ctx, id7) {
13082
+ return ctx.chapter.network.targets.find((target) => target.id === id7);
12603
13083
  }
12604
13084
  var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
12605
13085
  if (url.pathname !== "/api/admin/network/shared-notes" || req.method !== "GET" && req.method !== "POST") return null;
@@ -12868,10 +13348,10 @@ async function linkNetworkChapterPerson(db, input, mutationId, actor, now = Date
12868
13348
  if (!state.claims.some((claim) => claim.id === expectedClaim.id && claim.personId === person.id && claim.kind === "email")) {
12869
13349
  throw new NetworkPersonRegistryError("identity_mismatch", "source email is not an exact verified identity on this Person");
12870
13350
  }
12871
- const id6 = await networkChapterLinkId(person.id, input.chapterId, input.chapterPersonId);
12872
- const previous = state.links.find((link2) => link2.id === id6);
13351
+ const id7 = await networkChapterLinkId(person.id, input.chapterId, input.chapterPersonId);
13352
+ const previous = state.links.find((link2) => link2.id === id7);
12873
13353
  const link = {
12874
- id: id6,
13354
+ id: id7,
12875
13355
  personId: person.id,
12876
13356
  chapterId: input.chapterId,
12877
13357
  chapterPersonId: input.chapterPersonId,
@@ -12882,11 +13362,11 @@ async function linkNetworkChapterPerson(db, input, mutationId, actor, now = Date
12882
13362
  const nextPerson = { ...person, revision: person.revision + 1, updatedAt: now };
12883
13363
  await db.transact([
12884
13364
  { t: "update", ns: "networkPersons", id: person.id, attrs: { ...nextPerson } },
12885
- { t: "update", ns: "networkChapterPersonLinks", id: id6, attrs: { ...link } },
13365
+ { t: "update", ns: "networkChapterPersonLinks", id: id7, attrs: { ...link } },
12886
13366
  networkPersonEventOp("chapter_source_linked", mutationId, actor, now, { chapterId: input.chapterId }, { personId: person.id })
12887
13367
  ], { mutationId, guards: [
12888
13368
  { ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision, state: "active" } },
12889
- { ns: "networkChapterPersonLinks", id: id6, exists: Boolean(previous), ...previous ? { equals: { state: "revoked" } } : {} }
13369
+ { ns: "networkChapterPersonLinks", id: id7, exists: Boolean(previous), ...previous ? { equals: { state: "revoked" } } : {} }
12890
13370
  ] });
12891
13371
  return { person: nextPerson, link };
12892
13372
  }
@@ -12923,9 +13403,9 @@ var defaultPolicy = (now, consent) => ({
12923
13403
  var isGuardConflict = (error) => error instanceof Error && error.code === "transact_guard_failed";
12924
13404
  async function linkOps(personId, links, now) {
12925
13405
  return Promise.all(links.map(async (link) => {
12926
- const id6 = await networkChapterLinkId(personId, link.chapterId, link.chapterPersonId);
13406
+ const id7 = await networkChapterLinkId(personId, link.chapterId, link.chapterPersonId);
12927
13407
  const sourceKey = await networkChapterPersonSourceKey(link.chapterId, link.chapterPersonId);
12928
- return { t: "update", ns: "networkChapterPersonLinks", id: id6, attrs: { id: id6, personId, chapterId: link.chapterId, chapterPersonId: link.chapterPersonId, sourceKey, state: "shadow", createdAt: now } };
13408
+ return { t: "update", ns: "networkChapterPersonLinks", id: id7, attrs: { id: id7, personId, chapterId: link.chapterId, chapterPersonId: link.chapterPersonId, sourceKey, state: "shadow", createdAt: now } };
12929
13409
  }));
12930
13410
  }
12931
13411
  async function ambiguousAlias(db, input, aliasId, claims, candidates, mutationId, actor, now) {
@@ -13144,10 +13624,10 @@ async function networkPersonBulkBatchDigest(rows5) {
13144
13624
  }
13145
13625
  async function networkPersonRegistryStateDigest(state) {
13146
13626
  const snapshot = {
13147
- persons: state.persons.map(({ id: id6, revision, state: personState, mergedIntoId }) => ({ id: id6, revision, state: personState, mergedIntoId })).sort((a, b) => a.id.localeCompare(b.id)),
13148
- claims: state.claims.map(({ id: id6, personId }) => ({ id: id6, personId })).sort((a, b) => a.id.localeCompare(b.id)),
13149
- aliases: state.aliases.map(({ id: id6, revision, status: status2, personId }) => ({ id: id6, revision, status: status2, personId })).sort((a, b) => a.id.localeCompare(b.id)),
13150
- links: state.links.map(({ id: id6, personId, state: linkState }) => ({ id: id6, personId, state: linkState })).sort((a, b) => a.id.localeCompare(b.id))
13627
+ persons: state.persons.map(({ id: id7, revision, state: personState, mergedIntoId }) => ({ id: id7, revision, state: personState, mergedIntoId })).sort((a, b) => a.id.localeCompare(b.id)),
13628
+ claims: state.claims.map(({ id: id7, personId }) => ({ id: id7, personId })).sort((a, b) => a.id.localeCompare(b.id)),
13629
+ aliases: state.aliases.map(({ id: id7, revision, status: status2, personId }) => ({ id: id7, revision, status: status2, personId })).sort((a, b) => a.id.localeCompare(b.id)),
13630
+ links: state.links.map(({ id: id7, personId, state: linkState }) => ({ id: id7, personId, state: linkState })).sort((a, b) => a.id.localeCompare(b.id))
13151
13631
  };
13152
13632
  return networkPersonBulkSha256(JSON.stringify(stableValue(snapshot)));
13153
13633
  }
@@ -13372,7 +13852,7 @@ var safeNetworkPersonAlias = async (alias) => ({
13372
13852
  recordedAt: alias.consent.recordedAt,
13373
13853
  hasBasis: Boolean(alias.consent.basis)
13374
13854
  },
13375
- identityClaims: alias.identityClaims.map(({ id: id6, kind, hint, verifiedAt }) => ({ id: id6, kind, hint: hint ?? null, verifiedAt })),
13855
+ identityClaims: alias.identityClaims.map(({ id: id7, kind, hint, verifiedAt }) => ({ id: id7, kind, hint: hint ?? null, verifiedAt })),
13376
13856
  chapterIds: [...new Set(alias.chapterLinks.map((link) => link.chapterId))],
13377
13857
  chapterSources: await Promise.all(alias.chapterLinks.map(async (link) => ({
13378
13858
  chapterId: link.chapterId,
@@ -13401,7 +13881,7 @@ async function projectNetworkPersonRegistryState(state) {
13401
13881
  return {
13402
13882
  ...safeNetworkPerson(person, claims),
13403
13883
  canonicalPersonId: canonicalId,
13404
- identityClaims: claims.map(({ id: id6, kind, hint, verifiedAt }) => ({ id: id6, kind, hint: hint ?? null, verifiedAt })),
13884
+ identityClaims: claims.map(({ id: id7, kind, hint, verifiedAt }) => ({ id: id7, kind, hint: hint ?? null, verifiedAt })),
13405
13885
  chapterLinks: safeLinks,
13406
13886
  chapterProjections: projectNetworkContactPolicy(canonical3.contactPolicy, safeLinks).map(({ contactPolicy, ...projection }) => ({ ...projection, contactPolicy: safeNetworkContactPolicy(contactPolicy) }))
13407
13887
  };
@@ -13656,7 +14136,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
13656
14136
  };
13657
14137
 
13658
14138
  // src/worker-routes-network-commercial.ts
13659
- var import_stripe12 = require("@odla-ai/stripe");
14139
+ var import_stripe13 = require("@odla-ai/stripe");
13660
14140
 
13661
14141
  // src/worker-routes-network-commercial-parity.ts
13662
14142
  var import_db13 = require("@odla-ai/db");
@@ -14032,7 +14512,7 @@ var handleNetworkCommercialStripeWebhook = async (req, url, env, ctx) => {
14032
14512
  return json({ error: "commercial webhook is not configured" }, 503);
14033
14513
  }
14034
14514
  try {
14035
- const event2 = await (0, import_stripe12.constructStripeEvent)(body, signature, secret);
14515
+ const event2 = await (0, import_stripe13.constructStripeEvent)(body, signature, secret);
14036
14516
  const fact = normalizeNetworkCommercialStripeEvent(event2, await stripeEventDigest(body));
14037
14517
  if (!fact.eventId || !fact.eventType || !fact.objectId) return json({ error: "Stripe event identity is invalid" }, 400);
14038
14518
  const result = await ingestNetworkCommercialFact(
@@ -14053,7 +14533,7 @@ var handleNetworkCommercialStripeWebhook = async (req, url, env, ctx) => {
14053
14533
  ...result.status === "replayed" && result.resolvedCollision ? { resolvedCollision: true } : {}
14054
14534
  });
14055
14535
  } catch (error) {
14056
- if (error instanceof import_stripe12.StripeError) return json({ error: error.message, code: error.code }, 400);
14536
+ if (error instanceof import_stripe13.StripeError) return json({ error: error.message, code: error.code }, 400);
14057
14537
  throw error;
14058
14538
  }
14059
14539
  };
@@ -14116,7 +14596,7 @@ var handleAdminNetworkCommercialReadModel = async (req, url, env, ctx) => {
14116
14596
  const secretKey = await getVaultSecret(db, "stripe_secret_key");
14117
14597
  if (!secretKey) return json({ error: "Stripe reconciliation is not configured" }, 503);
14118
14598
  try {
14119
- const stripe = (0, import_stripe12.initStripe)({ secretKey });
14599
+ const stripe = (0, import_stripe13.initStripe)({ secretKey });
14120
14600
  const [customers, subscriptions, invoices, refunds, payments] = await Promise.all([
14121
14601
  listStripeObjects(stripe, "/v1/customers"),
14122
14602
  listStripeObjects(stripe, "/v1/subscriptions", { status: "all" }),
@@ -14130,7 +14610,7 @@ var handleAdminNetworkCommercialReadModel = async (req, url, env, ctx) => {
14130
14610
  ...reconcileStripeSnapshot(projection2, { customers, subscriptions, invoices, refunds, payments }, ctx.chapter.networkCommercialReadModel.sources)
14131
14611
  });
14132
14612
  } catch (error) {
14133
- if (error instanceof import_stripe12.StripeError) return json({ error: "Stripe reconciliation failed", code: error.code }, 502);
14613
+ if (error instanceof import_stripe13.StripeError) return json({ error: "Stripe reconciliation failed", code: error.code }, 502);
14134
14614
  return json({ error: "Stripe reconciliation failed" }, 502);
14135
14615
  }
14136
14616
  }
@@ -14194,14 +14674,14 @@ var handleFormation = async (req, url, env, ctx) => {
14194
14674
  }
14195
14675
  }
14196
14676
  const db = ctx.makeDb(env);
14197
- const id6 = submissionId ? await applicationIdForSubmission(`formation:${ctx.chapter.id}:${submissionId}`) : crypto.randomUUID();
14677
+ const id7 = submissionId ? await applicationIdForSubmission(`formation:${ctx.chapter.id}:${submissionId}`) : crypto.randomUUID();
14198
14678
  try {
14199
14679
  const created = await (0, import_crm25.createRecord)(
14200
14680
  {
14201
14681
  crm: ctx.chapter.crm,
14202
14682
  db,
14203
14683
  now: () => Date.now(),
14204
- newId: () => id6
14684
+ newId: () => id7
14205
14685
  },
14206
14686
  {
14207
14687
  type: formation.type,
@@ -14210,7 +14690,7 @@ var handleFormation = async (req, url, env, ctx) => {
14210
14690
  }
14211
14691
  );
14212
14692
  return json({
14213
- id: id6,
14693
+ id: id7,
14214
14694
  duplicate: created.duplicate,
14215
14695
  status: created.record.stage ?? null
14216
14696
  }, created.duplicate ? 200 : 201);
@@ -14287,8 +14767,8 @@ function resourceValue(value, path, errors) {
14287
14767
  return null;
14288
14768
  }
14289
14769
  unknownKeys3(raw2, RESOURCE_KEYS, path, errors);
14290
- const id6 = text7(raw2.id, `${path}.id`, errors, 80) ?? "";
14291
- if (id6 && !ID5.test(id6)) errors.push(`${path}.id must be lowercase kebab-case`);
14770
+ const id7 = text7(raw2.id, `${path}.id`, errors, 80) ?? "";
14771
+ if (id7 && !ID5.test(id7)) errors.push(`${path}.id must be lowercase kebab-case`);
14292
14772
  const kind = text7(raw2.kind, `${path}.kind`, errors) ?? "";
14293
14773
  if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
14294
14774
  const title = text7(raw2.title, `${path}.title`, errors, 240) ?? "";
@@ -14345,7 +14825,7 @@ function resourceValue(value, path, errors) {
14345
14825
  updatedAt: integer4(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
14346
14826
  ...promotedFrom ? { promotedFrom } : {}
14347
14827
  };
14348
- return { id: id6, kind, title, ...summary ? { summary } : {}, tags, scope: scope2, status: status2, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance: provenance2 };
14828
+ return { id: id7, kind, title, ...summary ? { summary } : {}, tags, scope: scope2, status: status2, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance: provenance2 };
14349
14829
  }
14350
14830
  function validateBuilderLibraryDocument(value, options = {}) {
14351
14831
  const errors = [];
@@ -14376,7 +14856,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
14376
14856
  const inputs = Array.isArray(raw2.resources) ? raw2.resources : [];
14377
14857
  if (inputs.length > MAX_RESOURCES) errors.push(`document.resources may contain at most ${MAX_RESOURCES} items`);
14378
14858
  const resources = inputs.map((item, index2) => resourceValue(item, `document.resources[${index2}]`, errors)).filter(Boolean);
14379
- const ids2 = resources.map(({ id: id6 }) => id6);
14859
+ const ids2 = resources.map(({ id: id7 }) => id7);
14380
14860
  if (new Set(ids2).size !== ids2.length) errors.push("document.resources ids must be unique");
14381
14861
  for (const resource of resources) {
14382
14862
  if (sourceType === "chapter" && (resource.scope.kind !== "chapter" || resource.scope.chapterId !== sourceAppId)) {
@@ -14465,18 +14945,18 @@ async function loadLatestBuilderLibraryControl(db, sourceId, targetId, environme
14465
14945
  return control2(Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
14466
14946
  }
14467
14947
  async function loadLocalBuilderLibrary(db, appId, environment) {
14468
- const id6 = `builder-library-local:${environment}`;
14469
- const rows5 = (await db.query({ builderLibraryLocal: { $: { where: { id: id6 }, limit: 1 } } })).builderLibraryLocal;
14948
+ const id7 = `builder-library-local:${environment}`;
14949
+ const rows5 = (await db.query({ builderLibraryLocal: { $: { where: { id: id7 }, limit: 1 } } })).builderLibraryLocal;
14470
14950
  return local3(Array.isArray(rows5) ? rows5[0] : void 0);
14471
14951
  }
14472
14952
  async function materializeBuilderLibraryControl(db, envelope, previous, receivedAt) {
14473
14953
  const { document, digest } = envelope;
14474
14954
  const target = document.target;
14475
- const id6 = `builder-library-control:${document.source.appId}:${target.environment}:${document.revision}`;
14955
+ const id7 = `builder-library-control:${document.source.appId}:${target.environment}:${document.revision}`;
14476
14956
  const ops = [];
14477
14957
  if (previous?.active) ops.push({ t: "update", ns: "builderLibraryControls", id: previous.id, attrs: { active: false } });
14478
- ops.push({ t: "update", ns: "builderLibraryControls", id: id6, attrs: {
14479
- id: id6,
14958
+ ops.push({ t: "update", ns: "builderLibraryControls", id: id7, attrs: {
14959
+ id: id7,
14480
14960
  sourceId: document.source.appId,
14481
14961
  targetId: target.appId,
14482
14962
  environment: target.environment,
@@ -14489,13 +14969,13 @@ async function materializeBuilderLibraryControl(db, envelope, previous, received
14489
14969
  await db.transact(ops, { mutationId: `builder-library-control:${document.source.appId}:${target.environment}:${document.revision}:${digest}` });
14490
14970
  }
14491
14971
  async function saveLocalBuilderLibrary(db, document, environment, expectedRevision, updatedAt, updatedBy) {
14492
- const id6 = `builder-library-local:${environment}`;
14972
+ const id7 = `builder-library-local:${environment}`;
14493
14973
  const current = await loadLocalBuilderLibrary(db, document.source.appId, environment);
14494
14974
  if ((current?.revision ?? 0) !== expectedRevision) throw new Error("builder_library_revision_conflict");
14495
- const row2 = { id: id6, appId: document.source.appId, environment, revision: document.revision, document, updatedAt, ...updatedBy ? { updatedBy } : {} };
14496
- await db.transact([{ t: "update", ns: "builderLibraryLocal", id: id6, attrs: row2 }], {
14975
+ const row2 = { id: id7, appId: document.source.appId, environment, revision: document.revision, document, updatedAt, ...updatedBy ? { updatedBy } : {} };
14976
+ await db.transact([{ t: "update", ns: "builderLibraryLocal", id: id7, attrs: row2 }], {
14497
14977
  mutationId: `builder-library-local:${environment}:${document.revision}`,
14498
- guards: current ? [{ ns: "builderLibraryLocal", id: id6, exists: true, equals: { revision: expectedRevision } }] : [{ ns: "builderLibraryLocal", id: id6, exists: false }]
14978
+ guards: current ? [{ ns: "builderLibraryLocal", id: id7, exists: true, equals: { revision: expectedRevision } }] : [{ ns: "builderLibraryLocal", id: id7, exists: false }]
14499
14979
  });
14500
14980
  return row2;
14501
14981
  }
@@ -14710,30 +15190,30 @@ var handleAdminBuilderLibrary = async (req, url, env, ctx) => {
14710
15190
  };
14711
15191
 
14712
15192
  // src/membership-cancel-authority.ts
14713
- var import_stripe13 = require("@odla-ai/stripe");
15193
+ var import_stripe14 = require("@odla-ai/stripe");
14714
15194
  async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
14715
15195
  membershipClock(now);
14716
- const id6 = await networkMembershipId(target);
15196
+ const id7 = await networkMembershipId(target);
14717
15197
  const state = await loadMembershipAuthorityState(db, target);
14718
- const member = await membershipLoad(db, "networkMembership", id6);
15198
+ const member = await membershipLoad(db, "networkMembership", id7);
14719
15199
  requireMembershipScope(member, target);
14720
15200
  if (!["active", "cancel_at_term_end", "payment_grace", "paid_pending_approval", "lapsed"].includes(String(member.status))) {
14721
15201
  membershipFail("membership_cancellation_unavailable");
14722
15202
  }
14723
15203
  const path = `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`;
14724
- const read2 = await (0, import_stripe13.stripeRequest)({ secretKey: stripeSecret }, "GET", path);
15204
+ const read2 = await (0, import_stripe14.stripeRequest)({ secretKey: stripeSecret }, "GET", path);
14725
15205
  const sub = read2.body;
14726
15206
  if (!read2.ok || sub.id !== member.providerSubscriptionId || sub.customer !== member.providerCustomerId || sub.livemode !== (target.environment === "prod") || !Object.entries({
14727
15207
  ...stripePartitionMetadata(target),
14728
15208
  applicationId: target.applicationId
14729
15209
  }).every(([key3, value]) => sub.metadata?.[key3] === value)) membershipFail("membership_cancellation_provider_mismatch");
14730
15210
  if (sub.status !== "canceled" && sub.cancel_at_period_end !== true) {
14731
- const write = await (0, import_stripe13.stripeRequest)(
15211
+ const write = await (0, import_stripe14.stripeRequest)(
14732
15212
  { secretKey: stripeSecret },
14733
15213
  "POST",
14734
15214
  path,
14735
15215
  { cancel_at_period_end: true, proration_behavior: "none" },
14736
- { idempotencyKey: `membership-cancel-at-term:${id6}:${member.providerSubscriptionId}` }
15216
+ { idempotencyKey: `membership-cancel-at-term:${id7}:${member.providerSubscriptionId}` }
14737
15217
  );
14738
15218
  if (!write.ok || write.body.id !== member.providerSubscriptionId || write.body.cancel_at_period_end !== true) {
14739
15219
  membershipFail("membership_cancellation_pending");
@@ -14741,7 +15221,7 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
14741
15221
  }
14742
15222
  const status2 = Number(member.termEndsAt) <= now ? "lapsed" : member.journeyApprovedAt ? "cancel_at_term_end" : "paid_pending_approval";
14743
15223
  if (member.status !== status2 || member.autoRenew !== false || Number(member.graceEndsAt ?? 0) !== 0) {
14744
- await db.transact([membershipUpdate("networkMembership", id6, {
15224
+ await db.transact([membershipUpdate("networkMembership", id7, {
14745
15225
  status: status2,
14746
15226
  autoRenew: false,
14747
15227
  graceEndsAt: 0,
@@ -14750,12 +15230,12 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
14750
15230
  updatedAt: now
14751
15231
  })], {
14752
15232
  guards: [
14753
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
15233
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
14754
15234
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
14755
15235
  ]
14756
15236
  });
14757
15237
  }
14758
- return { membershipId: id6, status: status2, termEndsAt: Number(member.termEndsAt), graceEndsAt: 0, autoRenew: false };
15238
+ return { membershipId: id7, status: status2, termEndsAt: Number(member.termEndsAt), graceEndsAt: 0, autoRenew: false };
14759
15239
  }
14760
15240
 
14761
15241
  // src/membership-authority-handler.ts
@@ -14794,17 +15274,17 @@ async function namedSeatPrimaryActivation(db, member, now) {
14794
15274
  // src/membership-approval-authority.ts
14795
15275
  async function approveNetworkMembership(db, input, now) {
14796
15276
  membershipClock(now);
14797
- const id6 = await networkMembershipId(input), entitlementId = `${id6}:primary`;
15277
+ const id7 = await networkMembershipId(input), entitlementId = `${id7}:primary`;
14798
15278
  for (let attempt = 0; attempt < 8; attempt++) {
14799
15279
  const state = await loadMembershipAuthorityState(db, input);
14800
- const member = await membershipLoad(db, "networkMembership", id6);
15280
+ const member = await membershipLoad(db, "networkMembership", id7);
14801
15281
  requireMembershipScope(member, input);
14802
15282
  const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
14803
15283
  requireMembershipScope(entitlement, input);
14804
- if (entitlement.membershipId !== id6 || entitlement.kind !== "primary") membershipFail("membership_entitlement_conflict");
15284
+ if (entitlement.membershipId !== id7 || entitlement.kind !== "primary") membershipFail("membership_entitlement_conflict");
14805
15285
  if (["active", "cancel_at_term_end"].includes(String(member.status)) && entitlement.status === "active" && Number(member.refundedCents) === 0 && Number.isSafeInteger(member.memberNumber) && Number(member.memberNumber) > 0 && Number(member.termEndsAt) > now) {
14806
15286
  return {
14807
- membershipId: id6,
15287
+ membershipId: id7,
14808
15288
  status: String(member.status),
14809
15289
  cohort: String(member.cohort),
14810
15290
  memberNumber: Number(member.memberNumber),
@@ -14824,7 +15304,7 @@ async function approveNetworkMembership(db, input, now) {
14824
15304
  const seatActivation = await namedSeatPrimaryActivation(db, member, now);
14825
15305
  try {
14826
15306
  await db.transact([
14827
- membershipUpdate("networkMembership", id6, {
15307
+ membershipUpdate("networkMembership", id7, {
14828
15308
  status: member.autoRenew ? "active" : "cancel_at_term_end",
14829
15309
  memberNumber,
14830
15310
  journeyApprovedAt: now,
@@ -14839,8 +15319,8 @@ async function approveNetworkMembership(db, input, now) {
14839
15319
  updatedAt: now
14840
15320
  }),
14841
15321
  ...seatActivation.ops
14842
- ], { mutationId: `membership-approve:${id6}`, guards: [
14843
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision, status: "paid_pending_approval" }, absent: ["memberNumber"] },
15322
+ ], { mutationId: `membership-approve:${id7}`, guards: [
15323
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision, status: "paid_pending_approval" }, absent: ["memberNumber"] },
14844
15324
  { ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision, status: "pending_approval" } },
14845
15325
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
14846
15326
  ...seatActivation.guards
@@ -14855,41 +15335,41 @@ async function approveNetworkMembership(db, input, now) {
14855
15335
  // src/membership-refund-authority.ts
14856
15336
  async function refundNetworkMembership(db, input, secretKey, now) {
14857
15337
  membershipClock(now);
14858
- const id6 = await networkMembershipId(input), entitlementId = `${id6}:primary`;
14859
- let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id6);
15338
+ const id7 = await networkMembershipId(input), entitlementId = `${id7}:primary`;
15339
+ let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id7);
14860
15340
  requireMembershipScope(member, input);
14861
- if (member.status === "refunded") return { membershipId: id6, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
15341
+ if (member.status === "refunded") return { membershipId: id7, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
14862
15342
  if (!["paid_pending_approval", "active", "cancel_at_term_end", "payment_grace", "lapsed", "refund_pending"].includes(String(member.status))) membershipFail("membership_refund_not_eligible");
14863
15343
  if (member.status !== "refund_pending") {
14864
15344
  const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
14865
15345
  requireMembershipScope(entitlement, input);
14866
15346
  await db.transact([
14867
- membershipUpdate("networkMembership", id6, {
15347
+ membershipUpdate("networkMembership", id7, {
14868
15348
  status: "refund_pending",
14869
15349
  refundRequestedAt: now,
14870
15350
  revision: Number(member.revision) + 1,
14871
15351
  updatedAt: now
14872
15352
  }),
14873
15353
  membershipUpdate("networkEntitlement", entitlementId, { status: "closed", revision: Number(entitlement.revision) + 1, updatedAt: now }),
14874
- membershipUpdate("membershipEffect", `${id6}:refund`, {
14875
- id: `${id6}:refund`,
15354
+ membershipUpdate("membershipEffect", `${id7}:refund`, {
15355
+ id: `${id7}:refund`,
14876
15356
  chapterId: input.chapterId,
14877
15357
  environment: input.environment,
14878
15358
  runtime: input.runtime,
14879
- membershipId: id6,
15359
+ membershipId: id7,
14880
15360
  kind: "refund_membership",
14881
15361
  status: "pending",
14882
15362
  attempts: 0,
14883
15363
  createdAt: now,
14884
15364
  updatedAt: now
14885
15365
  })
14886
- ], { mutationId: `membership-refund-request:${id6}`, guards: [
14887
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
15366
+ ], { mutationId: `membership-refund-request:${id7}`, guards: [
15367
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
14888
15368
  { ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision } },
14889
15369
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
14890
15370
  ] });
14891
15371
  }
14892
- member = await membershipLoad(db, "networkMembership", id6);
15372
+ member = await membershipLoad(db, "networkMembership", id7);
14893
15373
  requireMembershipScope(member, input);
14894
15374
  if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
14895
15375
  const result = await refundSubscriptionPayment(secretKey, {
@@ -14903,23 +15383,23 @@ async function refundNetworkMembership(db, input, secretKey, now) {
14903
15383
  if (!result.subscriptionCanceled || result.refundedCents !== member.amountCents) membershipFail("membership_refund_unconfirmed");
14904
15384
  for (let attempt = 0; attempt < 8; attempt++) {
14905
15385
  state = await loadMembershipAuthorityState(db, input);
14906
- member = await membershipLoad(db, "networkMembership", id6);
15386
+ member = await membershipLoad(db, "networkMembership", id7);
14907
15387
  requireMembershipScope(member, input);
14908
- if (member.status === "refunded") return { membershipId: id6, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
15388
+ if (member.status === "refunded") return { membershipId: id7, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
14909
15389
  if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
14910
15390
  const counted = member.founding === true && member.memberNumber !== void 0;
14911
15391
  const counts = state.cohortCounts;
14912
15392
  if (!counts || counted && (!Number.isSafeInteger(counts[String(member.cohort)]) || counts[String(member.cohort)] < 1)) membershipFail("invalid_cohort_count");
14913
15393
  try {
14914
15394
  await db.transact([
14915
- membershipUpdate("networkMembership", id6, {
15395
+ membershipUpdate("networkMembership", id7, {
14916
15396
  status: "refunded",
14917
15397
  refundedCents: result.refundedCents,
14918
15398
  autoRenew: false,
14919
15399
  revision: Number(member.revision) + 1,
14920
15400
  updatedAt: now
14921
15401
  }),
14922
- membershipUpdate("membershipEffect", `${id6}:refund`, { status: "complete", updatedAt: now }),
15402
+ membershipUpdate("membershipEffect", `${id7}:refund`, { status: "complete", updatedAt: now }),
14923
15403
  ...counted ? [membershipUpdate("membershipAuthorityState", String(state.id), {
14924
15404
  cohortCounts: { ...counts, [String(member.cohort)]: counts[String(member.cohort)] - 1 },
14925
15405
  revision: Number(state.revision) + 1,
@@ -14930,7 +15410,7 @@ async function refundNetworkMembership(db, input, secretKey, now) {
14930
15410
  chapterId: input.chapterId,
14931
15411
  environment: input.environment,
14932
15412
  runtime: input.runtime,
14933
- membershipId: id6,
15413
+ membershipId: id7,
14934
15414
  seatId: member.namedSeatId,
14935
15415
  kind: "refund_seat_with_primary",
14936
15416
  status: "pending",
@@ -14938,8 +15418,8 @@ async function refundNetworkMembership(db, input, secretKey, now) {
14938
15418
  createdAt: now,
14939
15419
  updatedAt: now
14940
15420
  })] : []
14941
- ], { mutationId: `membership-refunded:${id6}`, guards: [
14942
- { ns: "networkMembership", id: id6, exists: true, equals: { status: "refund_pending", revision: member.revision } },
15421
+ ], { mutationId: `membership-refunded:${id7}`, guards: [
15422
+ { ns: "networkMembership", id: id7, exists: true, equals: { status: "refund_pending", revision: member.revision } },
14943
15423
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
14944
15424
  ] });
14945
15425
  } catch (error) {
@@ -14950,12 +15430,12 @@ async function refundNetworkMembership(db, input, secretKey, now) {
14950
15430
  }
14951
15431
 
14952
15432
  // src/membership-provider-lifecycle.ts
14953
- var import_stripe14 = require("@odla-ai/stripe");
15433
+ var import_stripe15 = require("@odla-ai/stripe");
14954
15434
 
14955
15435
  // src/membership-renewal-authority.ts
14956
15436
  async function reconcileMembershipRenewal(db, input, now) {
14957
15437
  membershipClock(now);
14958
- const id6 = await networkMembershipId(input), eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
15438
+ const id7 = await networkMembershipId(input), eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
14959
15439
  const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow?.digest, "membership_renewal_evidence_pending");
14960
15440
  const fact = eventRow?.fact;
14961
15441
  if (!fact || !/^evt_[A-Za-z0-9_]+$/.test(input.eventId) || !/^[a-f0-9]{64}$/.test(String(eventRow.digest)) || fact.digest !== eventRow.digest || fact.eventId !== input.eventId || fact.objectKind !== "invoice" || !["invoice.payment_succeeded", "invoice.payment_failed"].includes(fact.eventType) || fact.billingReason !== "subscription_cycle" || fact.livemode !== (input.environment === "prod") || fact.applicationId !== input.applicationId || Object.entries({
@@ -14968,10 +15448,10 @@ async function reconcileMembershipRenewal(db, input, now) {
14968
15448
  return membershipFail("membership_renewal_evidence_pending");
14969
15449
  }
14970
15450
  for (let attempt = 0; attempt < 8; attempt++) {
14971
- const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id6);
15451
+ const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id7);
14972
15452
  requireMembershipScope(member, input);
14973
15453
  const receipt = () => ({
14974
- membershipId: id6,
15454
+ membershipId: id7,
14975
15455
  status: String(member.status),
14976
15456
  termEndsAt: Number(member.termEndsAt),
14977
15457
  graceEndsAt: Number(member.graceEndsAt ?? 0),
@@ -14996,13 +15476,13 @@ async function reconcileMembershipRenewal(db, input, now) {
14996
15476
  const paid = fact.eventType === "invoice.payment_succeeded";
14997
15477
  if (!paid && (fact.status !== "open" || !Number.isSafeInteger(fact.amountPaidCents) || Number(fact.amountPaidCents) < 0 || Number(fact.amountPaidCents) >= amount)) membershipFail("membership_renewal_failure_mismatch");
14998
15478
  if (paid && (fact.status !== "paid" || fact.amountPaidCents !== amount || !Number.isSafeInteger(fact.invoicePaidAt) || Number(fact.invoicePaidAt) < start || Number(fact.invoicePaidAt) > now + 3e4)) membershipFail("membership_renewal_unpaid");
14999
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id6}:primary`);
15479
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id7}:primary`);
15000
15480
  requireMembershipScope(entitlement, input);
15001
15481
  const graceEndsAt = paid ? 0 : start + 14 * 864e5;
15002
15482
  const status2 = paid ? member.journeyApprovedAt ? member.autoRenew ? "active" : "cancel_at_term_end" : "paid_pending_approval" : now < graceEndsAt ? "payment_grace" : "lapsed";
15003
15483
  const entitlementStatus = member.journeyApprovedAt && (paid || now < graceEndsAt) ? "active" : member.journeyApprovedAt ? "closed" : "pending_approval";
15004
15484
  const ops = [
15005
- membershipUpdate("networkMembership", id6, {
15485
+ membershipUpdate("networkMembership", id7, {
15006
15486
  status: status2,
15007
15487
  graceEndsAt,
15008
15488
  ...paid ? {
@@ -15013,7 +15493,7 @@ async function reconcileMembershipRenewal(db, input, now) {
15013
15493
  revision: Number(member.revision) + 1,
15014
15494
  updatedAt: now
15015
15495
  }),
15016
- membershipUpdate("networkEntitlement", `${id6}:primary`, {
15496
+ membershipUpdate("networkEntitlement", `${id7}:primary`, {
15017
15497
  status: entitlementStatus,
15018
15498
  ...paid ? { termStartedAt: start, termEndsAt: end } : {},
15019
15499
  sourceEventId: input.eventId,
@@ -15022,8 +15502,8 @@ async function reconcileMembershipRenewal(db, input, now) {
15022
15502
  })
15023
15503
  ];
15024
15504
  const guards = [
15025
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
15026
- { ns: "networkEntitlement", id: `${id6}:primary`, exists: true, equals: { revision: entitlement.revision } },
15505
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
15506
+ { ns: "networkEntitlement", id: `${id7}:primary`, exists: true, equals: { revision: entitlement.revision } },
15027
15507
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
15028
15508
  { ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
15029
15509
  evidenceGuard
@@ -15048,15 +15528,15 @@ async function reconcileMembershipRenewal(db, input, now) {
15048
15528
  );
15049
15529
  }
15050
15530
  try {
15051
- await db.transact(ops, { mutationId: `membership-renewal:${id6}:${input.eventId}`, guards });
15531
+ await db.transact(ops, { mutationId: `membership-renewal:${id7}:${input.eventId}`, guards });
15052
15532
  } catch (error) {
15053
15533
  if (error.code !== "transact_guard_failed") throw error;
15054
15534
  continue;
15055
15535
  }
15056
- const saved = await membershipLoad(db, "networkMembership", id6);
15536
+ const saved = await membershipLoad(db, "networkMembership", id7);
15057
15537
  requireMembershipScope(saved, input);
15058
15538
  return {
15059
- membershipId: id6,
15539
+ membershipId: id7,
15060
15540
  status: String(saved.status),
15061
15541
  termEndsAt: Number(saved.termEndsAt),
15062
15542
  graceEndsAt: Number(saved.graceEndsAt ?? 0),
@@ -15082,12 +15562,12 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15082
15562
  }).some(([key3, value]) => fact.identity[key3] !== value)) membershipFail("membership_billing_evidence_pending");
15083
15563
  const sk = await getVaultSecret(db, stripeSecretName);
15084
15564
  if (!sk) membershipFail("membership_billing_provider_unavailable");
15085
- const id6 = await networkMembershipId(input);
15565
+ const id7 = await networkMembershipId(input);
15086
15566
  for (let attempt = 0; attempt < 8; attempt++) {
15087
- const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id6);
15567
+ const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id7);
15088
15568
  requireMembershipScope(member, input);
15089
15569
  const receipt = (value = member) => ({
15090
- membershipId: id6,
15570
+ membershipId: id7,
15091
15571
  status: String(value.status),
15092
15572
  termEndsAt: Number(value.termEndsAt),
15093
15573
  graceEndsAt: Number(value.graceEndsAt ?? 0),
@@ -15108,13 +15588,13 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15108
15588
  });
15109
15589
  await commercialEvidenceGuard(db, input.eventId, row2?.digest, "membership_billing_evidence_pending");
15110
15590
  await refundNetworkMembership(db, input, sk, now);
15111
- const saved2 = await membershipLoad(db, "networkMembership", id6);
15591
+ const saved2 = await membershipLoad(db, "networkMembership", id7);
15112
15592
  requireMembershipScope(saved2, input);
15113
15593
  return receipt(saved2);
15114
15594
  }
15115
15595
  if (fact.subscriptionId !== member.providerSubscriptionId || fact.objectKind !== "subscription") membershipFail("membership_billing_scope_mismatch");
15116
15596
  if (["refund_pending", "refunded", "declined", "canceled", "restarting"].includes(String(member.status))) return receipt();
15117
- const result = await (0, import_stripe14.stripeRequest)({ secretKey: sk }, "GET", `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`);
15597
+ const result = await (0, import_stripe15.stripeRequest)({ secretKey: sk }, "GET", `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`);
15118
15598
  const sub = result.body;
15119
15599
  if (!result.ok || sub.id !== member.providerSubscriptionId || sub.customer !== member.providerCustomerId || sub.livemode !== (input.environment === "prod") || typeof sub.cancel_at_period_end !== "boolean" || !["active", "trialing", "past_due", "unpaid", "canceled"].includes(sub.status) || !Object.entries({ ...stripePartitionMetadata(input), applicationId: input.applicationId }).every(([key3, value]) => sub.metadata?.[key3] === value)) {
15120
15600
  membershipFail("membership_billing_provider_mismatch");
@@ -15123,18 +15603,18 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15123
15603
  const status2 = autoRenew ? member.status === "cancel_at_term_end" ? "active" : String(member.status) : now >= Number(member.termEndsAt) ? "lapsed" : member.journeyApprovedAt ? "cancel_at_term_end" : "paid_pending_approval";
15124
15604
  const graceEndsAt = autoRenew ? Number(member.graceEndsAt ?? 0) : 0;
15125
15605
  if (member.autoRenew === autoRenew && member.status === status2 && Number(member.graceEndsAt ?? 0) === graceEndsAt) return receipt();
15126
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id6}:primary`);
15606
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id7}:primary`);
15127
15607
  requireMembershipScope(entitlement, input);
15128
15608
  try {
15129
15609
  await db.transact([
15130
- membershipUpdate("networkMembership", id6, { status: status2, autoRenew, graceEndsAt, revision: Number(member.revision) + 1, updatedAt: now }),
15610
+ membershipUpdate("networkMembership", id7, { status: status2, autoRenew, graceEndsAt, revision: Number(member.revision) + 1, updatedAt: now }),
15131
15611
  membershipUpdate("networkEntitlement", String(entitlement.id), {
15132
15612
  status: status2 === "lapsed" ? "closed" : entitlement.status,
15133
15613
  revision: Number(entitlement.revision) + 1,
15134
15614
  updatedAt: now
15135
15615
  })
15136
15616
  ], { guards: [
15137
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
15617
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
15138
15618
  { ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision } },
15139
15619
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
15140
15620
  { ns: "networkCommercialEvents", id: String(row2.id), exists: true, equals: { digest: fact.digest } },
@@ -15144,7 +15624,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15144
15624
  if (error.code !== "transact_guard_failed") throw error;
15145
15625
  continue;
15146
15626
  }
15147
- const saved = await membershipLoad(db, "networkMembership", id6);
15627
+ const saved = await membershipLoad(db, "networkMembership", id7);
15148
15628
  requireMembershipScope(saved, input);
15149
15629
  return receipt(saved);
15150
15630
  }
@@ -15152,7 +15632,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15152
15632
  }
15153
15633
 
15154
15634
  // src/membership-seat-stripe.ts
15155
- var import_stripe15 = require("@odla-ai/stripe");
15635
+ var import_stripe16 = require("@odla-ai/stripe");
15156
15636
  var reject7 = () => {
15157
15637
  throw Object.assign(new Error("named seat provider contract mismatch"), { code: "named_seat_provider_mismatch" });
15158
15638
  };
@@ -15164,7 +15644,7 @@ var metadataFor = (seat) => ({
15164
15644
  });
15165
15645
  var matches = (value, expected) => value && Object.entries(expected).every(([key3, item]) => value[key3] === item);
15166
15646
  async function read(sk, path, params) {
15167
- const response = await (0, import_stripe15.stripeRequest)({ secretKey: sk }, "GET", path, params);
15647
+ const response = await (0, import_stripe16.stripeRequest)({ secretKey: sk }, "GET", path, params);
15168
15648
  if (!response.ok) return reject7();
15169
15649
  return response.body;
15170
15650
  }
@@ -15175,7 +15655,7 @@ async function subscriptionForSeat(sk, seat) {
15175
15655
  currency: seat.currency,
15176
15656
  interval: seat.interval,
15177
15657
  environment: seat.environment
15178
- }, Boolean(seat.providerPaymentIntentId));
15658
+ }, Boolean(seat.providerPaymentIntentId || seat.combinedInvoice));
15179
15659
  if (!productId) reject7();
15180
15660
  const sub = await read(sk, `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}?expand[]=discounts`);
15181
15661
  const items = sub.items?.data;
@@ -15201,11 +15681,11 @@ async function createNamedSeatStripePayment(sk, seat, now) {
15201
15681
  }
15202
15682
  await subscriptionForSeat(sk, seat);
15203
15683
  const metadata = metadataFor(seat);
15204
- const result = seat.providerPaymentIntentId ? await (0, import_stripe15.stripeRequest)(
15684
+ const result = seat.providerPaymentIntentId ? await (0, import_stripe16.stripeRequest)(
15205
15685
  { secretKey: sk },
15206
15686
  "GET",
15207
15687
  `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`
15208
- ) : await (0, import_stripe15.stripeRequest)({ secretKey: sk }, "POST", "/v1/payment_intents", {
15688
+ ) : await (0, import_stripe16.stripeRequest)({ secretKey: sk }, "POST", "/v1/payment_intents", {
15209
15689
  amount: seat.amountCents,
15210
15690
  currency: seat.currency,
15211
15691
  customer: seat.providerCustomerId,
@@ -15219,11 +15699,15 @@ async function createNamedSeatStripePayment(sk, seat, now) {
15219
15699
  return { paymentIntentId: pi.id, clientSecret: pi.client_secret, status: pi.status };
15220
15700
  }
15221
15701
  async function attachNamedSeatStripeRenewal(sk, seat) {
15222
- if (!seat.providerPaymentIntentId || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject7();
15702
+ if (!seat.providerPaymentIntentId && !seat.combinedInvoice || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject7();
15223
15703
  const sub = await subscriptionForSeat(sk, seat);
15224
15704
  const existing = sub.items.data.find((item2) => item2.metadata?.seatId === seat.id);
15225
- if (existing) return String(existing.id);
15226
- const result = await (0, import_stripe15.stripeRequest)({ secretKey: sk }, "POST", "/v1/subscription_items", {
15705
+ if (existing) {
15706
+ if (!/^si_[A-Za-z0-9_]+$/.test(existing.id)) reject7();
15707
+ return String(existing.id);
15708
+ }
15709
+ if (seat.combinedInvoice) reject7();
15710
+ const result = await (0, import_stripe16.stripeRequest)({ secretKey: sk }, "POST", "/v1/subscription_items", {
15227
15711
  subscription: seat.providerSubscriptionId,
15228
15712
  price: seat.priceId,
15229
15713
  quantity: 1,
@@ -15440,7 +15924,7 @@ async function recordRecipient(db, input, now, approve) {
15440
15924
  const state = await loadMembershipAuthorityState(db, input);
15441
15925
  const seat = await membershipLoad(db, "networkSeat", input.seatId);
15442
15926
  requireMembershipScope(seat, input);
15443
- if (!seat.purchaserUserId || seat.purchaserUserId === input.recipientUserId) membershipFail("named_seat_recipient_must_differ_from_purchaser");
15927
+ if (!seat.purchaserUserId && !seat.purchaserEmailNormalized || seat.purchaserUserId === input.recipientUserId || seat.purchaserEmailNormalized === normalizeChapterEmail(input.verifiedRecipientEmail)) membershipFail("named_seat_recipient_must_differ_from_purchaser");
15444
15928
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
15445
15929
  requireMembershipScope(member, input);
15446
15930
  const entitlement = await membershipLoad(db, "networkEntitlement", String(seat.entitlementId));
@@ -15760,9 +16244,14 @@ function createMembershipAuthorityHandler(options) {
15760
16244
  try {
15761
16245
  input = JSON.parse(verified.body);
15762
16246
  const fields = action === "seat-invitation" ? MEMBERSHIP_SEAT_ACTION_FIELDS[action] : action === "offer-preview" ? ["skuId"] : ["applicationId", "skuId", ...action === "offers" ? ["requestId"] : action === "checkouts" ? ["offerId", "offerDigest", "quoteDigest", "priceId"] : action === "payments" ? ["eventId", "providerSubscriptionId", "providerCustomerId"] : action === "renewals" ? ["eventId"] : action === "restarts" ? ["previousSubscriptionId"] : MEMBERSHIP_SEAT_ACTION_FIELDS[action] ?? []];
15763
- if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key3) => !fields.includes(key3)) || !fields.every((key3) => typeof input[key3] === "string" && (["recipientName", "recipientEmail", "purchaserEmail", "verifiedRecipientEmail"].includes(key3) ? input[key3].length > 0 && input[key3].length <= 254 && !/[\u0000-\u001f\u007f]/.test(input[key3]) : SAFE2.test(input[key3])))) {
16247
+ if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key3) => !fields.includes(key3) && !(action === "checkouts" && key3 === "namedSeat")) || !fields.every((key3) => typeof input[key3] === "string" && (["recipientName", "recipientEmail", "purchaserEmail", "verifiedRecipientEmail"].includes(key3) ? input[key3].length > 0 && input[key3].length <= 254 && !/[\u0000-\u001f\u007f]/.test(input[key3]) : SAFE2.test(input[key3])))) {
15764
16248
  throw new TypeError("invalid request");
15765
16249
  }
16250
+ if (action === "checkouts" && input.namedSeat !== void 0) {
16251
+ const seat = input.namedSeat;
16252
+ 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");
16253
+ checkoutNamedSeatSelection(seat, seat.purchaserEmail);
16254
+ }
15766
16255
  } catch {
15767
16256
  return respond({ error: "invalid_offer_request" }, 400);
15768
16257
  }
@@ -15835,8 +16324,9 @@ function createMembershipAuthorityHandler(options) {
15835
16324
  offerId: String(input.offerId),
15836
16325
  offerDigest: String(input.offerDigest),
15837
16326
  quoteDigest: String(input.quoteDigest),
15838
- priceId: String(input.priceId)
15839
- }, options.now?.() ?? Date.now());
16327
+ priceId: String(input.priceId),
16328
+ ...input.namedSeat ? { namedSeat: input.namedSeat } : {}
16329
+ }, options.now?.() ?? Date.now(), peer.seatPriceId);
15840
16330
  return respond({ ok: true, binding });
15841
16331
  }
15842
16332
  const offerId = await membershipOfferIdForRequest({ ...peer, applicationId: String(input.applicationId), requestId: String(input.requestId) });
@@ -15864,13 +16354,13 @@ function createMembershipAuthorityHandler(options) {
15864
16354
 
15865
16355
  // src/schema-membership-authority.ts
15866
16356
  var attr = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
15867
- var id4 = () => attr("string", false, true, true);
16357
+ var id5 = () => attr("string", false, true, true);
15868
16358
  var key = (optional = false) => attr("string", optional, false, true);
15869
16359
  var scope = () => ({ appId: key(), chapterId: key(), environment: key(), runtime: key(), applicationId: key(), skuId: key() });
15870
16360
  var times = () => ({ revision: attr("number"), createdAt: attr("number"), updatedAt: attr("number") });
15871
16361
  var membershipAuthorityEntities = {
15872
16362
  networkMembership: { attrs: {
15873
- id: id4(),
16363
+ id: id5(),
15874
16364
  membershipId: key(),
15875
16365
  ...scope(),
15876
16366
  personId: key(true),
@@ -15910,7 +16400,7 @@ var membershipAuthorityEntities = {
15910
16400
  restartId: key(true)
15911
16401
  } },
15912
16402
  membershipRestart: { attrs: {
15913
- id: id4(),
16403
+ id: id5(),
15914
16404
  ...scope(),
15915
16405
  membershipId: key(),
15916
16406
  restartId: key(),
@@ -15920,7 +16410,7 @@ var membershipAuthorityEntities = {
15920
16410
  createdAt: attr("number")
15921
16411
  } },
15922
16412
  membershipPurchaseHistory: { attrs: {
15923
- id: id4(),
16413
+ id: id5(),
15924
16414
  ...scope(),
15925
16415
  membershipId: key(),
15926
16416
  providerSubscriptionId: key(),
@@ -15929,7 +16419,7 @@ var membershipAuthorityEntities = {
15929
16419
  archivedAt: attr("number")
15930
16420
  } },
15931
16421
  networkEntitlement: { attrs: {
15932
- id: id4(),
16422
+ id: id5(),
15933
16423
  ...scope(),
15934
16424
  membershipId: key(),
15935
16425
  seatId: key(true),
@@ -15946,10 +16436,12 @@ var membershipAuthorityEntities = {
15946
16436
  journeyApprovedAt: attr("number", true)
15947
16437
  } },
15948
16438
  networkSeat: { attrs: {
15949
- id: id4(),
16439
+ id: id5(),
15950
16440
  ...scope(),
15951
16441
  primaryMembershipId: key(),
15952
- purchaserUserId: key(),
16442
+ purchaserUserId: key(true),
16443
+ purchaserEmailNormalized: key(true),
16444
+ combinedInvoice: attr("json", true),
15953
16445
  recipientName: attr("string"),
15954
16446
  recipientEmailNormalized: key(),
15955
16447
  recipientClaimId: key(),
@@ -15981,9 +16473,9 @@ var membershipAuthorityEntities = {
15981
16473
  refundedCents: attr("number", true),
15982
16474
  providerRefundIds: attr("json", true)
15983
16475
  } },
15984
- networkSeatRecipient: { attrs: { id: id4(), appId: key(), chapterId: key(), environment: key(), runtime: key(), seatId: key(), recipientEmailNormalized: key(), createdAt: attr("number") } },
16476
+ networkSeatRecipient: { attrs: { id: id5(), appId: key(), chapterId: key(), environment: key(), runtime: key(), seatId: key(), recipientEmailNormalized: key(), createdAt: attr("number") } },
15985
16477
  membershipEffect: { attrs: {
15986
- id: id4(),
16478
+ id: id5(),
15987
16479
  chapterId: key(),
15988
16480
  environment: key(),
15989
16481
  runtime: key(),
@@ -16002,11 +16494,11 @@ var membershipAuthorityRules = Object.fromEntries(Object.keys(membershipAuthorit
16002
16494
 
16003
16495
  // src/schema-membership-offers.ts
16004
16496
  var attr2 = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
16005
- var id5 = () => attr2("string", false, true, true);
16497
+ var id6 = () => attr2("string", false, true, true);
16006
16498
  var key2 = () => attr2("string", false, false, true);
16007
16499
  var membershipOfferEntities = {
16008
16500
  membershipAuthorityState: { attrs: {
16009
- id: id5(),
16501
+ id: id6(),
16010
16502
  chapterId: key2(),
16011
16503
  environment: key2(),
16012
16504
  mode: key2(),
@@ -16016,7 +16508,7 @@ var membershipOfferEntities = {
16016
16508
  updatedAt: attr2("number")
16017
16509
  } },
16018
16510
  membershipCheckoutOffer: { attrs: {
16019
- id: id5(),
16511
+ id: id6(),
16020
16512
  applicationId: key2(),
16021
16513
  appId: key2(),
16022
16514
  chapterId: key2(),
@@ -16031,7 +16523,7 @@ var membershipOfferEntities = {
16031
16523
  authorityRevision: attr2("number")
16032
16524
  } },
16033
16525
  membershipOfferCheckout: { attrs: {
16034
- id: id5(),
16526
+ id: id6(),
16035
16527
  appId: key2(),
16036
16528
  chapterId: key2(),
16037
16529
  environment: key2(),
@@ -16042,7 +16534,8 @@ var membershipOfferEntities = {
16042
16534
  offerDigest: attr2("string"),
16043
16535
  quoteDigest: attr2("string"),
16044
16536
  priceId: attr2("string"),
16045
- boundAt: attr2("number")
16537
+ boundAt: attr2("number"),
16538
+ namedSeat: attr2("json", true)
16046
16539
  } }
16047
16540
  };
16048
16541
  var membershipOfferRules = Object.fromEntries(Object.keys(membershipOfferEntities).map((ns) => [ns, { view: "false", create: "false", update: "false", delete: "false" }]));
@@ -16097,7 +16590,7 @@ function createMembershipAuthorityStorage(db, secretNames) {
16097
16590
  }
16098
16591
 
16099
16592
  // src/membership-seat-cancel.ts
16100
- var import_stripe16 = require("@odla-ai/stripe");
16593
+ var import_stripe17 = require("@odla-ai/stripe");
16101
16594
  async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
16102
16595
  membershipClock(now);
16103
16596
  const state = await loadMembershipAuthorityState(db, target);
@@ -16125,7 +16618,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
16125
16618
  }
16126
16619
  const metadata = { ...stripePartitionMetadata(target), applicationId: target.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
16127
16620
  const request = async (method, path, params) => {
16128
- const result = await (0, import_stripe16.stripeRequest)(
16621
+ const result = await (0, import_stripe17.stripeRequest)(
16129
16622
  { secretKey },
16130
16623
  method,
16131
16624
  path,
@@ -16138,7 +16631,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
16138
16631
  let payment;
16139
16632
  if (seat.providerPaymentIntentId) payment = await request("GET", `/v1/payment_intents/${encodeURIComponent(String(seat.providerPaymentIntentId))}`);
16140
16633
  else {
16141
- const snapshot = await listStripeObjects((0, import_stripe16.initStripe)({ secretKey }), "/v1/payment_intents", { customer: String(seat.providerCustomerId) });
16634
+ const snapshot = await listStripeObjects((0, import_stripe17.initStripe)({ secretKey }), "/v1/payment_intents", { customer: String(seat.providerCustomerId) });
16142
16635
  const matches2 = snapshot.objects.filter((row2) => row2.metadata?.seatId === target.seatId);
16143
16636
  if (!snapshot.complete || matches2.length !== 1) throw new Error("seat payment creation outcome unresolved");
16144
16637
  payment = matches2[0];
@@ -16151,9 +16644,9 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
16151
16644
  if (payment.status === "succeeded" || payment.amount_received > 0) throw new Error("paid seat requires payment evidence and full refund");
16152
16645
  if (payment.status !== "canceled") {
16153
16646
  if (!["requires_payment_method", "requires_confirmation", "requires_action", "processing"].includes(payment.status)) throw new Error("seat payment outcome unresolved");
16154
- const id6 = payment.id;
16155
- payment = await request("POST", `/v1/payment_intents/${encodeURIComponent(id6)}/cancel`, { cancellation_reason: "abandoned" });
16156
- if (payment.id !== id6) throw new Error("seat cancellation receipt mismatch");
16647
+ const id7 = payment.id;
16648
+ payment = await request("POST", `/v1/payment_intents/${encodeURIComponent(id7)}/cancel`, { cancellation_reason: "abandoned" });
16649
+ if (payment.id !== id7) throw new Error("seat cancellation receipt mismatch");
16157
16650
  }
16158
16651
  if (!valid(payment) || payment.status !== "canceled" || payment.amount_received !== 0) throw new Error("seat cancellation unconfirmed");
16159
16652
  await db.transact([membershipUpdate("networkSeat", target.seatId, { status: "canceled", revision: Number(seat.revision) + 1, updatedAt: now })], {
@@ -16475,7 +16968,7 @@ async function requestMembershipCommercialOffer(options) {
16475
16968
  async function requestMembershipOfferPreview(options) {
16476
16969
  const result = await requestAuthority(options, "offer-preview", { skuId: options.skuId });
16477
16970
  const preview = result.preview;
16478
- if (!preview || preview.skuId !== options.skuId || typeof preview.founding !== "boolean" || !Number.isSafeInteger(preview.basePriceCents) || !Number.isSafeInteger(preview.dueTodayCents) || preview.dueTodayCents < 0 || preview.dueTodayCents > preview.basePriceCents || !Number.isSafeInteger(preview.renewalCents) || !/^[a-z]{3}$/.test(preview.currency) || !["month", "year"].includes(preview.interval) || preview.cohortLabel !== void 0 && typeof preview.cohortLabel !== "string" || preview.discountDuration !== void 0 && !["first_term", "while_active"].includes(preview.discountDuration)) throw new Error("invalid offer preview");
16971
+ if (!preview || preview.skuId !== options.skuId || typeof preview.founding !== "boolean" || !Number.isSafeInteger(preview.basePriceCents) || !Number.isSafeInteger(preview.dueTodayCents) || preview.dueTodayCents < 0 || preview.dueTodayCents > preview.basePriceCents || !Number.isSafeInteger(preview.renewalCents) || !/^[a-z]{3}$/.test(preview.currency) || !["month", "year"].includes(preview.interval) || preview.cohortLabel !== void 0 && typeof preview.cohortLabel !== "string" || preview.seatPriceCents !== void 0 && (!Number.isSafeInteger(preview.seatPriceCents) || preview.seatPriceCents < 0) || preview.discountDuration !== void 0 && !["first_term", "while_active"].includes(preview.discountDuration)) throw new Error("invalid offer preview");
16479
16972
  return preview;
16480
16973
  }
16481
16974
  async function requestMembershipCheckoutBinding(options) {
@@ -16485,7 +16978,8 @@ async function requestMembershipCheckoutBinding(options) {
16485
16978
  offerId: options.offerId,
16486
16979
  offerDigest: options.offerDigest,
16487
16980
  quoteDigest: options.quoteDigest,
16488
- priceId: options.priceId
16981
+ priceId: options.priceId,
16982
+ ...options.namedSeat ? { namedSeat: options.namedSeat } : {}
16489
16983
  });
16490
16984
  return verifyMembershipCheckoutBinding(result.binding, options);
16491
16985
  }