@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.
@@ -79,10 +79,10 @@ async function clerkGetUserByEmail(secretKey, email, fetchImpl = fetch, source =
79
79
  const user = Array.isArray(data) ? data[0] : void 0;
80
80
  return user ? toRecord(user, source) : null;
81
81
  }
82
- async function clerkGetUser(secretKey, id6, fetchImpl = fetch, source = "public") {
83
- const data = await clerkGet(`/v1/users/${encodeURIComponent(id6)}`, secretKey, fetchImpl).catch(() => null);
82
+ async function clerkGetUser(secretKey, id7, fetchImpl = fetch, source = "public") {
83
+ const data = await clerkGet(`/v1/users/${encodeURIComponent(id7)}`, secretKey, fetchImpl).catch(() => null);
84
84
  const record4 = data ? toRecord(data, source) : null;
85
- return record4?.id === id6 ? record4 : null;
85
+ return record4?.id === id7 ? record4 : null;
86
86
  }
87
87
  async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
88
88
  const out = [];
@@ -98,8 +98,8 @@ async function clerkListUsers(secretKey, fetchImpl = fetch, source = "public") {
98
98
  }
99
99
  return out;
100
100
  }
101
- async function clerkSetRole(secretKey, id6, role, fetchImpl = fetch, source = "public") {
102
- const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id6)}/metadata`, {
101
+ async function clerkSetRole(secretKey, id7, role, fetchImpl = fetch, source = "public") {
102
+ const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id7)}/metadata`, {
103
103
  method: "PATCH",
104
104
  headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
105
105
  redirect: "manual",
@@ -158,10 +158,10 @@ async function ensureProjectedPerson(deps, input, options = {}) {
158
158
  const existing = await find();
159
159
  if (existing) return { record: existing, created: false };
160
160
  const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`chapter:person:v1:${email}`)));
161
- const id6 = `person-${Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
161
+ const id7 = `person-${Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
162
162
  try {
163
163
  const created = await createRecord({ ...deps, db: deps.db }, {
164
- id: id6,
164
+ id: id7,
165
165
  type: "person",
166
166
  input: { ...input, email },
167
167
  ...options
@@ -614,7 +614,10 @@ function verifyInitialMembershipInvoice(intent, evidence) {
614
614
  if (!intent.providerSubscriptionId || !intent.providerCustomerId) return { status: "retry" };
615
615
  try {
616
616
  const terms2 = intent.billingTerms ? subscriptionBillingTerms(intent.billingTerms) : void 0;
617
- const due = subscriptionAmountDue({ priceCents: Number(intent.priceCents), billingTerms: terms2 });
617
+ const quote = intent.paymentQuote;
618
+ const seatCents = quote?.namedSeat ? quote.lineItems.namedSeatCents : 0;
619
+ if (!Number.isSafeInteger(seatCents) || Number(seatCents) < 0 || quote?.namedSeat && quote.seatOffer?.amountCents !== seatCents) return { status: "mismatch" };
620
+ const due = subscriptionAmountDue({ priceCents: Number(intent.priceCents), billingTerms: terms2 }) + Number(seatCents);
618
621
  if (!evidence || !/^in_[A-Za-z0-9]+$/.test(evidence.invoiceId ?? "") || evidence.subscriptionId !== intent.providerSubscriptionId || evidence.customerId !== intent.providerCustomerId || evidence.currency !== intent.currency || evidence.total !== due || evidence.amountPaid !== due || due <= 0 || evidence.status !== "paid") return { status: "mismatch" };
619
622
  return { status: "verified", ...terms2?.initialTermEndsAt ? { renewalAt: terms2.initialTermEndsAt } : {} };
620
623
  } catch {
@@ -965,8 +968,8 @@ async function rebuildNetworkCommercialProjection(db, sources, mutationId, now =
965
968
  return projection;
966
969
  }
967
970
  async function collide(db, sources, stored, fact, now, legacyDigest, alerts) {
968
- const id6 = exceptionId(fact.eventId, "collision");
969
- const exception = (await db.query({ networkCommercialExceptions: { $: { where: { id: id6 }, limit: 1 } } })).networkCommercialExceptions?.[0];
971
+ const id7 = exceptionId(fact.eventId, "collision");
972
+ const exception = (await db.query({ networkCommercialExceptions: { $: { where: { id: id7 }, limit: 1 } } })).networkCommercialExceptions?.[0];
970
973
  const exact = stored.digest === fact.digest || stored.digest === legacyDigest;
971
974
  const canonical3 = !exact && sameNetworkCommercialFact(stored.fact, fact);
972
975
  if (exact || canonical3) {
@@ -987,7 +990,7 @@ async function collide(db, sources, stored, fact, now, legacyDigest, alerts) {
987
990
  action: "canonical_replay",
988
991
  reason: "a resend matched the retained canonical fact"
989
992
  } },
990
- { t: "update", ns: "networkCommercialExceptions", id: id6, attrs: {
993
+ { t: "update", ns: "networkCommercialExceptions", id: id7, attrs: {
991
994
  status: "resolved",
992
995
  resolvedDigest: stored.digest,
993
996
  reviewedDigest: fact.digest,
@@ -996,7 +999,7 @@ async function collide(db, sources, stored, fact, now, legacyDigest, alerts) {
996
999
  updatedAt: now
997
1000
  } }
998
1001
  ], { mutationId: `network-commercial-resolve:${fact.eventId}:${fact.digest}`, guards: [
999
- { ns: "networkCommercialExceptions", id: id6, exists: true, equals: { status: "active", updatedAt: exception.updatedAt } }
1002
+ { ns: "networkCommercialExceptions", id: id7, exists: true, equals: { status: "active", updatedAt: exception.updatedAt } }
1000
1003
  ] });
1001
1004
  const projection2 = await rebuildNetworkCommercialProjection(db, sources, `network-commercial-resolve-rebuild:${fact.eventId}:${fact.digest}`, now, alerts);
1002
1005
  return { status: "replayed", resolvedCollision: true, projection: projection2 };
@@ -1006,9 +1009,9 @@ async function collide(db, sources, stored, fact, now, legacyDigest, alerts) {
1006
1009
  }
1007
1010
  const stale = RESOLUTION_ATTRS.filter((attr3) => exception !== void 0 && Object.hasOwn(exception, attr3));
1008
1011
  await db.transact([
1009
- ...stale.length ? [{ t: "retract", ns: "networkCommercialExceptions", id: id6, attrs: stale }] : [],
1010
- { t: "update", ns: "networkCommercialExceptions", id: id6, attrs: {
1011
- id: id6,
1012
+ ...stale.length ? [{ t: "retract", ns: "networkCommercialExceptions", id: id7, attrs: stale }] : [],
1013
+ { t: "update", ns: "networkCommercialExceptions", id: id7, attrs: {
1014
+ id: id7,
1012
1015
  eventId: fact.eventId,
1013
1016
  kind: "collision",
1014
1017
  status: "active",
@@ -1028,12 +1031,12 @@ async function ingestNetworkCommercialFact(db, sources, fact, now = Date.now(),
1028
1031
  const found = await db.query({ networkCommercialEvents: { $: { where: { eventId: fact.eventId }, limit: 1 } } });
1029
1032
  const existing = found.networkCommercialEvents?.[0];
1030
1033
  if (existing) return collide(db, sources, existing, fact, now, legacyDigest, alerts);
1031
- const id6 = eventRowId(fact.eventId);
1034
+ const id7 = eventRowId(fact.eventId);
1032
1035
  let lostRace = false;
1033
1036
  let duplicate = false;
1034
1037
  try {
1035
- const write = await db.transact([{ t: "update", ns: "networkCommercialEvents", id: id6, attrs: {
1036
- id: id6,
1038
+ const write = await db.transact([{ t: "update", ns: "networkCommercialEvents", id: id7, attrs: {
1039
+ id: id7,
1037
1040
  eventId: fact.eventId,
1038
1041
  digest: fact.digest,
1039
1042
  eventType: fact.eventType,
@@ -1043,7 +1046,7 @@ async function ingestNetworkCommercialFact(db, sources, fact, now = Date.now(),
1043
1046
  receivedAt: now
1044
1047
  } }], {
1045
1048
  mutationId: `network-commercial-ingest:${fact.eventId}:${fact.digest}`,
1046
- guards: [{ ns: "networkCommercialEvents", id: id6, exists: false }]
1049
+ guards: [{ ns: "networkCommercialEvents", id: id7, exists: false }]
1047
1050
  });
1048
1051
  duplicate = write.duplicate;
1049
1052
  } catch (error) {
@@ -1087,10 +1090,10 @@ async function recoverNetworkCommercialEvent(db, sources, eventId, actorUserId,
1087
1090
  const provider = await readStripeRecoveryEvent(db, eventId);
1088
1091
  const fact = normalizeNetworkCommercialStripeEvent(provider.event, provider.digest);
1089
1092
  if (!sameNetworkCommercialFact(stored.fact, fact)) throw new Error("Stripe facts differ; collision requires investigation");
1090
- const id6 = crypto.randomUUID();
1093
+ const id7 = crypto.randomUUID();
1091
1094
  await db.transact([
1092
- { t: "update", ns: "paymentRecoveryReviews", id: id6, attrs: {
1093
- id: id6,
1095
+ { t: "update", ns: "paymentRecoveryReviews", id: id7, attrs: {
1096
+ id: id7,
1094
1097
  eventId,
1095
1098
  actorUserId,
1096
1099
  reviewedAt: now,
@@ -1105,9 +1108,9 @@ async function recoverNetworkCommercialEvent(db, sources, eventId, actorUserId,
1105
1108
  t: "update",
1106
1109
  ns: "networkCommercialExceptions",
1107
1110
  id: exceptionId2,
1108
- attrs: { status: "resolved", resolvedDigest: provider.digest, resolvedBy: actorUserId, reviewId: id6, updatedAt: now }
1111
+ attrs: { status: "resolved", resolvedDigest: provider.digest, resolvedBy: actorUserId, reviewId: id7, updatedAt: now }
1109
1112
  }] : []
1110
- ], { mutationId: `payment-recovery:${id6}`, guards: [
1113
+ ], { mutationId: `payment-recovery:${id7}`, guards: [
1111
1114
  { ns: "networkCommercialEvents", id: eventRowId2, exists: true, equals: { digest: stored.digest } },
1112
1115
  {
1113
1116
  ns: "networkCommercialExceptions",
@@ -1116,8 +1119,8 @@ async function recoverNetworkCommercialEvent(db, sources, eventId, actorUserId,
1116
1119
  ...exception ? { equals: { status: exception.status, updatedAt: exception.updatedAt } } : {}
1117
1120
  }
1118
1121
  ] });
1119
- await rebuildNetworkCommercialProjection(db, sources, `payment-recovery-rebuild:${id6}`, now, alerts);
1120
- return { recovered: true, eventId, reviewId: id6 };
1122
+ await rebuildNetworkCommercialProjection(db, sources, `payment-recovery-rebuild:${id7}`, now, alerts);
1123
+ return { recovered: true, eventId, reviewId: id7 };
1121
1124
  }
1122
1125
 
1123
1126
  // src/payments.ts
@@ -1710,7 +1713,7 @@ function findTier(tiers, tierId) {
1710
1713
  if (!tierId) return tiers.length === 1 ? tiers[0] : null;
1711
1714
  return tiers.find((tier) => tier.id === tierId) ?? null;
1712
1715
  }
1713
- function joinConfigTiers(tiers, defaults = {}, founding = /* @__PURE__ */ new Map()) {
1716
+ function joinConfigTiers(tiers, defaults = {}, founding = /* @__PURE__ */ new Map(), seats = /* @__PURE__ */ new Map()) {
1714
1717
  return tiers.map((tier) => ({
1715
1718
  id: tier.id,
1716
1719
  name: tier.name,
@@ -1719,7 +1722,8 @@ function joinConfigTiers(tiers, defaults = {}, founding = /* @__PURE__ */ new Ma
1719
1722
  interval: tier.interval ?? defaults.interval ?? "year",
1720
1723
  blurb: tier.blurb ?? "",
1721
1724
  free: tierIsFree(tier),
1722
- ...founding.has(tier.id) ? { founding: founding.get(tier.id) } : {}
1725
+ ...founding.has(tier.id) ? { founding: founding.get(tier.id) } : {},
1726
+ ...seats.has(tier.id) ? { seatOffer: seats.get(tier.id) } : {}
1723
1727
  }));
1724
1728
  }
1725
1729
 
@@ -1777,7 +1781,7 @@ async function frozenCheckoutVariables(db, app) {
1777
1781
  const intent = (await db.query({ subscriptionCheckoutIntents: { $: { where: { id: String(app.id) }, limit: 1 } } })).subscriptionCheckoutIntents?.[0];
1778
1782
  const offer = intent?.membershipOffer, contract = offer?.contract;
1779
1783
  const offering = contract?.offering, quote = intent?.paymentQuote;
1780
- const amount = contract?.dueTodayCents ?? intent?.priceCents;
1784
+ const amount = quote?.lineItems?.dueTodayCents ?? contract?.dueTodayCents ?? intent?.priceCents;
1781
1785
  const currency = offering?.currency ?? intent?.currency;
1782
1786
  let formatted = "";
1783
1787
  if (Number.isSafeInteger(amount) && Number(amount) > 0 && typeof currency === "string" && /^[a-z]{3}$/.test(currency)) {
@@ -1901,11 +1905,11 @@ async function sendTemplated(deps, input) {
1901
1905
  const previous = prior[0];
1902
1906
  if (previous && (previous.groupId !== input.group.id || previous.template !== input.template || previous.to !== decision.to || previous.transport !== decision.transport || previous.applicationId !== input.applicationId)) return { sent: false, reason: "delivery-identity-conflict" };
1903
1907
  const hash2 = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input.dedupeKey));
1904
- const id6 = previous ? String(previous.id) : `email_${Array.from(new Uint8Array(hash2), (b) => b.toString(16).padStart(2, "0")).join("")}`;
1908
+ const id7 = previous ? String(previous.id) : `email_${Array.from(new Uint8Array(hash2), (b) => b.toString(16).padStart(2, "0")).join("")}`;
1905
1909
  const attempt = Number(previous?.deliveryAttempt ?? 0) + 1;
1906
1910
  if (!Number.isSafeInteger(attempt) || attempt < 1) return { sent: false, reason: "delivery-state-invalid" };
1907
1911
  const row2 = {
1908
- id: id6,
1912
+ id: id7,
1909
1913
  groupId: input.group.id,
1910
1914
  to: decision.to,
1911
1915
  template: input.template,
@@ -1923,15 +1927,15 @@ async function sendTemplated(deps, input) {
1923
1927
  if (isRetiredEmailCopy(String(row2.subject), String(row2.body))) return { sent: false, reason: "template-retired" };
1924
1928
  if (validateEmailCopy2({ subject: row2.subject, text: row2.body }, { allowedVariables: [] }).length) return { sent: false, reason: "template-invalid" };
1925
1929
  try {
1926
- const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id: id6, attrs: row2 }], {
1927
- mutationId: `email-claim:${id6}:${attempt}`,
1930
+ const claim = await deps.db.transact([{ t: "update", ns: "emailLog", id: id7, attrs: row2 }], {
1931
+ mutationId: `email-claim:${id7}:${attempt}`,
1928
1932
  guards: [previous ? {
1929
1933
  ns: "emailLog",
1930
- id: id6,
1934
+ id: id7,
1931
1935
  exists: true,
1932
1936
  equals: { deliveryState: "retry_ready", ...previous.deliveryAttempt !== void 0 ? { deliveryAttempt: previous.deliveryAttempt } : {} },
1933
1937
  ...previous.deliveryAttempt === void 0 ? { absent: ["deliveryAttempt"] } : {}
1934
- } : { ns: "emailLog", id: id6, exists: false }]
1938
+ } : { ns: "emailLog", id: id7, exists: false }]
1935
1939
  });
1936
1940
  if (claim.duplicate) return { sent: false, reason: "delivery-uncertain" };
1937
1941
  } catch (error) {
@@ -1951,16 +1955,16 @@ async function sendTemplated(deps, input) {
1951
1955
  if (!response || typeof response.messageId !== "string" || !response.messageId) throw new Error("missing delivery receipt");
1952
1956
  messageId = response.messageId;
1953
1957
  } catch {
1954
- await deps.db.transact([{ t: "update", ns: "emailLog", id: id6, attrs: { deliveryState: "uncertain", error: "delivery-uncertain" } }], {
1955
- guards: [{ ns: "emailLog", id: id6, exists: true, equals: { deliveryState: "sending", deliveryAttempt: attempt } }]
1958
+ await deps.db.transact([{ t: "update", ns: "emailLog", id: id7, attrs: { deliveryState: "uncertain", error: "delivery-uncertain" } }], {
1959
+ guards: [{ ns: "emailLog", id: id7, exists: true, equals: { deliveryState: "sending", deliveryAttempt: attempt } }]
1956
1960
  });
1957
1961
  return { sent: false, reason: "delivery-uncertain" };
1958
1962
  }
1959
- await deps.db.transact([{ t: "update", ns: "emailLog", id: id6, attrs: { deliveryState: "sent", error: "", messageId, sentAt: deps.now() } }], {
1960
- mutationId: `email-confirmed:${id6}:${attempt}`,
1963
+ await deps.db.transact([{ t: "update", ns: "emailLog", id: id7, attrs: { deliveryState: "sent", error: "", messageId, sentAt: deps.now() } }], {
1964
+ mutationId: `email-confirmed:${id7}:${attempt}`,
1961
1965
  guards: [{
1962
1966
  ns: "emailLog",
1963
- id: id6,
1967
+ id: id7,
1964
1968
  exists: true,
1965
1969
  equals: { deliveryState: "sending", deliveryAttempt: attempt }
1966
1970
  }]
@@ -2145,8 +2149,8 @@ async function hash(value) {
2145
2149
  async function membershipDiscountCouponId(input, policyId) {
2146
2150
  return policyId ? `odla_m_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.skuId, policyId])}` : void 0;
2147
2151
  }
2148
- async function load(db, ns, id6) {
2149
- return (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
2152
+ async function load(db, ns, id7) {
2153
+ return (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
2150
2154
  }
2151
2155
  async function membershipOfferIdForRequest(input) {
2152
2156
  return `offer_${await hash([input.appId, input.chapterId, input.environment, input.runtime, input.applicationId, input.requestId])}`;
@@ -2212,6 +2216,7 @@ async function previewMembershipCommercialOffer(db, input) {
2212
2216
  ...contract.founding && contract.discountPolicy ? { cohortLabel: contract.discountPolicy.cohortLabel } : {},
2213
2217
  dueTodayCents: contract.dueTodayCents,
2214
2218
  renewalCents: contract.renewalCents,
2219
+ ...offering.seatPriceCents !== void 0 ? { seatPriceCents: offering.seatPriceCents } : {},
2215
2220
  ...discount ? { discountDuration: discount.duration === "once" ? "first_term" : "while_active" } : {},
2216
2221
  ...contract.initialTermEndsAt !== void 0 ? { initialTermEndsAt: contract.initialTermEndsAt } : {}
2217
2222
  };
@@ -2280,20 +2285,20 @@ async function issueMembershipCommercialOffer(db, input) {
2280
2285
 
2281
2286
  // src/commercial-evidence-guard.ts
2282
2287
  async function commercialEvidenceGuard(db, eventId, digest, code = "membership_payment_evidence_quarantined") {
2283
- const ns = "networkCommercialExceptions", id6 = `network-commercial-exception:${eventId}:collision`;
2284
- const row2 = (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
2285
- if (!row2) return { ns, id: id6, exists: false };
2288
+ const ns = "networkCommercialExceptions", id7 = `network-commercial-exception:${eventId}:collision`;
2289
+ const row2 = (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
2290
+ if (!row2) return { ns, id: id7, exists: false };
2286
2291
  if (row2.status !== "resolved" || row2.resolvedDigest !== digest || typeof digest !== "string") {
2287
2292
  throw Object.assign(new Error(code), { code });
2288
2293
  }
2289
- return { ns, id: id6, exists: true, equals: { status: "resolved", resolvedDigest: digest, updatedAt: row2.updatedAt } };
2294
+ return { ns, id: id7, exists: true, equals: { status: "resolved", resolvedDigest: digest, updatedAt: row2.updatedAt } };
2290
2295
  }
2291
2296
 
2292
2297
  // src/membership-authority-state.ts
2293
2298
  var membershipFail = (code) => {
2294
2299
  throw Object.assign(new Error(code), { code });
2295
2300
  };
2296
- var membershipLoad = async (db, ns, id6) => (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
2301
+ var membershipLoad = async (db, ns, id7) => (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
2297
2302
  var membershipScopeKeys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId"];
2298
2303
  var membershipScope = (input) => Object.fromEntries(membershipScopeKeys.map((key3) => [key3, input[key3]]));
2299
2304
  function requireMembershipScope(row2, input) {
@@ -2304,18 +2309,18 @@ async function loadMembershipAuthorityState(db, input) {
2304
2309
  if (!row2 || row2.mode !== "authoritative" || row2.chapterId !== input.chapterId || row2.environment !== input.environment || !Number.isSafeInteger(row2.revision) || Number(row2.revision) < 1) return membershipFail("membership_authority_unavailable");
2305
2310
  return row2;
2306
2311
  }
2307
- var membershipUpdate = (ns, id6, attrs) => ({ t: "update", ns, id: id6, attrs });
2312
+ var membershipUpdate = (ns, id7, attrs) => ({ t: "update", ns, id: id7, attrs });
2308
2313
  var membershipClock = (now) => {
2309
2314
  if (!Number.isSafeInteger(now) || now < 0) membershipFail("invalid_membership_clock");
2310
2315
  };
2311
2316
 
2312
2317
  // src/membership-restart-payment.ts
2313
2318
  async function membershipRestartPayment(db, target, member, now) {
2314
- const id6 = String(member.id), restart = await membershipLoad(db, "membershipRestart", id6);
2319
+ const id7 = String(member.id), restart = await membershipLoad(db, "membershipRestart", id7);
2315
2320
  requireMembershipScope(restart, target);
2316
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id6}:primary`);
2321
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id7}:primary`);
2317
2322
  requireMembershipScope(entitlement, target);
2318
- if (entitlement.membershipId !== id6 || entitlement.kind !== "primary") membershipFail("membership_restart_conflict");
2323
+ if (entitlement.membershipId !== id7 || entitlement.kind !== "primary") membershipFail("membership_restart_conflict");
2319
2324
  if (restart.status !== "ready" || member.status !== "restarting" || member.restartId !== restart.restartId || member.memberNumber !== restart.memberNumber || !Number.isSafeInteger(member.journeyApprovedAt)) membershipFail("membership_restart_conflict");
2320
2325
  return {
2321
2326
  restartId: String(restart.restartId),
@@ -2332,49 +2337,117 @@ async function membershipRestartPayment(db, target, member, now) {
2332
2337
  graceEndsAt: 0
2333
2338
  },
2334
2339
  ops: [
2335
- membershipUpdate("membershipRestart", id6, { status: "complete" }),
2336
- { t: "retract", ns: "networkMembership", id: id6, attrs: ["refundRequestedAt", "cancellationRequestedAt", "discountPolicyVersionId", "discountSnapshot"] }
2340
+ membershipUpdate("membershipRestart", id7, { status: "complete" }),
2341
+ { t: "retract", ns: "networkMembership", id: id7, attrs: ["refundRequestedAt", "cancellationRequestedAt", "discountPolicyVersionId", "discountSnapshot"] }
2337
2342
  ],
2338
2343
  guards: [
2339
- { ns: "networkMembership", id: id6, exists: true, equals: { status: "restarting", revision: member.revision, restartId: restart.restartId } },
2340
- { ns: "membershipRestart", id: id6, exists: true, equals: { status: "ready", restartId: restart.restartId } },
2344
+ { ns: "networkMembership", id: id7, exists: true, equals: { status: "restarting", revision: member.revision, restartId: restart.restartId } },
2345
+ { ns: "membershipRestart", id: id7, exists: true, equals: { status: "ready", restartId: restart.restartId } },
2341
2346
  { ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision, status: entitlement.status } }
2342
2347
  ]
2343
2348
  };
2344
2349
  }
2345
2350
 
2351
+ // src/checkout-named-seat.ts
2352
+ function checkoutNamedSeatSelection(value, purchaserEmail) {
2353
+ const row2 = value;
2354
+ const name = typeof row2?.recipientName === "string" ? row2.recipientName.trim().replace(/\s+/g, " ") : "";
2355
+ const email = normalizeChapterEmail(typeof row2?.recipientEmail === "string" ? row2.recipientEmail : "");
2356
+ const purchaser = normalizeChapterEmail(purchaserEmail);
2357
+ 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) {
2358
+ throw Object.assign(new Error("Enter a different recipient's name and email."), { code: "invalid_named_seat_recipient" });
2359
+ }
2360
+ return { recipientName: name, recipientEmail: email };
2361
+ }
2362
+ async function checkoutNamedSeatIdentity(target, email) {
2363
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([
2364
+ target.appId,
2365
+ target.chapterId,
2366
+ target.environment,
2367
+ target.runtime,
2368
+ email
2369
+ ])));
2370
+ return {
2371
+ seatId: (await membershipOfferIdForRequest({ ...target, requestId: "named_seat" })).replace(/^offer_/, "seat_"),
2372
+ membershipId: (await membershipOfferIdForRequest({ ...target, requestId: "checkout_binding" })).replace(/^offer_/, "membership_"),
2373
+ recipientClaimId: `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`
2374
+ };
2375
+ }
2376
+
2346
2377
  // src/membership-checkout-binding.ts
2347
2378
  var keys = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId", "offerId", "offerDigest", "quoteDigest", "priceId"];
2348
2379
  var reject2 = (code) => {
2349
2380
  throw Object.assign(new Error(code), { code });
2350
2381
  };
2351
- var load2 = async (db, ns, id6) => (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
2382
+ var load2 = async (db, ns, id7) => (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
2352
2383
  async function membershipCheckoutBindingId(input) {
2353
2384
  return (await membershipOfferIdForRequest({ ...input, requestId: "checkout_binding" })).replace(/^offer_/, "checkout_");
2354
2385
  }
2355
2386
  async function verifyMembershipCheckoutBinding(value, input) {
2356
2387
  const row2 = value;
2357
2388
  if (!row2 || typeof row2 !== "object" || row2.id !== await membershipCheckoutBindingId(input) || keys.some((key3) => row2[key3] !== input[key3]) || !Number.isSafeInteger(row2.boundAt) || row2.boundAt < 0) return reject2("membership_checkout_conflict");
2358
- return { id: row2.id, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: row2.boundAt };
2389
+ if (Boolean(row2.namedSeat) !== Boolean(input.namedSeat)) return reject2("membership_checkout_conflict");
2390
+ if (input.namedSeat) {
2391
+ const expected = {
2392
+ ...checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail),
2393
+ purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail)
2394
+ };
2395
+ const identity2 = await checkoutNamedSeatIdentity(input, expected.recipientEmail);
2396
+ 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");
2397
+ }
2398
+ return {
2399
+ id: row2.id,
2400
+ ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])),
2401
+ boundAt: row2.boundAt,
2402
+ ...row2.namedSeat ? { namedSeat: row2.namedSeat } : {}
2403
+ };
2359
2404
  }
2360
- async function bindMembershipCheckout(db, input, now) {
2405
+ async function bindMembershipCheckout(db, input, now, seatPriceId) {
2361
2406
  if (!Number.isSafeInteger(now) || now < 0 || !/^[a-f0-9]{64}$/.test(input.offerDigest) || !/^[a-f0-9]{64}$/.test(input.quoteDigest) || !/^price_[A-Za-z0-9_]+$/.test(input.priceId)) return reject2("invalid_membership_checkout");
2362
2407
  const stateId = `${input.chapterId}:${input.environment}`;
2363
2408
  const state = await load2(db, "membershipAuthorityState", stateId);
2364
2409
  if (!state || state.mode !== "authoritative" || state.chapterId !== input.chapterId || state.environment !== input.environment || !Number.isSafeInteger(state.revision) || Number(state.revision) < 1) return reject2("membership_authority_unavailable");
2365
- const id6 = await membershipCheckoutBindingId(input);
2366
- const existing = await load2(db, "membershipOfferCheckout", id6);
2410
+ const id7 = await membershipCheckoutBindingId(input);
2411
+ const existing = await load2(db, "membershipOfferCheckout", id7);
2367
2412
  if (existing) return verifyMembershipCheckoutBinding(existing, input);
2368
2413
  const row2 = await load2(db, "membershipCheckoutOffer", input.offerId);
2369
2414
  const offer = await verifyIssuedMembershipOffer(row2, { ...input, now, lifetimeMs: 6e4 });
2370
2415
  if (offer.digest !== input.offerDigest || offer.expiresAt <= now || offer.issuedAt > now) return reject2("membership_offer_expired");
2371
- const restart = await load2(db, "membershipRestart", id6.replace(/^checkout_/, "membership_"));
2372
- const receipt = { id: id6, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: now };
2416
+ const restart = await load2(db, "membershipRestart", id7.replace(/^checkout_/, "membership_"));
2417
+ let namedSeat;
2418
+ if (input.namedSeat) {
2419
+ if (!seatPriceId || !/^price_[A-Za-z0-9_]+$/.test(seatPriceId) || seatPriceId === input.priceId || restart) return reject2("named_seat_purchase_unavailable");
2420
+ const selection = checkoutNamedSeatSelection(input.namedSeat, input.namedSeat.purchaserEmail);
2421
+ namedSeat = {
2422
+ ...selection,
2423
+ purchaserEmail: normalizeChapterEmail(input.namedSeat.purchaserEmail),
2424
+ ...await checkoutNamedSeatIdentity(input, selection.recipientEmail),
2425
+ priceId: seatPriceId,
2426
+ amountCents: offer.contract.offering.seatPriceCents
2427
+ };
2428
+ }
2429
+ const receipt = { id: id7, ...Object.fromEntries(keys.map((key3) => [key3, input[key3]])), boundAt: now, ...namedSeat ? { namedSeat } : {} };
2373
2430
  try {
2374
- await db.transact([{ t: "update", ns: "membershipOfferCheckout", id: id6, attrs: receipt }], {
2375
- mutationId: `membership-checkout:${id6}${restart?.restartId ? `:${restart.restartId}` : ""}`,
2431
+ await db.transact([
2432
+ { t: "update", ns: "membershipOfferCheckout", id: id7, attrs: receipt },
2433
+ ...namedSeat ? [{ t: "update", ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, attrs: {
2434
+ id: namedSeat.recipientClaimId,
2435
+ appId: input.appId,
2436
+ chapterId: input.chapterId,
2437
+ environment: input.environment,
2438
+ runtime: input.runtime,
2439
+ seatId: namedSeat.seatId,
2440
+ recipientEmailNormalized: namedSeat.recipientEmail,
2441
+ createdAt: now
2442
+ } }] : []
2443
+ ], {
2444
+ mutationId: `membership-checkout:${id7}${restart?.restartId ? `:${restart.restartId}` : ""}`,
2376
2445
  guards: [
2377
- { ns: "membershipOfferCheckout", id: id6, exists: false },
2446
+ { ns: "membershipOfferCheckout", id: id7, exists: false },
2447
+ ...namedSeat ? [
2448
+ { ns: "networkSeat", id: namedSeat.seatId, exists: false },
2449
+ { ns: "networkSeatRecipient", id: namedSeat.recipientClaimId, exists: false }
2450
+ ] : [],
2378
2451
  { ns: "membershipCheckoutOffer", id: input.offerId, exists: true, equals: { digest: input.offerDigest } },
2379
2452
  { ns: "membershipAuthorityState", id: stateId, exists: true, equals: { mode: "authoritative", revision: state.revision } }
2380
2453
  ]
@@ -2382,11 +2455,78 @@ async function bindMembershipCheckout(db, input, now) {
2382
2455
  } catch (error) {
2383
2456
  if (error.code !== "transact_guard_failed") throw error;
2384
2457
  }
2385
- const saved = await load2(db, "membershipOfferCheckout", id6);
2458
+ const saved = await load2(db, "membershipOfferCheckout", id7);
2386
2459
  if (!saved) return reject2("membership_binding_unconfirmed");
2387
2460
  return verifyMembershipCheckoutBinding(saved, input);
2388
2461
  }
2389
2462
 
2463
+ // src/membership-checkout-seat-payment.ts
2464
+ function checkoutSeatPaymentOperations(target, seat, input) {
2465
+ const { now, paidAt, endsAt } = input;
2466
+ const included = seat.amountCents === 0, entitlementId = `${seat.seatId}:entitlement`;
2467
+ const effectId = `${seat.seatId}:${included ? "invite" : "attach-renewal"}`;
2468
+ return [
2469
+ membershipUpdate("networkSeat", seat.seatId, {
2470
+ id: seat.seatId,
2471
+ ...membershipScope(target),
2472
+ primaryMembershipId: seat.membershipId,
2473
+ purchaserEmailNormalized: seat.purchaserEmail,
2474
+ recipientName: seat.recipientName,
2475
+ recipientEmailNormalized: seat.recipientEmail,
2476
+ recipientClaimId: seat.recipientClaimId,
2477
+ priceId: seat.priceId,
2478
+ primaryPriceId: input.primaryPriceId,
2479
+ amountCents: seat.amountCents,
2480
+ currency: input.offering.currency,
2481
+ interval: input.offering.interval,
2482
+ providerCustomerId: input.customerId,
2483
+ providerSubscriptionId: input.subscriptionId,
2484
+ combinedInvoice: { invoiceId: input.invoiceId, totalCents: input.invoiceTotalCents },
2485
+ status: included ? "pending_acceptance" : "renewal_pending",
2486
+ preparedAt: input.boundAt,
2487
+ purchasedAt: paidAt,
2488
+ termStartedAt: paidAt,
2489
+ termEndsAt: endsAt,
2490
+ acceptanceEndsAt: endsAt,
2491
+ seatPurchaseEndsAt: seatPurchaseDeadline(input.offering, { initialTermStartedAt: paidAt, termEndsAt: endsAt }),
2492
+ offeringSnapshot: input.offering,
2493
+ paymentEventId: input.eventId,
2494
+ ...included ? { entitlementId } : {},
2495
+ revision: 1,
2496
+ createdAt: now,
2497
+ updatedAt: now
2498
+ }),
2499
+ ...included ? [membershipUpdate("networkEntitlement", entitlementId, {
2500
+ id: entitlementId,
2501
+ ...membershipScope(target),
2502
+ membershipId: seat.membershipId,
2503
+ seatId: seat.seatId,
2504
+ kind: "named_seat",
2505
+ status: "pending_acceptance",
2506
+ termStartedAt: paidAt,
2507
+ termEndsAt: endsAt,
2508
+ offeringSnapshot: input.offering,
2509
+ sourceEventId: input.eventId,
2510
+ revision: 1,
2511
+ createdAt: now,
2512
+ updatedAt: now
2513
+ })] : [],
2514
+ membershipUpdate("membershipEffect", effectId, {
2515
+ id: effectId,
2516
+ chapterId: target.chapterId,
2517
+ environment: target.environment,
2518
+ runtime: target.runtime,
2519
+ membershipId: seat.membershipId,
2520
+ seatId: seat.seatId,
2521
+ kind: included ? "invite_named_recipient" : "attach_seat_renewal",
2522
+ status: "pending",
2523
+ attempts: 0,
2524
+ createdAt: now,
2525
+ updatedAt: now
2526
+ })
2527
+ ];
2528
+ }
2529
+
2390
2530
  // src/membership-payment-authority.ts
2391
2531
  async function networkMembershipId(input) {
2392
2532
  return (await membershipCheckoutBindingId(input)).replace(/^checkout_/, "membership_");
@@ -2402,11 +2542,14 @@ async function confirmMembershipPayment(db, input, now) {
2402
2542
  { ...input, offerId: String(binding.offerId), now, lifetimeMs: 6e4 }
2403
2543
  );
2404
2544
  if (binding.offerDigest !== offer.digest) membershipFail("membership_checkout_conflict");
2545
+ const { namedSeat } = await verifyMembershipCheckoutBinding(binding, binding);
2546
+ if (namedSeat && namedSeat.amountCents !== offer.contract.offering.seatPriceCents) membershipFail("membership_checkout_conflict");
2547
+ const totalCents = offer.contract.dueTodayCents + (namedSeat?.amountCents ?? 0);
2405
2548
  const eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
2406
2549
  if (!eventRow) membershipFail("membership_payment_evidence_pending");
2407
2550
  const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow.digest);
2408
2551
  const fact = eventRow.fact;
2409
- if (!fact || !fact.identity || !/^[a-f0-9]{64}$/.test(String(eventRow.digest)) || fact.digest !== eventRow.digest || fact.eventId !== input.eventId || fact.eventType !== "invoice.payment_succeeded" || fact.objectKind !== "invoice" || fact.billingReason !== "subscription_create" || fact.livemode !== (input.environment === "prod") || fact.status !== "paid" || fact.applicationId !== input.applicationId || fact.subscriptionId !== input.providerSubscriptionId || fact.customerId !== input.providerCustomerId || !/^in_[A-Za-z0-9_]+$/.test(fact.invoiceId ?? "") || fact.currency !== offer.contract.offering.currency || fact.invoiceTotalCents !== offer.contract.dueTodayCents || fact.amountPaidCents !== offer.contract.dueTodayCents || Object.entries({
2552
+ if (!fact || !fact.identity || !/^[a-f0-9]{64}$/.test(String(eventRow.digest)) || fact.digest !== eventRow.digest || fact.eventId !== input.eventId || fact.eventType !== "invoice.payment_succeeded" || fact.objectKind !== "invoice" || fact.billingReason !== "subscription_create" || fact.livemode !== (input.environment === "prod") || fact.status !== "paid" || fact.applicationId !== input.applicationId || fact.subscriptionId !== input.providerSubscriptionId || fact.customerId !== input.providerCustomerId || !/^in_[A-Za-z0-9_]+$/.test(fact.invoiceId ?? "") || fact.currency !== offer.contract.offering.currency || fact.invoiceTotalCents !== totalCents || fact.amountPaidCents !== totalCents || Object.entries({
2410
2553
  appId: input.appId,
2411
2554
  chapterId: input.chapterId,
2412
2555
  environment: input.environment,
@@ -2418,14 +2561,14 @@ async function confirmMembershipPayment(db, input, now) {
2418
2561
  const paidAt = fact.invoicePaidAt;
2419
2562
  const endsAt = offer.contract.initialTermEndsAt ?? fact.invoicePeriodEndsAt;
2420
2563
  if (!Number.isSafeInteger(paidAt) || Number(paidAt) < Math.floor(Number(binding.boundAt) / 1e3) * 1e3 || Number(paidAt) > now + 3e4 || !Number.isSafeInteger(endsAt) || Number(endsAt) <= Number(paidAt)) membershipFail("membership_payment_period_mismatch");
2421
- const id6 = await networkMembershipId(input), entitlementId = `${id6}:primary`;
2422
- let member = await membershipLoad(db, "networkMembership", id6);
2564
+ const id7 = await networkMembershipId(input), entitlementId = `${id7}:primary`;
2565
+ let member = await membershipLoad(db, "networkMembership", id7);
2423
2566
  if (!member || member.status === "restarting") {
2424
2567
  const offering = offer.contract.offering;
2425
2568
  const restart = member ? await membershipRestartPayment(db, input, member, now) : null;
2426
2569
  const attrs = {
2427
- id: id6,
2428
- membershipId: id6,
2570
+ id: id7,
2571
+ membershipId: id7,
2429
2572
  ...membershipScope(input),
2430
2573
  baseSkuId: input.skuId,
2431
2574
  status: "paid_pending_approval",
@@ -2441,8 +2584,9 @@ async function confirmMembershipPayment(db, input, now) {
2441
2584
  founding: offer.contract.founding,
2442
2585
  cohort: offer.contract.founding ? offer.contract.discountPolicy.cohortLabel : "standard",
2443
2586
  baseAmountCents: offering.basePriceCents,
2444
- amountCents: offer.contract.dueTodayCents,
2587
+ amountCents: totalCents,
2445
2588
  renewalAmountCents: offer.contract.renewalCents,
2589
+ ...namedSeat ? { namedSeatId: namedSeat.seatId } : {},
2446
2590
  currency: offering.currency,
2447
2591
  interval: offering.interval,
2448
2592
  initialTermStartedAt: paidAt,
@@ -2459,11 +2603,11 @@ async function confirmMembershipPayment(db, input, now) {
2459
2603
  };
2460
2604
  try {
2461
2605
  await db.transact([
2462
- membershipUpdate("networkMembership", id6, attrs),
2606
+ membershipUpdate("networkMembership", id7, attrs),
2463
2607
  membershipUpdate("networkEntitlement", entitlementId, {
2464
2608
  id: entitlementId,
2465
2609
  ...membershipScope(input),
2466
- membershipId: id6,
2610
+ membershipId: id7,
2467
2611
  kind: "primary",
2468
2612
  status: restart?.attrs.status === "active" ? "active" : "pending_approval",
2469
2613
  termStartedAt: paidAt,
@@ -2475,23 +2619,45 @@ async function confirmMembershipPayment(db, input, now) {
2475
2619
  updatedAt: now,
2476
2620
  ...restart?.entitlementAttrs
2477
2621
  }),
2478
- ...restart?.ops ?? []
2479
- ], { mutationId: `membership-paid:${id6}${restart ? `:${restart.restartId}` : ""}`, guards: [
2480
- ...restart?.guards ?? [{ ns: "networkMembership", id: id6, exists: false }],
2622
+ ...restart?.ops ?? [],
2623
+ ...namedSeat ? checkoutSeatPaymentOperations(input, namedSeat, {
2624
+ offering,
2625
+ primaryPriceId: String(binding.priceId),
2626
+ invoiceId: fact.invoiceId,
2627
+ invoiceTotalCents: totalCents,
2628
+ subscriptionId: input.providerSubscriptionId,
2629
+ customerId: input.providerCustomerId,
2630
+ eventId: input.eventId,
2631
+ paidAt: Number(paidAt),
2632
+ endsAt: Number(endsAt),
2633
+ boundAt: Number(binding.boundAt),
2634
+ now
2635
+ }) : []
2636
+ ], { mutationId: `membership-paid:${id7}${restart ? `:${restart.restartId}` : ""}`, guards: [
2637
+ ...restart?.guards ?? [{ ns: "networkMembership", id: id7, exists: false }],
2638
+ ...namedSeat ? [
2639
+ { ns: "networkSeat", id: namedSeat.seatId, exists: false },
2640
+ {
2641
+ ns: "networkSeatRecipient",
2642
+ id: namedSeat.recipientClaimId,
2643
+ exists: true,
2644
+ equals: { seatId: namedSeat.seatId, recipientEmailNormalized: namedSeat.recipientEmail }
2645
+ }
2646
+ ] : [],
2481
2647
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
2482
- { ns: "membershipOfferCheckout", id: String(binding.id), exists: true, equals: { offerDigest: offer.digest } },
2648
+ { ns: "membershipOfferCheckout", id: String(binding.id), exists: true, equals: { offerDigest: offer.digest, quoteDigest: binding.quoteDigest } },
2483
2649
  { ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
2484
2650
  evidenceGuard
2485
2651
  ] });
2486
2652
  } catch (error) {
2487
2653
  if (error.code !== "transact_guard_failed") throw error;
2488
2654
  }
2489
- member = await membershipLoad(db, "networkMembership", id6);
2655
+ member = await membershipLoad(db, "networkMembership", id7);
2490
2656
  }
2491
2657
  requireMembershipScope(member, input);
2492
2658
  if (member.purchaseOfferDigest !== offer.digest || member.providerInitialInvoiceId !== fact.invoiceId || member.providerSubscriptionId !== fact.subscriptionId || member.providerCustomerId !== fact.customerId) membershipFail("membership_purchase_conflict");
2493
2659
  return {
2494
- membershipId: id6,
2660
+ membershipId: id7,
2495
2661
  status: String(member.status),
2496
2662
  cohort: String(member.cohort),
2497
2663
  initialTermStartedAt: Number(member.initialTermStartedAt),
@@ -2562,19 +2728,19 @@ async function stopPreviousSubscription(member, target, secret) {
2562
2728
  async function prepareMembershipRestart(db, input, now, stripeSecret) {
2563
2729
  membershipClock(now);
2564
2730
  if (!/^sub_[A-Za-z0-9_]+$/.test(input.previousSubscriptionId)) membershipFail("membership_restart_conflict");
2565
- const id6 = await networkMembershipId(input), bindingId = await membershipCheckoutBindingId(input);
2566
- const restartId = `restart_${await stripePayloadDigest(JSON.stringify([id6, input.previousSubscriptionId]))}`;
2731
+ const id7 = await networkMembershipId(input), bindingId = await membershipCheckoutBindingId(input);
2732
+ const restartId = `restart_${await stripePayloadDigest(JSON.stringify([id7, input.previousSubscriptionId]))}`;
2567
2733
  const state = await loadMembershipAuthorityState(db, input);
2568
- const previous = await membershipLoad(db, "networkMembership", id6);
2734
+ const previous = await membershipLoad(db, "networkMembership", id7);
2569
2735
  requireMembershipScope(previous, input);
2570
2736
  const receipt = {
2571
2737
  applicationId: input.applicationId,
2572
- membershipId: id6,
2738
+ membershipId: id7,
2573
2739
  restartId,
2574
2740
  memberNumber: Number(previous.memberNumber),
2575
2741
  status: "ready"
2576
2742
  };
2577
- const existing = await membershipLoad(db, "membershipRestart", id6);
2743
+ const existing = await membershipLoad(db, "membershipRestart", id7);
2578
2744
  if (existing?.restartId === restartId && existing.status === "ready") {
2579
2745
  requireMembershipScope(existing, input);
2580
2746
  return receipt;
@@ -2590,33 +2756,33 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
2590
2756
  membershipUpdate("membershipPurchaseHistory", restartId, {
2591
2757
  id: restartId,
2592
2758
  ...membershipScope(input),
2593
- membershipId: id6,
2759
+ membershipId: id7,
2594
2760
  providerSubscriptionId: input.previousSubscriptionId,
2595
2761
  memberSnapshot: previous,
2596
2762
  bindingSnapshot: binding2,
2597
2763
  archivedAt: now
2598
2764
  }),
2599
- membershipUpdate("networkMembership", id6, { status: "restarting", restartId, revision: Number(previous.revision) + 1, updatedAt: now })
2765
+ membershipUpdate("networkMembership", id7, { status: "restarting", restartId, revision: Number(previous.revision) + 1, updatedAt: now })
2600
2766
  ], { mutationId: `membership-restart-reserve:${restartId}`, guards: [
2601
2767
  ...seats.guards,
2602
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: previous.revision, status: previous.status } },
2768
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: previous.revision, status: previous.status } },
2603
2769
  { ns: "membershipPurchaseHistory", id: restartId, exists: false },
2604
2770
  { ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding2.offerDigest } },
2605
2771
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
2606
2772
  ] });
2607
2773
  } else if (previous.restartId !== restartId) membershipFail("membership_restart_conflict");
2608
- const reserved = await membershipLoad(db, "networkMembership", id6);
2774
+ const reserved = await membershipLoad(db, "networkMembership", id7);
2609
2775
  requireMembershipScope(reserved, input);
2610
2776
  if (reserved.status !== "restarting" || reserved.restartId !== restartId) membershipFail("membership_restart_conflict");
2611
2777
  await stopPreviousSubscription(reserved, input, stripeSecret);
2612
2778
  const binding = await membershipLoad(db, "membershipOfferCheckout", bindingId);
2613
2779
  requireMembershipScope(binding, input);
2614
2780
  await db.transact([
2615
- membershipUpdate("membershipRestart", id6, { id: id6, ...membershipScope(input), ...receipt, previousSubscriptionId: input.previousSubscriptionId, createdAt: now }),
2781
+ membershipUpdate("membershipRestart", id7, { id: id7, ...membershipScope(input), ...receipt, previousSubscriptionId: input.previousSubscriptionId, createdAt: now }),
2616
2782
  { t: "delete", ns: "membershipOfferCheckout", id: bindingId }
2617
2783
  ], { mutationId: `membership-restart-ready:${restartId}`, guards: [
2618
- { ns: "membershipRestart", id: id6, exists: Boolean(existing), ...existing ? { equals: { restartId: existing.restartId, status: existing.status } } : {} },
2619
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: reserved.revision, status: "restarting", restartId } },
2784
+ { ns: "membershipRestart", id: id7, exists: Boolean(existing), ...existing ? { equals: { restartId: existing.restartId, status: existing.status } } : {} },
2785
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: reserved.revision, status: "restarting", restartId } },
2620
2786
  { ns: "membershipOfferCheckout", id: bindingId, exists: true, equals: { offerDigest: binding.offerDigest } }
2621
2787
  ] });
2622
2788
  return receipt;
@@ -2626,15 +2792,15 @@ async function prepareMembershipRestart(db, input, now, stripeSecret) {
2626
2792
  async function readMembershipAccess(db, input, now) {
2627
2793
  membershipClock(now);
2628
2794
  await loadMembershipAuthorityState(db, input);
2629
- const id6 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id6);
2630
- if (!member) return { membershipId: id6, allowed: false, status: "unpaid", accessEndsAt: 0, revision: 0 };
2795
+ const id7 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id7);
2796
+ if (!member) return { membershipId: id7, allowed: false, status: "unpaid", accessEndsAt: 0, revision: 0 };
2631
2797
  requireMembershipScope(member, input);
2632
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id6}:primary`);
2798
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id7}:primary`);
2633
2799
  requireMembershipScope(entitlement, input);
2634
2800
  const endsAt = member.status === "payment_grace" ? Number(member.graceEndsAt) : Number(member.termEndsAt);
2635
- const allowed2 = ["active", "cancel_at_term_end", "payment_grace"].includes(String(member.status)) && member.journeyApprovedAt !== void 0 && Number.isSafeInteger(member.memberNumber) && Number(member.memberNumber) > 0 && member.refundedCents === 0 && entitlement.status === "active" && entitlement.membershipId === id6 && entitlement.kind === "primary" && Number.isSafeInteger(endsAt) && endsAt > now && Number(member.termStartedAt) <= now;
2801
+ const allowed2 = ["active", "cancel_at_term_end", "payment_grace"].includes(String(member.status)) && member.journeyApprovedAt !== void 0 && Number.isSafeInteger(member.memberNumber) && Number(member.memberNumber) > 0 && member.refundedCents === 0 && entitlement.status === "active" && entitlement.membershipId === id7 && entitlement.kind === "primary" && Number.isSafeInteger(endsAt) && endsAt > now && Number(member.termStartedAt) <= now;
2636
2802
  return {
2637
- membershipId: id6,
2803
+ membershipId: id7,
2638
2804
  allowed: allowed2,
2639
2805
  status: String(member.status),
2640
2806
  accessEndsAt: Number.isSafeInteger(endsAt) ? endsAt : 0,
@@ -2752,7 +2918,7 @@ async function refundChapterNetworkMembership(db, env, ctx, app) {
2752
2918
  const target = await membershipTargetForIntent(intent, app, env, ctx);
2753
2919
  const receipt = await ctx.membershipAuthority.refundMembership(target, env);
2754
2920
  const offer = intent.membershipOffer;
2755
- if (receipt.membershipId !== await networkMembershipId(target) || receipt.status !== "refunded" || receipt.subscriptionCanceled !== true || receipt.refundedCents !== offer.contract?.dueTodayCents) throw new Error("membership refund unconfirmed");
2921
+ if (receipt.membershipId !== await networkMembershipId(target) || receipt.status !== "refunded" || receipt.subscriptionCanceled !== true || receipt.refundedCents !== Number(offer.contract?.dueTodayCents) + Number(intent.paymentQuote?.lineItems?.namedSeatCents ?? 0)) throw new Error("membership refund unconfirmed");
2756
2922
  return receipt;
2757
2923
  }
2758
2924
  async function renewChapterNetworkMembership(db, env, ctx, app, eventId) {
@@ -2803,14 +2969,14 @@ function facts(snapshots) {
2803
2969
  ["payment_intent.updated", snapshots.payments?.objects ?? []]
2804
2970
  ];
2805
2971
  return groups.flatMap(([eventType, objects]) => objects.map((object2) => {
2806
- const id6 = typeof object2.id === "string" ? object2.id : "invalid";
2972
+ const id7 = typeof object2.id === "string" ? object2.id : "invalid";
2807
2973
  return normalizeNetworkCommercialStripeEvent({
2808
- id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id6}`,
2974
+ id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id7}`,
2809
2975
  type: eventType,
2810
2976
  created: typeof object2.created === "number" ? object2.created : 0,
2811
2977
  livemode: object2.livemode === true,
2812
2978
  data: { object: object2 }
2813
- }, `stripe-snapshot:${eventType}:${id6}`);
2979
+ }, `stripe-snapshot:${eventType}:${id7}`);
2814
2980
  }));
2815
2981
  }
2816
2982
  var selected = (row2) => ({
@@ -2885,9 +3051,9 @@ async function chapterBillingSubscriptions(secretKey, chapterId, env) {
2885
3051
  // src/approve-application.ts
2886
3052
  async function approveApplication(input) {
2887
3053
  const { db, env, ctx } = input;
2888
- const id6 = String(input.app.id ?? "");
2889
- if (!id6) return { ok: false, status: 404, error: "not found" };
2890
- const app = (await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } })).applications?.[0];
3054
+ const id7 = String(input.app.id ?? "");
3055
+ if (!id7) return { ok: false, status: 404, error: "not found" };
3056
+ const app = (await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } })).applications?.[0];
2891
3057
  if (!app || !chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME)) {
2892
3058
  return { ok: false, status: 404, error: "not found" };
2893
3059
  }
@@ -2921,10 +3087,10 @@ async function approveApplication(input) {
2921
3087
  try {
2922
3088
  if (!duplicate) {
2923
3089
  const write = await db.transact(
2924
- [{ t: "update", ns: "applications", id: id6, attrs: { status: "approved", approvalEffectsPending: true, approvalEffectsOrigin: input.origin } }],
2925
- { mutationId: `approve:${id6}`, guards: [{
3090
+ [{ t: "update", ns: "applications", id: id7, attrs: { status: "approved", approvalEffectsPending: true, approvalEffectsOrigin: input.origin } }],
3091
+ { mutationId: `approve:${id7}`, guards: [{
2926
3092
  ns: "applications",
2927
- id: id6,
3093
+ id: id7,
2928
3094
  exists: true,
2929
3095
  equals: Object.fromEntries(guardFields.filter((key3) => app[key3] !== void 0).map((key3) => [key3, app[key3]])),
2930
3096
  absent: guardFields.filter((key3) => app[key3] === void 0)
@@ -2936,7 +3102,7 @@ async function approveApplication(input) {
2936
3102
  if (error.code !== "transact_guard_failed") throw error;
2937
3103
  return { ok: false, status: 409, error: "application changed; reload before approving" };
2938
3104
  }
2939
- const current = (await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } })).applications?.[0];
3105
+ const current = (await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } })).applications?.[0];
2940
3106
  if (!current || current.status !== "approved" || current.canceled === true || guardFields.filter((key3) => key3 !== "status").some((key3) => current[key3] !== app[key3])) {
2941
3107
  return { ok: false, status: 409, error: "application changed; approval effects were not applied" };
2942
3108
  }
@@ -2946,7 +3112,7 @@ async function approveApplication(input) {
2946
3112
  } catch {
2947
3113
  return { ok: false, status: 503, error: "network membership approval reconciliation pending" };
2948
3114
  }
2949
- const afterAuthority = (await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } })).applications?.[0];
3115
+ const afterAuthority = (await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } })).applications?.[0];
2950
3116
  if (!afterAuthority || afterAuthority.status !== "approved" || afterAuthority.canceled === true || guardFields.filter((key3) => key3 !== "status").some((key3) => afterAuthority[key3] !== app[key3])) {
2951
3117
  return { ok: false, status: 409, error: "application changed during network approval" };
2952
3118
  }
@@ -2959,7 +3125,7 @@ async function approveApplication(input) {
2959
3125
  if (!userId && typeof app.email === "string") {
2960
3126
  const found = await clerkGetUserByEmail(secret, app.email, fetch, clerkRoleMetadata(ctx.chapter.auth));
2961
3127
  userId = found?.id ?? null;
2962
- if (userId) await db.transact([{ t: "update", ns: "applications", id: id6, attrs: { clerkUserId: userId } }]);
3128
+ if (userId) await db.transact([{ t: "update", ns: "applications", id: id7, attrs: { clerkUserId: userId } }]);
2963
3129
  }
2964
3130
  if (userId) {
2965
3131
  const user = await clerkGetUser(secret, userId, fetch, clerkRoleMetadata(ctx.chapter.auth));
@@ -2981,8 +3147,8 @@ async function approveApplication(input) {
2981
3147
  template: send,
2982
3148
  to: app.email,
2983
3149
  vars: { firstName: String(app.firstName ?? ""), membersUrl: `${input.origin}/members/` },
2984
- applicationId: id6,
2985
- dedupeKey: `approve:${id6}`
3150
+ applicationId: id7,
3151
+ dedupeKey: `approve:${id7}`
2986
3152
  }
2987
3153
  );
2988
3154
  emailLogged = result.sent;
@@ -2990,10 +3156,10 @@ async function approveApplication(input) {
2990
3156
  return { ok: false, status: 503, error: "approval notice reconciliation pending", rolePromoted, emailLogged };
2991
3157
  }
2992
3158
  }
2993
- await db.transact([{ t: "update", ns: "applications", id: id6, attrs: { approvalEffectsPending: false } }], {
3159
+ await db.transact([{ t: "update", ns: "applications", id: id7, attrs: { approvalEffectsPending: false } }], {
2994
3160
  guards: [{
2995
3161
  ns: "applications",
2996
- id: id6,
3162
+ id: id7,
2997
3163
  exists: true,
2998
3164
  equals: { status: "approved", ...app.canceled !== void 0 ? { canceled: app.canceled } : {} },
2999
3165
  ...app.canceled === void 0 ? { absent: ["canceled"] } : {}
@@ -3022,6 +3188,48 @@ async function revokeRefundedApplicationRole(db, env, ctx, applicationId) {
3022
3188
  }
3023
3189
  }
3024
3190
 
3191
+ // src/payment-receipt.ts
3192
+ async function retainPaymentReceipt(db, intent, rawBody) {
3193
+ const quote = intent.paymentQuote;
3194
+ if (!quote || intent.paymentReceipt) return;
3195
+ const event2 = JSON.parse(rawBody), invoice = event2.data?.object;
3196
+ const paidAt = invoice?.status_transitions?.paid_at ?? event2.created;
3197
+ if (!/^in_[A-Za-z0-9_]+$/.test(invoice?.id ?? "") || !Number.isSafeInteger(paidAt) || paidAt <= 0) return;
3198
+ const receipt = {
3199
+ invoiceId: invoice.id,
3200
+ paidAt: paidAt * 1e3,
3201
+ currency: quote.currency,
3202
+ tierName: quote.tierName,
3203
+ merchant: quote.merchantDisclosureText,
3204
+ membershipCents: quote.lineItems.dueTodayCents - (quote.lineItems.namedSeatCents ?? 0),
3205
+ totalCents: quote.lineItems.dueTodayCents,
3206
+ ...quote.namedSeat ? { namedSeat: { recipientName: quote.namedSeat.recipientName, amountCents: quote.lineItems.namedSeatCents } } : {}
3207
+ };
3208
+ await db.transact([{ t: "update", ns: "subscriptionCheckoutIntents", id: String(intent.id), attrs: { paymentReceipt: receipt } }], {
3209
+ mutationId: `payment-receipt:${invoice.id}`,
3210
+ guards: [{
3211
+ ns: "subscriptionCheckoutIntents",
3212
+ id: String(intent.id),
3213
+ exists: true,
3214
+ equals: { providerSubscriptionId: intent.providerSubscriptionId, providerCustomerId: intent.providerCustomerId },
3215
+ absent: ["paymentReceipt"]
3216
+ }]
3217
+ });
3218
+ }
3219
+ function paymentReceiptText(receipt, chapterName) {
3220
+ const money = (cents) => new Intl.NumberFormat("en-US", { style: "currency", currency: receipt.currency }).format(cents / 100);
3221
+ return [
3222
+ chapterName,
3223
+ "Payment receipt",
3224
+ `Invoice: ${receipt.invoiceId}`,
3225
+ `Paid: ${new Intl.DateTimeFormat("en-US", { dateStyle: "long", timeZone: "UTC" }).format(receipt.paidAt)}`,
3226
+ receipt.merchant,
3227
+ `${receipt.tierName}: ${money(receipt.membershipCents)}`,
3228
+ ...receipt.namedSeat ? [`Additional member \u2014 ${receipt.namedSeat.recipientName}: ${receipt.namedSeat.amountCents === 0 ? "Included \u2014 " : ""}${money(receipt.namedSeat.amountCents)}`] : [],
3229
+ `Total paid: ${money(receipt.totalCents)}`
3230
+ ].filter(Boolean).join("\n");
3231
+ }
3232
+
3025
3233
  // src/payment-notifications.ts
3026
3234
  var groupFor = async (db, app) => {
3027
3235
  const result = await db.query({ groups: { $: { where: { id: String(app.groupId ?? "") }, limit: 1 } } });
@@ -3215,10 +3423,10 @@ async function redeemGiftClaim(db, claim, beneficiary, now) {
3215
3423
  tierId: target.tierId,
3216
3424
  ...typeof existing?.clerkUserId === "string" ? { clerkUserId: existing.clerkUserId } : {}
3217
3425
  };
3218
- const id6 = existing ? String(existing.id) : `gift-application:${String(claim.id)}`;
3426
+ const id7 = existing ? String(existing.id) : `gift-application:${String(claim.id)}`;
3219
3427
  const status2 = existing && existing.status !== "submitted" ? String(existing.status) : "paid_pending_vetting";
3220
- ops.push({ t: "update", ns: "applications", id: id6, attrs: {
3221
- id: id6,
3428
+ ops.push({ t: "update", ns: "applications", id: id7, attrs: {
3429
+ id: id7,
3222
3430
  groupId: chapterId,
3223
3431
  email: beneficiary.email,
3224
3432
  emailNormalized,
@@ -3229,12 +3437,12 @@ async function redeemGiftClaim(db, claim, beneficiary, now) {
3229
3437
  clerkUserId: beneficiary.userId,
3230
3438
  ...existing ? {} : { createdAt: now }
3231
3439
  } });
3232
- result = { kind: "application", id: id6 };
3440
+ result = { kind: "application", id: id7 };
3233
3441
  } else {
3234
3442
  if (beneficiary.applicationId) return null;
3235
- const id6 = `gift-entitlement:${String(claim.id)}`;
3236
- ops.push({ t: "update", ns: "giftEntitlements", id: id6, attrs: {
3237
- id: id6,
3443
+ const id7 = `gift-entitlement:${String(claim.id)}`;
3444
+ ops.push({ t: "update", ns: "giftEntitlements", id: id7, attrs: {
3445
+ id: id7,
3238
3446
  claimId: String(claim.id),
3239
3447
  orderId: String(claim.orderId),
3240
3448
  chapterId,
@@ -3244,7 +3452,7 @@ async function redeemGiftClaim(db, claim, beneficiary, now) {
3244
3452
  active: true,
3245
3453
  createdAt: now
3246
3454
  } });
3247
- result = { kind: "entitlement", id: id6 };
3455
+ result = { kind: "entitlement", id: id7 };
3248
3456
  }
3249
3457
  ops.unshift({ t: "update", ns: "giftClaims", id: String(claim.id), attrs: {
3250
3458
  status: "redeemed",
@@ -3418,9 +3626,9 @@ async function storeStripeReceipt(db, input) {
3418
3626
  return "stored";
3419
3627
  }
3420
3628
  if (existing) return existing.digest === input.digest || existing.digest === input.legacyDigest ? "replayed" : "collision";
3421
- const id6 = `stripe-event:${input.eventId}`;
3422
- const write = await db.transact([{ t: "update", ns: "stripeEventReceipts", id: id6, attrs: {
3423
- id: id6,
3629
+ const id7 = `stripe-event:${input.eventId}`;
3630
+ const write = await db.transact([{ t: "update", ns: "stripeEventReceipts", id: id7, attrs: {
3631
+ id: id7,
3424
3632
  eventId: input.eventId,
3425
3633
  digest: input.digest,
3426
3634
  disposition: input.disposition,
@@ -3584,6 +3792,7 @@ async function reconcileChapterStripeEvent(input, env, ctx) {
3584
3792
  } catch {
3585
3793
  return json({ error: "network membership payment reconciliation pending" }, 503);
3586
3794
  }
3795
+ if (intent) await retainPaymentReceipt(db, intent, rawBody);
3587
3796
  }
3588
3797
  }
3589
3798
  const invoiceEvent = event2.kind === "first_payment" || event2.kind === "renewal" || event2.kind === "renewal_failed";
@@ -3652,9 +3861,9 @@ var EVENT_ID = /^evt_[A-Za-z0-9_]{1,150}$/;
3652
3861
  var fail3 = (message, code) => Object.assign(new Error(message), { code });
3653
3862
  async function resolveNetworkCommercialCollision(db, sources, input, now = Date.now(), alerts) {
3654
3863
  if (!EVENT_ID.test(input.eventId)) throw fail3("invalid event reference", "invalid_event");
3655
- const id6 = exceptionId(input.eventId, "collision"), reviewId = `resolve:${input.mutationId}`;
3864
+ const id7 = exceptionId(input.eventId, "collision"), reviewId = `resolve:${input.mutationId}`;
3656
3865
  const found = await db.query({
3657
- networkCommercialExceptions: { $: { where: { id: id6 }, limit: 1 } },
3866
+ networkCommercialExceptions: { $: { where: { id: id7 }, limit: 1 } },
3658
3867
  networkCommercialEvents: { $: { where: { eventId: input.eventId }, limit: 1 } }
3659
3868
  });
3660
3869
  const exception = found.networkCommercialExceptions?.[0], stored = found.networkCommercialEvents?.[0];
@@ -3677,7 +3886,7 @@ async function resolveNetworkCommercialCollision(db, sources, input, now = Date.
3677
3886
  action: "resolve",
3678
3887
  reason: input.reason
3679
3888
  } },
3680
- { t: "update", ns: "networkCommercialExceptions", id: id6, attrs: {
3889
+ { t: "update", ns: "networkCommercialExceptions", id: id7, attrs: {
3681
3890
  status: "resolved",
3682
3891
  resolvedDigest: stored.digest,
3683
3892
  ...dismissed ? { reviewedDigest: dismissed } : {},
@@ -3686,7 +3895,7 @@ async function resolveNetworkCommercialCollision(db, sources, input, now = Date.
3686
3895
  updatedAt: now
3687
3896
  } }
3688
3897
  ], { mutationId: input.mutationId, guards: [
3689
- { ns: "networkCommercialExceptions", id: id6, exists: true, equals: { status: "active", updatedAt: exception.updatedAt } }
3898
+ { ns: "networkCommercialExceptions", id: id7, exists: true, equals: { status: "active", updatedAt: exception.updatedAt } }
3690
3899
  ] });
3691
3900
  if (write.duplicate) throw fail3("mutationId was already used for another change", "mutation_reused");
3692
3901
  const projection = await rebuildNetworkCommercialProjection(db, sources, `${input.mutationId}:rebuild`, now, alerts);
@@ -3801,10 +4010,10 @@ var handlePaymentRecovery = async (req, url, env, ctx) => {
3801
4010
  const provider = await readStripeRecoveryEvent(db, eventId);
3802
4011
  const event2 = normalizeWebhookEvent(provider.event, { requireProviderPayment: Boolean(ctx.membershipAuthority) });
3803
4012
  if (provider.event.livemode !== (env.ODLA_ENV === "prod") || classifyMemberStripeEvent(stripeEventIdentity(event2), target, false) !== "apply") return json({ error: "provider event belongs to another or unknown runtime" }, 409);
3804
- const id6 = crypto.randomUUID();
4013
+ const id7 = crypto.randomUUID();
3805
4014
  await db.transact([
3806
- { t: "update", ns: "paymentRecoveryReviews", id: id6, attrs: {
3807
- id: id6,
4015
+ { t: "update", ns: "paymentRecoveryReviews", id: id7, attrs: {
4016
+ id: id7,
3808
4017
  eventId,
3809
4018
  actorUserId: user.userId,
3810
4019
  reviewedAt: Date.now(),
@@ -3813,8 +4022,8 @@ var handlePaymentRecovery = async (req, url, env, ctx) => {
3813
4022
  scope: `${target.appId}:${target.environment}:${target.runtime}`,
3814
4023
  previousState: row2
3815
4024
  } },
3816
- { t: "update", ns: "stripeEventReceipts", id: String(row2.id), attrs: { retryReady: true, reviewedDigest: provider.digest, reviewId: id6 } }
3817
- ], { mutationId: `payment-recovery:${id6}`, guards: [{
4025
+ { t: "update", ns: "stripeEventReceipts", id: String(row2.id), attrs: { retryReady: true, reviewedDigest: provider.digest, reviewId: id7 } }
4026
+ ], { mutationId: `payment-recovery:${id7}`, guards: [{
3818
4027
  ns: "stripeEventReceipts",
3819
4028
  id: String(row2.id),
3820
4029
  exists: true,
@@ -3883,43 +4092,121 @@ async function closeRecipientApplication(db, env, ctx, seatId) {
3883
4092
  }
3884
4093
 
3885
4094
  // src/membership-seat-refund-stripe.ts
4095
+ import { stripeRequest as stripeRequest5 } from "@odla-ai/stripe";
4096
+
4097
+ // src/membership-checkout-seat-refund.ts
3886
4098
  import { stripeRequest as stripeRequest4 } from "@odla-ai/stripe";
3887
4099
  var fail4 = (code = "named_seat_refund_mismatch") => {
3888
4100
  throw Object.assign(new Error(code), { code });
3889
4101
  };
4102
+ var id2 = (value) => typeof value === "string" ? value : value?.id ?? "";
4103
+ async function refundCheckoutNamedSeat(secretKey, seat) {
4104
+ const invoiceRef = seat.combinedInvoice;
4105
+ 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();
4106
+ const call = async (method, path, params, key3) => {
4107
+ const result = await stripeRequest4({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
4108
+ if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
4109
+ return result.body;
4110
+ };
4111
+ const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId };
4112
+ const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
4113
+ 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();
4114
+ const invoice = await call("GET", `/v1/invoices/${encodeURIComponent(invoiceRef.invoiceId)}`);
4115
+ 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();
4116
+ let chargeId = id2(invoice.charge), paymentId = id2(invoice.payment_intent);
4117
+ if (!chargeId && !paymentId) {
4118
+ const payments = await call("GET", "/v1/invoice_payments", { invoice: invoice.id, status: "paid", limit: 100 });
4119
+ if (!Array.isArray(payments.data) || payments.data.length !== 1 || payments.has_more !== false) fail4();
4120
+ const payment = payments.data[0];
4121
+ if (id2(payment.invoice) !== invoice.id || payment.status !== "paid" || payment.payment?.type !== "payment_intent") fail4();
4122
+ paymentId = id2(payment.payment.payment_intent);
4123
+ }
4124
+ if (!chargeId) {
4125
+ if (!/^pi_[A-Za-z0-9_]+$/.test(paymentId)) fail4();
4126
+ const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(paymentId)}`);
4127
+ 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();
4128
+ chargeId = id2(payment.latest_charge);
4129
+ }
4130
+ if (!/^ch_[A-Za-z0-9_]+$/.test(chargeId)) fail4();
4131
+ const charge = await call("GET", `/v1/charges/${encodeURIComponent(chargeId)}`);
4132
+ 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();
4133
+ const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
4134
+ if (items.length > 1) fail4();
4135
+ if (items.length && sub.status !== "canceled") {
4136
+ const item = items[0];
4137
+ if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.quantity !== 1 || item.price?.id !== seat.priceId) fail4();
4138
+ const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
4139
+ if (removed.id !== item.id || removed.deleted !== true) fail4("named_seat_renewal_removal_unconfirmed");
4140
+ }
4141
+ if (charge.amount_refunded === charge.amount && charge.refunded === true) return { refundedCents: seat.amountCents, refundIds: [] };
4142
+ const list = await call("GET", "/v1/refunds", { charge: chargeId, limit: 100 });
4143
+ if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail4("named_seat_refund_history_incomplete");
4144
+ let refundedCents = 0;
4145
+ const refundIds = [];
4146
+ for (const refund of list.data) {
4147
+ 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();
4148
+ if (["pending", "requires_action"].includes(refund.status)) fail4("named_seat_refund_pending");
4149
+ if (refund.status === "succeeded") {
4150
+ if (refund.metadata?.seatId !== seat.id || refund.metadata?.membershipId !== seat.primaryMembershipId) fail4("named_seat_refund_allocation_ambiguous");
4151
+ refundedCents += refund.amount;
4152
+ refundIds.push(refund.id);
4153
+ }
4154
+ }
4155
+ if (refundedCents !== charge.amount_refunded || refundedCents > seat.amountCents) fail4();
4156
+ if (refundedCents < seat.amountCents) {
4157
+ const remaining = seat.amountCents - refundedCents;
4158
+ const refund = await call("POST", "/v1/refunds", {
4159
+ charge: chargeId,
4160
+ amount: remaining,
4161
+ reason: "requested_by_customer",
4162
+ metadata: { ...metadata, membershipId: seat.primaryMembershipId, seatId: seat.id }
4163
+ }, `combined-seat-refund:${seat.id}:${remaining}`);
4164
+ if (id2(refund.charge) !== chargeId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail4();
4165
+ if (refund.status !== "succeeded") fail4("named_seat_refund_pending");
4166
+ refundedCents += remaining;
4167
+ refundIds.push(refund.id);
4168
+ }
4169
+ return { refundedCents, refundIds };
4170
+ }
4171
+
4172
+ // src/membership-seat-refund-stripe.ts
4173
+ var fail5 = (code = "named_seat_refund_mismatch") => {
4174
+ throw Object.assign(new Error(code), { code });
4175
+ };
3890
4176
  async function refundNamedSeatStripePayment(secretKey, seat) {
3891
- if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail4();
4177
+ if (seat.combinedInvoice) return refundCheckoutNamedSeat(secretKey, seat);
4178
+ if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail5();
3892
4179
  const call = async (method, path, params, idempotencyKey) => {
3893
- const result = await stripeRequest4({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
3894
- if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
4180
+ const result = await stripeRequest5({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
4181
+ if (!result.ok || !result.body) return fail5("named_seat_refund_provider_unavailable");
3895
4182
  return result.body;
3896
4183
  };
3897
4184
  const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
3898
4185
  const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`);
3899
- if (payment.id !== seat.providerPaymentIntentId || payment.customer !== seat.providerCustomerId || payment.amount !== seat.amountCents || payment.amount_received !== seat.amountCents || payment.currency !== seat.currency || payment.livemode !== (seat.environment === "prod") || payment.status !== "succeeded" || !Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === value)) fail4();
4186
+ if (payment.id !== seat.providerPaymentIntentId || payment.customer !== seat.providerCustomerId || payment.amount !== seat.amountCents || payment.amount_received !== seat.amountCents || payment.currency !== seat.currency || payment.livemode !== (seat.environment === "prod") || payment.status !== "succeeded" || !Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === value)) fail5();
3900
4187
  const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
3901
- if (sub.id !== seat.providerSubscriptionId || sub.customer !== seat.providerCustomerId || sub.livemode !== (seat.environment === "prod") || !Object.entries({ ...stripePartitionMetadata(seat), applicationId: seat.applicationId }).every(([key3, value]) => sub.metadata?.[key3] === value) || sub.items?.has_more !== false || !Array.isArray(sub.items?.data)) fail4();
4188
+ if (sub.id !== seat.providerSubscriptionId || sub.customer !== seat.providerCustomerId || sub.livemode !== (seat.environment === "prod") || !Object.entries({ ...stripePartitionMetadata(seat), applicationId: seat.applicationId }).every(([key3, value]) => sub.metadata?.[key3] === value) || sub.items?.has_more !== false || !Array.isArray(sub.items?.data)) fail5();
3902
4189
  const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
3903
- if (items.length > 1) fail4();
4190
+ if (items.length > 1) fail5();
3904
4191
  if (items.length && sub.status !== "canceled") {
3905
4192
  const item = items[0];
3906
- if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail4();
4193
+ if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail5();
3907
4194
  const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
3908
- if (removed.id !== item.id || removed.deleted !== true) fail4("named_seat_renewal_removal_unconfirmed");
4195
+ if (removed.id !== item.id || removed.deleted !== true) fail5("named_seat_renewal_removal_unconfirmed");
3909
4196
  }
3910
4197
  const list = await call("GET", "/v1/refunds", { payment_intent: seat.providerPaymentIntentId, limit: 100 });
3911
- if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail4("named_seat_refund_history_incomplete");
4198
+ if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail5("named_seat_refund_history_incomplete");
3912
4199
  let refundedCents = 0, pendingCents = 0;
3913
4200
  const refundIds = [];
3914
4201
  for (const refund of list.data) {
3915
- if (!/^re_[A-Za-z0-9_]+$/.test(refund.id) || refund.payment_intent !== seat.providerPaymentIntentId || refund.currency !== seat.currency || !Number.isSafeInteger(refund.amount) || refund.amount <= 0 || !["succeeded", "pending", "requires_action", "failed", "canceled"].includes(refund.status)) fail4();
4202
+ if (!/^re_[A-Za-z0-9_]+$/.test(refund.id) || refund.payment_intent !== seat.providerPaymentIntentId || refund.currency !== seat.currency || !Number.isSafeInteger(refund.amount) || refund.amount <= 0 || !["succeeded", "pending", "requires_action", "failed", "canceled"].includes(refund.status)) fail5();
3916
4203
  if (refund.status === "succeeded") {
3917
4204
  refundedCents += refund.amount;
3918
4205
  refundIds.push(refund.id);
3919
4206
  } else if (refund.status === "pending" || refund.status === "requires_action") pendingCents += refund.amount;
3920
4207
  }
3921
- if (refundedCents + pendingCents > seat.amountCents) fail4();
3922
- if (pendingCents) fail4("named_seat_refund_pending");
4208
+ if (refundedCents + pendingCents > seat.amountCents) fail5();
4209
+ if (pendingCents) fail5("named_seat_refund_pending");
3923
4210
  if (refundedCents < seat.amountCents) {
3924
4211
  const remaining = seat.amountCents - refundedCents;
3925
4212
  const refund = await call("POST", "/v1/refunds", {
@@ -3928,8 +4215,8 @@ async function refundNamedSeatStripePayment(secretKey, seat) {
3928
4215
  reason: "requested_by_customer",
3929
4216
  metadata
3930
4217
  }, `named-seat-refund:${seat.id}:${remaining}`);
3931
- if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail4();
3932
- if (refund.status !== "succeeded") fail4("named_seat_refund_pending");
4218
+ if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail5();
4219
+ if (refund.status !== "succeeded") fail5("named_seat_refund_pending");
3933
4220
  refundedCents += remaining;
3934
4221
  refundIds.push(refund.id);
3935
4222
  }
@@ -3951,8 +4238,8 @@ async function reserveNamedMembershipSeat(db, input, now) {
3951
4238
  const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
3952
4239
  requireMembershipScope(member, input);
3953
4240
  if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
3954
- const id6 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
3955
- let seat = await membershipLoad(db, "networkSeat", id6);
4241
+ const id7 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
4242
+ let seat = await membershipLoad(db, "networkSeat", id7);
3956
4243
  if (!seat) {
3957
4244
  const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
3958
4245
  const purchaseEndsAt = seatPurchaseDeadline(offering, member);
@@ -3960,7 +4247,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
3960
4247
  membershipFail("named_seat_purchase_unavailable");
3961
4248
  }
3962
4249
  const attrs = {
3963
- id: id6,
4250
+ id: id7,
3964
4251
  ...membershipScope(input),
3965
4252
  primaryMembershipId: memberId,
3966
4253
  recipientName: name,
@@ -3985,20 +4272,20 @@ async function reserveNamedMembershipSeat(db, input, now) {
3985
4272
  };
3986
4273
  try {
3987
4274
  await db.transact([
3988
- membershipUpdate("networkSeat", id6, attrs),
4275
+ membershipUpdate("networkSeat", id7, attrs),
3989
4276
  membershipUpdate("networkSeatRecipient", claimId, {
3990
4277
  id: claimId,
3991
4278
  appId: input.appId,
3992
4279
  chapterId: input.chapterId,
3993
4280
  environment: input.environment,
3994
4281
  runtime: input.runtime,
3995
- seatId: id6,
4282
+ seatId: id7,
3996
4283
  recipientEmailNormalized: email,
3997
4284
  createdAt: now
3998
4285
  }),
3999
- membershipUpdate("networkMembership", memberId, { namedSeatId: id6, revision: Number(member.revision) + 1, updatedAt: now })
4000
- ], { mutationId: `named-seat-reserve:${id6}`, guards: [
4001
- { ns: "networkSeat", id: id6, exists: false },
4286
+ membershipUpdate("networkMembership", memberId, { namedSeatId: id7, revision: Number(member.revision) + 1, updatedAt: now })
4287
+ ], { mutationId: `named-seat-reserve:${id7}`, guards: [
4288
+ { ns: "networkSeat", id: id7, exists: false },
4002
4289
  { ns: "networkSeatRecipient", id: claimId, exists: false },
4003
4290
  { ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
4004
4291
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
@@ -4006,7 +4293,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4006
4293
  } catch (error) {
4007
4294
  if (error.code !== "transact_guard_failed") throw error;
4008
4295
  }
4009
- seat = await membershipLoad(db, "networkSeat", id6);
4296
+ seat = await membershipLoad(db, "networkSeat", id7);
4010
4297
  if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
4011
4298
  }
4012
4299
  requireMembershipScope(seat, input);
@@ -4016,7 +4303,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4016
4303
 
4017
4304
  // src/membership-seat-included.ts
4018
4305
  var namedSeatIncluded = (seat) => Number(seat.amountCents) === 0;
4019
- var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean(seat.providerPaymentIntentId && seat.providerSubscriptionItemId);
4306
+ var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean((seat.providerPaymentIntentId || seat.combinedInvoice) && seat.providerSubscriptionItemId);
4020
4307
  function namedSeatRecipientState(seat) {
4021
4308
  switch (String(seat.status)) {
4022
4309
  case "renewal_pending":
@@ -4120,7 +4407,7 @@ async function requestNamedSeatRefund(db, input, now) {
4120
4407
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
4121
4408
  requireMembershipScope(member, input);
4122
4409
  if (seat.status === "refunded" || seat.status === "refund_pending") return;
4123
- if (!seat.purchasedAt || !seat.providerPaymentIntentId && !namedSeatIncluded(seat) || input.reason === "declined" && seat.journeyApprovedAt !== void 0 || input.reason === "primary_closed" && !["refunded", "declined"].includes(String(member.status))) membershipFail("named_seat_refund_not_eligible");
4410
+ if (!seat.purchasedAt || !seat.providerPaymentIntentId && !seat.combinedInvoice && !namedSeatIncluded(seat) || input.reason === "declined" && seat.journeyApprovedAt !== void 0 || input.reason === "primary_closed" && !["refunded", "declined"].includes(String(member.status))) membershipFail("named_seat_refund_not_eligible");
4124
4411
  const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
4125
4412
  await db.transact([
4126
4413
  membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
@@ -4233,8 +4520,8 @@ async function lowerSeatPrice(db, scope2, now, limit = 100) {
4233
4520
  const receipt = { seatPriceCents: target, repriced: 0, skipped: [], more: rows5.length > limit };
4234
4521
  for (const member of rows5.slice(0, limit)) {
4235
4522
  if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) throw new Error("membership reprice scope mismatch");
4236
- const id6 = String(member.id), skip = (reason) => {
4237
- receipt.skipped.push({ membershipId: id6, reason });
4523
+ const id7 = String(member.id), skip = (reason) => {
4524
+ receipt.skipped.push({ membershipId: id7, reason });
4238
4525
  };
4239
4526
  const offering = membershipOfferingVersion(member.offeringSnapshot);
4240
4527
  if (offering.seatPriceCents <= target) {
@@ -4250,16 +4537,16 @@ async function lowerSeatPrice(db, scope2, now, limit = 100) {
4250
4537
  skip(`seat is ${String(seat.status)}`);
4251
4538
  continue;
4252
4539
  }
4253
- const ops = [membershipUpdate("networkMembership", id6, { offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(member.revision) + 1, updatedAt: now })];
4540
+ const ops = [membershipUpdate("networkMembership", id7, { offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(member.revision) + 1, updatedAt: now })];
4254
4541
  const guards = [
4255
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
4542
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
4256
4543
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative" } }
4257
4544
  ];
4258
4545
  if (seat) {
4259
4546
  ops.push(membershipUpdate("networkSeat", String(seat.id), { amountCents: target, offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(seat.revision) + 1, updatedAt: now }));
4260
4547
  guards.push({ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision, status: "payment_pending" } });
4261
4548
  }
4262
- await db.transact(ops, { mutationId: `seat-reprice:${id6}:${member.revision}`, guards });
4549
+ await db.transact(ops, { mutationId: `seat-reprice:${id7}:${member.revision}`, guards });
4263
4550
  receipt.repriced++;
4264
4551
  }
4265
4552
  return receipt;
@@ -4460,25 +4747,28 @@ var handleAdminMembershipPolicyVersions = async (req, url, env, ctx) => {
4460
4747
  };
4461
4748
 
4462
4749
  // src/membership-discount-reterm.ts
4463
- import { stripeRequest as stripeRequest6 } from "@odla-ai/stripe";
4750
+ import { stripeRequest as stripeRequest7 } from "@odla-ai/stripe";
4464
4751
 
4465
4752
  // src/subscription-provider-contract.ts
4466
- import { stripeRequest as stripeRequest5 } from "@odla-ai/stripe";
4753
+ import { stripeRequest as stripeRequest6 } from "@odla-ai/stripe";
4467
4754
  function subscriptionContractMismatch() {
4468
4755
  throw Object.assign(new Error("Stripe subscription does not match the checkout contract"), { code: "stripe_contract_mismatch" });
4469
4756
  }
4470
4757
  async function verifySubscriptionPrice(secretKey, input, resume = false) {
4471
4758
  if (!/^price_[A-Za-z0-9]+$/.test(input.priceId) || !Number.isSafeInteger(input.priceCents) || input.priceCents <= 0 || !/^[a-z]{3}$/.test(input.currency) || !["month", "year"].includes(input.interval)) subscriptionContractMismatch();
4472
- const result = await stripeRequest5({ secretKey }, "GET", `/v1/prices/${encodeURIComponent(input.priceId)}`);
4759
+ const result = await stripeRequest6({ secretKey }, "GET", `/v1/prices/${encodeURIComponent(input.priceId)}`);
4473
4760
  const price = result.body;
4474
4761
  if (!result.ok || !price || price.id !== input.priceId || !resume && price.active !== true || price.unit_amount !== input.priceCents || price.currency !== input.currency || price.type !== "recurring" || price.billing_scheme !== "per_unit" || price.custom_unit_amount || price.transform_quantity || price.recurring?.interval !== input.interval || price.recurring?.interval_count !== 1 || price.recurring?.usage_type !== "licensed" || price.livemode !== (input.environment === "prod")) subscriptionContractMismatch();
4475
4762
  return typeof price.product === "string" ? price.product : void 0;
4476
4763
  }
4477
4764
  function verifySubscriptionResult(sub, input, customerId, metadata) {
4478
4765
  const items = sub?.items;
4479
- if (!sub || !/^sub_[A-Za-z0-9]+$/.test(sub.id) || sub.customer !== customerId || input.existingSubscriptionId && sub.id !== input.existingSubscriptionId || !(input.billingTerms?.initialTermEndsAt ? ["incomplete", "active", "trialing"] : ["incomplete", "active"]).includes(sub.status) || sub.livemode !== (input.environment === "prod") || !Object.entries(metadata).every(([key3, value]) => sub.metadata?.[key3] === value) || !Array.isArray(items?.data) || items.has_more !== false || items.data.length !== 1 || items.data[0]?.price?.id !== input.priceId || items.data[0]?.quantity !== 1) subscriptionContractMismatch();
4766
+ const seat = input.namedSeat?.amountCents ? input.namedSeat : void 0;
4767
+ const primary = items?.data?.find((item) => item.price?.id === input.priceId);
4768
+ const seatItem = seat && items?.data?.find((item) => item.price?.id === seat.priceId);
4769
+ if (!sub || !/^sub_[A-Za-z0-9]+$/.test(sub.id) || sub.customer !== customerId || input.existingSubscriptionId && sub.id !== input.existingSubscriptionId || !(input.billingTerms?.initialTermEndsAt ? ["incomplete", "active", "trialing"] : ["incomplete", "active"]).includes(sub.status) || sub.livemode !== (input.environment === "prod") || !Object.entries(metadata).every(([key3, value]) => sub.metadata?.[key3] === value) || !Array.isArray(items?.data) || items.has_more !== false || items.data.length !== (seat ? 2 : 1) || primary?.quantity !== 1 || seat && (seatItem?.quantity !== 1 || seatItem.metadata?.seatId !== seat.seatId || seatItem.metadata?.membershipId !== seat.membershipId)) subscriptionContractMismatch();
4480
4770
  const invoice = sub.latest_invoice;
4481
- if (!invoice || typeof invoice !== "object" || invoice.customer !== customerId || invoice.currency !== input.currency || invoice.total !== subscriptionAmountDue(input) || invoice.billing_reason !== "subscription_create") subscriptionContractMismatch();
4771
+ if (!invoice || typeof invoice !== "object" || invoice.customer !== customerId || invoice.currency !== input.currency || invoice.total !== subscriptionAmountDue(input) + (seat?.amountCents ?? 0) || invoice.billing_reason !== "subscription_create") subscriptionContractMismatch();
4482
4772
  verifySubscriptionBillingResult(sub, input);
4483
4773
  }
4484
4774
 
@@ -4490,8 +4780,8 @@ var couponOf = (discount) => {
4490
4780
  const ref = discount?.source?.coupon ?? discount?.coupon;
4491
4781
  return typeof ref === "string" ? ref : typeof ref?.id === "string" ? ref.id : void 0;
4492
4782
  };
4493
- async function retermPolicy(db, scope2, id6) {
4494
- const row2 = await membershipLoad(db, "discountPolicyVersion", id6);
4783
+ async function retermPolicy(db, scope2, id7) {
4784
+ const row2 = await membershipLoad(db, "discountPolicyVersion", id7);
4495
4785
  if (!row2) membershipFail("membership_reterm_policy_unknown");
4496
4786
  let policy2;
4497
4787
  try {
@@ -4499,12 +4789,12 @@ async function retermPolicy(db, scope2, id6) {
4499
4789
  } catch {
4500
4790
  return membershipFail("membership_reterm_policy_ineligible");
4501
4791
  }
4502
- if (policy2.id !== id6 || policy2.chapterId !== scope2.chapterId || policy2.status !== "published" || policy2.benefitDuration.kind !== "while_active" || policy2.percentOffBps <= 0 || policy2.discountEligibleSkuIds.length === 0) membershipFail("membership_reterm_policy_ineligible");
4792
+ if (policy2.id !== id7 || policy2.chapterId !== scope2.chapterId || policy2.status !== "published" || policy2.benefitDuration.kind !== "while_active" || policy2.percentOffBps <= 0 || policy2.discountEligibleSkuIds.length === 0) membershipFail("membership_reterm_policy_ineligible");
4503
4793
  return policy2;
4504
4794
  }
4505
4795
  async function attachProviderDiscount(sk, scope2, member, policy2, couponId, productId) {
4506
4796
  const subId = String(member.providerSubscriptionId), path = `/v1/subscriptions/${encodeURIComponent(subId)}`;
4507
- const read2 = await stripeRequest6({ secretKey: sk }, "GET", path, { "expand[0]": "discounts" });
4797
+ const read2 = await stripeRequest7({ secretKey: sk }, "GET", path, { "expand[0]": "discounts" });
4508
4798
  const sub = read2.body, items = sub?.items?.data;
4509
4799
  const metadata = { ...stripePartitionMetadata(scope2), applicationId: String(member.applicationId) };
4510
4800
  if (!read2.ok || sub.id !== subId || sub.customer !== member.providerCustomerId || sub.livemode !== (scope2.environment === "prod") || !PROVIDER_LIVE.has(sub.status) || !Object.entries(metadata).every(([key3, value]) => sub.metadata?.[key3] === value) || sub.items?.has_more !== false || !Array.isArray(items) || items.length < 1 || items.length > 2 || items.filter((item) => item.price?.id === member.providerPriceId && item.quantity === 1 && item.price?.product === productId).length !== 1) {
@@ -4515,7 +4805,7 @@ async function attachProviderDiscount(sk, scope2, member, policy2, couponId, pro
4515
4805
  const present = discounts.map(couponOf);
4516
4806
  if (present.length === 1 && present[0] === couponId) return false;
4517
4807
  if (present.length > 0) throw Object.assign(new Error("provider subscription carries an unrelated discount"), { code: "provider_mismatch" });
4518
- const written = await stripeRequest6({ secretKey: sk }, "POST", path, {
4808
+ const written = await stripeRequest7({ secretKey: sk }, "POST", path, {
4519
4809
  "discounts[0][coupon]": couponId,
4520
4810
  "expand[0]": "discounts",
4521
4811
  "metadata[discountPolicyVersionId]": policy2.id
@@ -4531,7 +4821,7 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4531
4821
  const limit = input.limit ?? 100;
4532
4822
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 200) throw new TypeError("invalid reterm limit");
4533
4823
  if (typeof input.policyVersionId !== "string" || !SAFE.test(input.policyVersionId)) throw new TypeError("invalid policy version id");
4534
- if (input.membershipIds !== void 0 && (!Array.isArray(input.membershipIds) || input.membershipIds.length === 0 || input.membershipIds.length > limit || input.membershipIds.some((id6) => typeof id6 !== "string" || !SAFE.test(id6)))) throw new TypeError("invalid membership ids");
4824
+ if (input.membershipIds !== void 0 && (!Array.isArray(input.membershipIds) || input.membershipIds.length === 0 || input.membershipIds.length > limit || input.membershipIds.some((id7) => typeof id7 !== "string" || !SAFE.test(id7)))) throw new TypeError("invalid membership ids");
4535
4825
  const state = await loadMembershipAuthorityState(db, scope2);
4536
4826
  const policy2 = await retermPolicy(db, scope2, input.policyVersionId);
4537
4827
  const receipt = { policyVersionId: policy2.id, retermed: [], unchanged: [], skipped: [], more: false };
@@ -4541,10 +4831,10 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4541
4831
  let rows5;
4542
4832
  if (input.membershipIds) {
4543
4833
  rows5 = [];
4544
- for (const id6 of input.membershipIds) {
4545
- const member = await membershipLoad(db, "networkMembership", id6);
4546
- if (!member) skip(id6, "not_found");
4547
- else if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) skip(id6, "scope_mismatch");
4834
+ for (const id7 of input.membershipIds) {
4835
+ const member = await membershipLoad(db, "networkMembership", id7);
4836
+ if (!member) skip(id7, "not_found");
4837
+ else if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) skip(id7, "scope_mismatch");
4548
4838
  else rows5.push(member);
4549
4839
  }
4550
4840
  } else {
@@ -4554,32 +4844,32 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4554
4844
  rows5 = found.slice(0, limit);
4555
4845
  }
4556
4846
  for (const member of rows5) {
4557
- const id6 = String(member.id);
4847
+ const id7 = String(member.id);
4558
4848
  if (member.discountPolicyVersionId === policy2.id) {
4559
- receipt.unchanged.push(id6);
4849
+ receipt.unchanged.push(id7);
4560
4850
  continue;
4561
4851
  }
4562
4852
  if (!RETERMABLE.has(String(member.status))) {
4563
- skip(id6, "status");
4853
+ skip(id7, "status");
4564
4854
  continue;
4565
4855
  }
4566
4856
  if (member.founding !== true) {
4567
- skip(id6, "not_founding");
4857
+ skip(id7, "not_founding");
4568
4858
  continue;
4569
4859
  }
4570
4860
  if (member.cohort !== policy2.cohortLabel) {
4571
- skip(id6, "cohort_mismatch");
4861
+ skip(id7, "cohort_mismatch");
4572
4862
  continue;
4573
4863
  }
4574
4864
  const skuId = String(member.baseSkuId);
4575
4865
  if (!policy2.discountEligibleSkuIds.includes(skuId)) {
4576
- skip(id6, "sku_not_discounted");
4866
+ skip(id7, "sku_not_discounted");
4577
4867
  continue;
4578
4868
  }
4579
4869
  const base = Number(member.baseAmountCents), current = Number(member.renewalAmountCents);
4580
4870
  const renewal = Math.round(base * (1e4 - policy2.percentOffBps) / 1e4);
4581
4871
  if (!Number.isSafeInteger(base) || base <= 0 || !Number.isSafeInteger(current) || renewal > current) {
4582
- skip(id6, "would_not_widen");
4872
+ skip(id7, "would_not_widen");
4583
4873
  continue;
4584
4874
  }
4585
4875
  const couponId = await membershipDiscountCouponId({ ...scope2, skuId }, policy2.id);
@@ -4607,22 +4897,22 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4607
4897
  } catch (error) {
4608
4898
  const code = error.code;
4609
4899
  if (code === "provider_mismatch" || code === "stripe_contract_mismatch") {
4610
- skip(id6, "provider_mismatch");
4900
+ skip(id7, "provider_mismatch");
4611
4901
  continue;
4612
4902
  }
4613
4903
  throw error;
4614
4904
  }
4615
4905
  try {
4616
- await db.transact([membershipUpdate("networkMembership", id6, {
4906
+ await db.transact([membershipUpdate("networkMembership", id7, {
4617
4907
  discountPolicyVersionId: policy2.id,
4618
4908
  discountSnapshot: policy2,
4619
4909
  renewalAmountCents: renewal,
4620
4910
  revision: Number(member.revision) + 1,
4621
4911
  updatedAt: now
4622
4912
  })], {
4623
- mutationId: `membership-reterm:${id6}:${policy2.id}:${member.revision}`,
4913
+ mutationId: `membership-reterm:${id7}:${policy2.id}:${member.revision}`,
4624
4914
  guards: [
4625
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision, status: member.status } },
4915
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision, status: member.status } },
4626
4916
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative" } },
4627
4917
  { ns: "discountPolicyVersion", id: policy2.id, exists: true }
4628
4918
  ]
@@ -4632,7 +4922,7 @@ async function retermMembershipDiscount(db, stripeSecretKey, scope2, input, now)
4632
4922
  return membershipFail("membership_reterm_retry_required");
4633
4923
  }
4634
4924
  receipt.retermed.push({
4635
- membershipId: id6,
4925
+ membershipId: id7,
4636
4926
  ...typeof member.discountPolicyVersionId === "string" ? { previousPolicyVersionId: member.discountPolicyVersionId } : {},
4637
4927
  previousRenewalAmountCents: current,
4638
4928
  renewalAmountCents: renewal,
@@ -4668,7 +4958,7 @@ var handleAdminMembershipReterm = async (req, url, env, ctx) => {
4668
4958
  return json({ error: BAD }, 400);
4669
4959
  }
4670
4960
  const appId = body.appId ?? body.chapterId, runtime = body.runtime ?? env.ODLA_RUNTIME;
4671
- if (typeof body.chapterId !== "string" || !SCOPE3.test(body.chapterId) || typeof appId !== "string" || !SCOPE3.test(appId) || typeof runtime !== "string" || !/^[a-z0-9]{2,12}$/.test(runtime) || typeof body.policyVersionId !== "string" || !ID.test(body.policyVersionId) || body.membershipIds !== void 0 && (!Array.isArray(body.membershipIds) || body.membershipIds.length === 0 || body.membershipIds.length > 200 || body.membershipIds.some((id6) => typeof id6 !== "string" || !ID.test(id6))) || body.limit !== void 0 && (!Number.isSafeInteger(body.limit) || Number(body.limit) < 1 || Number(body.limit) > 200)) {
4961
+ if (typeof body.chapterId !== "string" || !SCOPE3.test(body.chapterId) || typeof appId !== "string" || !SCOPE3.test(appId) || typeof runtime !== "string" || !/^[a-z0-9]{2,12}$/.test(runtime) || typeof body.policyVersionId !== "string" || !ID.test(body.policyVersionId) || body.membershipIds !== void 0 && (!Array.isArray(body.membershipIds) || body.membershipIds.length === 0 || body.membershipIds.length > 200 || body.membershipIds.some((id7) => typeof id7 !== "string" || !ID.test(id7))) || body.limit !== void 0 && (!Number.isSafeInteger(body.limit) || Number(body.limit) < 1 || Number(body.limit) > 200)) {
4672
4962
  return json({ error: BAD }, 400);
4673
4963
  }
4674
4964
  const secret = await getVaultSecret(db, operator.stripeSecretName ?? "stripe_secret_key");
@@ -4943,12 +5233,12 @@ var handleMembershipRestart = async (req, url, env, ctx) => {
4943
5233
  if (!app || app.clerkUserId !== user.userId || app.namedSeatId || app.giftClaimId) return json({ error: "membership is not eligible to restart" }, 409);
4944
5234
  if (app.membershipRestartId && app.status === "submitted") return json({ applicationId: app.id });
4945
5235
  try {
4946
- const id6 = String(app.id), intent = await membershipLoad(db, "subscriptionCheckoutIntents", id6);
5236
+ const id7 = String(app.id), intent = await membershipLoad(db, "subscriptionCheckoutIntents", id7);
4947
5237
  if (!intent || !app.stripeSubscriptionId || intent.providerSubscriptionId !== app.stripeSubscriptionId) return json({ error: "membership history needs reconciliation" }, 409);
4948
5238
  const target = await membershipTargetForIntent(intent, app, env, ctx);
4949
5239
  const restart = await ctx.membershipAuthority.restart({ ...target, previousSubscriptionId: String(app.stripeSubscriptionId) }, env);
4950
- if (restart.status !== "ready" || restart.applicationId !== id6 || restart.membershipId !== await networkMembershipId(target) || !/^restart_[a-f0-9]{64}$/.test(restart.restartId) || !Number.isSafeInteger(restart.memberNumber) || restart.memberNumber < 1) throw new Error("invalid restart receipt");
4951
- const now = Date.now(), quote = await membershipLoad(db, "membershipQuoteProjections", id6);
5240
+ 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");
5241
+ const now = Date.now(), quote = await membershipLoad(db, "membershipQuoteProjections", id7);
4952
5242
  const attrs = {
4953
5243
  status: "submitted",
4954
5244
  membershipRestartId: restart.restartId,
@@ -4968,8 +5258,8 @@ var handleMembershipRestart = async (req, url, env, ctx) => {
4968
5258
  quoteSnapshot: quote ?? {},
4969
5259
  archivedAt: now
4970
5260
  } },
4971
- { t: "delete", ns: "subscriptionCheckoutIntents", id: id6 },
4972
- { t: "retract", ns: "applications", id: id6, attrs: [
5261
+ { t: "delete", ns: "subscriptionCheckoutIntents", id: id7 },
5262
+ { t: "retract", ns: "applications", id: id7, attrs: [
4973
5263
  "stripeSubscriptionId",
4974
5264
  "canceled",
4975
5265
  "renewalAt",
@@ -4980,28 +5270,28 @@ var handleMembershipRestart = async (req, url, env, ctx) => {
4980
5270
  "approvalEffectsOrigin",
4981
5271
  "approvalEffectsLastAttemptAt"
4982
5272
  ] },
4983
- { t: "update", ns: "applications", id: id6, attrs },
4984
- ...quote ? [{ t: "update", ns: "membershipQuoteProjections", id: id6, attrs: { status: "restart", updatedAt: now } }] : []
5273
+ { t: "update", ns: "applications", id: id7, attrs },
5274
+ ...quote ? [{ t: "update", ns: "membershipQuoteProjections", id: id7, attrs: { status: "restart", updatedAt: now } }] : []
4985
5275
  ];
4986
5276
  try {
4987
5277
  await db.transact(ops, { mutationId: `membership-restart-application:${restart.restartId}`, guards: [
4988
- { ns: "applications", id: id6, exists: true, equals: {
5278
+ { ns: "applications", id: id7, exists: true, equals: {
4989
5279
  clerkUserId: user.userId,
4990
5280
  groupId: ctx.chapter.id,
4991
5281
  status: app.status,
4992
5282
  stripeSubscriptionId: app.stripeSubscriptionId
4993
5283
  } },
4994
- { ns: "subscriptionCheckoutIntents", id: id6, exists: true, equals: { providerSubscriptionId: app.stripeSubscriptionId } },
5284
+ { ns: "subscriptionCheckoutIntents", id: id7, exists: true, equals: { providerSubscriptionId: app.stripeSubscriptionId } },
4995
5285
  { ns: "membershipCheckoutHistory", id: restart.restartId, exists: false },
4996
- { ns: "membershipQuoteProjections", id: id6, exists: Boolean(quote), ...quote ? { equals: { revision: quote.revision, status: quote.status } } : {} }
5286
+ { ns: "membershipQuoteProjections", id: id7, exists: Boolean(quote), ...quote ? { equals: { revision: quote.revision, status: quote.status } } : {} }
4997
5287
  ] });
4998
5288
  } catch (error) {
4999
5289
  if (error.code !== "transact_guard_failed") throw error;
5000
5290
  }
5001
- const saved = await membershipLoad(db, "applications", id6);
5291
+ const saved = await membershipLoad(db, "applications", id7);
5002
5292
  if (!saved || saved.clerkUserId !== user.userId || saved.membershipRestartId !== restart.restartId || saved.groupId !== ctx.chapter.id || saved.stripeRuntime !== target.runtime) throw new Error("restart persistence pending");
5003
5293
  await syncApplicationToCrm(crmDeps(db, ctx), { app: saved, stage: String(saved.status) }).catch(() => void 0);
5004
- return json({ applicationId: id6 });
5294
+ return json({ applicationId: id7 });
5005
5295
  } catch {
5006
5296
  return json({ error: "We could not prepare your new subscription. Your account is saved. Please retry." }, 503);
5007
5297
  }
@@ -5132,8 +5422,8 @@ async function submitApplication(db, chapter, fields, opts) {
5132
5422
  if (app.requireDisclaimerAck && !acked) {
5133
5423
  return { ok: false, error: "disclaimerAck is required" };
5134
5424
  }
5135
- const id6 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
5136
- const row2 = { id: id6, status: chapter.pipeline.initial, createdAt: opts.now };
5425
+ const id7 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
5426
+ const row2 = { id: id7, status: chapter.pipeline.initial, createdAt: opts.now };
5137
5427
  for (const f of [...app.required, ...app.optional]) {
5138
5428
  if (fieldStates[f]?.visible === false) continue;
5139
5429
  if (typeof fields[f] === "string") row2[f] = fields[f].trim();
@@ -5167,12 +5457,12 @@ async function submitApplication(db, chapter, fields, opts) {
5167
5457
  }
5168
5458
  if (acked) row2.disclaimerAckAt = opts.now;
5169
5459
  const ops = [
5170
- { t: "update", ns: "applications", id: id6, attrs: row2 }
5460
+ { t: "update", ns: "applications", id: id7, attrs: row2 }
5171
5461
  ];
5172
5462
  if (opts.admissionGrant?.decision === "waive_interview") {
5173
5463
  ops.push({ t: "update", ns: "admissionGrants", id: opts.admissionGrant.id, attrs: {
5174
5464
  consumedAt: opts.now,
5175
- consumedApplicationId: id6
5465
+ consumedApplicationId: id7
5176
5466
  } });
5177
5467
  }
5178
5468
  try {
@@ -5180,7 +5470,7 @@ async function submitApplication(db, chapter, fields, opts) {
5180
5470
  ops,
5181
5471
  opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
5182
5472
  );
5183
- return { ok: true, id: id6, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
5473
+ return { ok: true, id: id7, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
5184
5474
  } catch (error) {
5185
5475
  if (error.code === "unique_violation") return {
5186
5476
  ok: false,
@@ -5289,7 +5579,7 @@ async function authorizeCrmDiscussionReference(req, env, fetcher = fetch) {
5289
5579
  }
5290
5580
 
5291
5581
  // src/join-founding-preview.ts
5292
- async function foundingPreviews(tiers, env, ctx) {
5582
+ async function foundingPreviews(tiers, env, ctx, seats = /* @__PURE__ */ new Map()) {
5293
5583
  const result = /* @__PURE__ */ new Map();
5294
5584
  const preview = ctx.membershipAuthority?.previewOffer;
5295
5585
  if (!preview) return result;
@@ -5297,6 +5587,10 @@ async function foundingPreviews(tiers, env, ctx) {
5297
5587
  await Promise.all(tiers.filter((tier) => !tierIsFree(tier) && tier.stripePriceId).map(async (tier) => {
5298
5588
  try {
5299
5589
  const offer = await preview({ ...scope2, skuId: tier.id }, env);
5590
+ if (offer.skuId !== tier.id || offer.basePriceCents !== tier.priceCents) return;
5591
+ if (ctx.membershipAuthority?.namedSeats && Number.isSafeInteger(offer.seatPriceCents) && Number(offer.seatPriceCents) >= 0) {
5592
+ seats.set(tier.id, { amountCents: Number(offer.seatPriceCents) });
5593
+ }
5300
5594
  if (!offer.founding || !offer.cohortLabel || offer.basePriceCents !== tier.priceCents || offer.dueTodayCents >= offer.basePriceCents) return;
5301
5595
  result.set(tier.id, {
5302
5596
  cohortLabel: offer.cohortLabel,
@@ -5386,14 +5680,14 @@ function validateAdmissionGrantDocument(input) {
5386
5680
  const seenEmails = /* @__PURE__ */ new Set();
5387
5681
  for (const [index2, raw2] of rawGrants.slice(0, MAX_GRANTS).entries()) {
5388
5682
  const grant = raw2 && typeof raw2 === "object" ? raw2 : {};
5389
- const id6 = typeof grant.id === "string" ? grant.id : "";
5683
+ const id7 = typeof grant.id === "string" ? grant.id : "";
5390
5684
  const email = typeof grant.email === "string" ? grant.email.trim() : "";
5391
5685
  const normalized = normalizeAdmissionEmail(email);
5392
5686
  const decision = grant.decision;
5393
5687
  const note = typeof grant.note === "string" ? grant.note.trim() : void 0;
5394
5688
  const expiresAt = grant.expiresAt === void 0 ? void 0 : Number(grant.expiresAt);
5395
- if (!ID2.test(id6)) errors.push(`document.grants[${index2}].id must be an identifier`);
5396
- if (seenIds.has(id6)) errors.push(`document.grants repeats id "${id6}"`);
5689
+ if (!ID2.test(id7)) errors.push(`document.grants[${index2}].id must be an identifier`);
5690
+ if (seenIds.has(id7)) errors.push(`document.grants repeats id "${id7}"`);
5397
5691
  if (!isAdmissionEmail(normalized)) errors.push(`document.grants[${index2}].email must be valid`);
5398
5692
  if (seenEmails.has(normalized)) errors.push(`document.grants repeats email "${normalized}"`);
5399
5693
  if (decision !== "waive_interview" && decision !== "require_interview") {
@@ -5403,10 +5697,10 @@ function validateAdmissionGrantDocument(input) {
5403
5697
  if (expiresAt !== void 0 && (!Number.isFinite(expiresAt) || expiresAt <= 0)) {
5404
5698
  errors.push(`document.grants[${index2}].expiresAt must be epoch milliseconds`);
5405
5699
  }
5406
- seenIds.add(id6);
5700
+ seenIds.add(id7);
5407
5701
  seenEmails.add(normalized);
5408
5702
  grants.push({
5409
- id: id6,
5703
+ id: id7,
5410
5704
  email,
5411
5705
  decision,
5412
5706
  ...note ? { note } : {},
@@ -5478,8 +5772,8 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
5478
5772
  const incomingIds = new Set(document.grants.map((grant) => `admission:parent:${document.source.appId}:${grant.id}`));
5479
5773
  const ops = [];
5480
5774
  for (const row2 of current) {
5481
- const id6 = String(row2.id ?? "");
5482
- if (id6 && !incomingIds.has(id6)) ops.push({ t: "update", ns: "admissionGrants", id: id6, attrs: {
5775
+ const id7 = String(row2.id ?? "");
5776
+ if (id7 && !incomingIds.has(id7)) ops.push({ t: "update", ns: "admissionGrants", id: id7, attrs: {
5483
5777
  active: false,
5484
5778
  revokedAt: receivedAt,
5485
5779
  revokedBy: document.source.actor,
@@ -5488,10 +5782,10 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
5488
5782
  } });
5489
5783
  }
5490
5784
  for (const grant of document.grants) {
5491
- const id6 = `admission:parent:${document.source.appId}:${grant.id}`;
5492
- const existing = current.find((row2) => row2.id === id6);
5493
- ops.push({ t: "update", ns: "admissionGrants", id: id6, attrs: {
5494
- id: id6,
5785
+ const id7 = `admission:parent:${document.source.appId}:${grant.id}`;
5786
+ const existing = current.find((row2) => row2.id === id7);
5787
+ ops.push({ t: "update", ns: "admissionGrants", id: id7, attrs: {
5788
+ id: id7,
5495
5789
  sourceGrantId: grant.id,
5496
5790
  sourceScope: "parent",
5497
5791
  sourceId: document.source.appId,
@@ -5512,7 +5806,7 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
5512
5806
  ...!grant.note && existing?.note !== void 0 ? ["note"] : [],
5513
5807
  ...grant.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
5514
5808
  ];
5515
- if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id6, attrs: retract });
5809
+ if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id7, attrs: retract });
5516
5810
  }
5517
5811
  if (previous?.active) ops.push({ t: "update", ns: "admissionGrantRevisions", id: previous.id, attrs: { active: false } });
5518
5812
  const revisionId = `admission-revision:${document.source.appId}:${document.target.environment}:${document.revision}`;
@@ -5543,9 +5837,9 @@ var APPLICATION_LABELS = {
5543
5837
  phone: "Phone",
5544
5838
  state: "State"
5545
5839
  };
5546
- function humanize(id6) {
5547
- const text8 = id6.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
5548
- return text8 ? `${text8[0].toUpperCase()}${text8.slice(1)}` : id6;
5840
+ function humanize(id7) {
5841
+ const text8 = id7.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
5842
+ return text8 ? `${text8[0].toUpperCase()}${text8.slice(1)}` : id7;
5549
5843
  }
5550
5844
  function applicationDefs(chapter) {
5551
5845
  try {
@@ -5554,23 +5848,23 @@ function applicationDefs(chapter) {
5554
5848
  return {};
5555
5849
  }
5556
5850
  }
5557
- function applicationType(id6, def) {
5851
+ function applicationType(id7, def) {
5558
5852
  if (def && ["string", "email", "url", "enum"].includes(def.type)) return def.type;
5559
- return id6 === "email" ? "email" : id6 === "linkedin" ? "url" : "string";
5853
+ return id7 === "email" ? "email" : id7 === "linkedin" ? "url" : "string";
5560
5854
  }
5561
5855
  function applicationFlowFields(chapter, application = chapter.application) {
5562
5856
  const defs = applicationDefs(chapter);
5563
- return [...application.required, ...application.optional].map((id6, order2) => {
5564
- const def = defs[id6];
5565
- const conditions = application.conditions[id6];
5566
- const type = applicationType(id6, def);
5857
+ return [...application.required, ...application.optional].map((id7, order2) => {
5858
+ const def = defs[id7];
5859
+ const conditions = application.conditions[id7];
5860
+ const type = applicationType(id7, def);
5567
5861
  return {
5568
- id: id6,
5569
- label: def?.label ?? APPLICATION_LABELS[id6] ?? humanize(id6),
5862
+ id: id7,
5863
+ label: def?.label ?? APPLICATION_LABELS[id7] ?? humanize(id7),
5570
5864
  type,
5571
- required: application.required.includes(id6),
5865
+ required: application.required.includes(id7),
5572
5866
  order: order2,
5573
- maxLength: application.maxLen[id6] ?? application.defaultMaxLen,
5867
+ maxLength: application.maxLen[id7] ?? application.defaultMaxLen,
5574
5868
  ...type === "enum" && def?.options ? { options: [...def.options] } : {},
5575
5869
  ...conditions ? { conditions: { ...conditions } } : {},
5576
5870
  ...type === "email" ? { emailIdentity: "literal" } : {}
@@ -5580,17 +5874,17 @@ function applicationFlowFields(chapter, application = chapter.application) {
5580
5874
  function resolveFormationFlowFields(crm, formation) {
5581
5875
  if (!formation.enabled) return [];
5582
5876
  const def = crm.type(formation.type);
5583
- return [...formation.required, ...formation.optional].map((id6, order2) => {
5584
- const field = def.fields[id6];
5877
+ return [...formation.required, ...formation.optional].map((id7, order2) => {
5878
+ const field = def.fields[id7];
5585
5879
  return {
5586
- id: id6,
5587
- label: field.label ?? humanize(id6),
5880
+ id: id7,
5881
+ label: field.label ?? humanize(id7),
5588
5882
  type: field.type,
5589
- required: formation.required.includes(id6),
5883
+ required: formation.required.includes(id7),
5590
5884
  order: order2,
5591
- maxLength: formation.maxLen[id6] ?? formation.defaultMaxLen,
5885
+ maxLength: formation.maxLen[id7] ?? formation.defaultMaxLen,
5592
5886
  ...field.options ? { options: [...field.options] } : {},
5593
- ...id6 === def.emailField || field.type === "email" ? { emailIdentity: "literal" } : {}
5887
+ ...id7 === def.emailField || field.type === "email" ? { emailIdentity: "literal" } : {}
5594
5888
  };
5595
5889
  });
5596
5890
  }
@@ -5640,10 +5934,10 @@ function validateGiftOfferCatalog(input, tierIds) {
5640
5934
  }
5641
5935
  const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
5642
5936
  for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
5643
- const id6 = text3(item.id, `${path}.id`, errors, 80);
5644
- if (id6 && !ID3.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
5645
- if (ids2.has(id6)) errors.push(`${path}.id duplicates "${id6}"`);
5646
- ids2.add(id6);
5937
+ const id7 = text3(item.id, `${path}.id`, errors, 80);
5938
+ if (id7 && !ID3.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
5939
+ if (ids2.has(id7)) errors.push(`${path}.id duplicates "${id7}"`);
5940
+ ids2.add(id7);
5647
5941
  const targetRaw = record(item.target);
5648
5942
  let target = { type: "entitlement", entitlementKey: "invalid" };
5649
5943
  if (targetRaw?.type === "tier") {
@@ -5669,7 +5963,7 @@ function validateGiftOfferCatalog(input, tierIds) {
5669
5963
  if (activeUntil !== void 0 && (!Number.isSafeInteger(activeUntil) || activeUntil < 1)) errors.push(`${path}.activeUntil must be epoch milliseconds`);
5670
5964
  if (activeFrom !== void 0 && activeUntil !== void 0 && activeUntil <= activeFrom) errors.push(`${path}.activeUntil must follow activeFrom`);
5671
5965
  offers.push({
5672
- id: id6,
5966
+ id: id7,
5673
5967
  target,
5674
5968
  billingMode: item.billingMode,
5675
5969
  stripePriceId,
@@ -5769,9 +6063,9 @@ async function materializeSignupControl(db, envelope, previous, receivedAt) {
5769
6063
  const ops = [];
5770
6064
  if (previous?.active) ops.push({ t: "update", ns: "signupControls", id: previous.id, attrs: { active: false } });
5771
6065
  for (const row2 of currentRows) {
5772
- const id7 = String(row2.id ?? "");
5773
- if (id7 && !incoming.has(id7)) {
5774
- ops.push({ t: "update", ns: "tiers", id: byPublicId("tiers", id7), attrs: { active: false } });
6066
+ const id8 = String(row2.id ?? "");
6067
+ if (id8 && !incoming.has(id8)) {
6068
+ ops.push({ t: "update", ns: "tiers", id: byPublicId("tiers", id8), attrs: { active: false } });
5775
6069
  }
5776
6070
  }
5777
6071
  for (const tier of document.tiers) {
@@ -5806,9 +6100,9 @@ async function materializeSignupControl(db, envelope, previous, receivedAt) {
5806
6100
  if (!("stripePriceId" in attrs)) {
5807
6101
  ops.push({ t: "retract", ns: "groups", id: groupRef, attrs: ["stripePriceId"] });
5808
6102
  }
5809
- const id6 = `signup-control:${document.target.environment}:${document.revision}`;
5810
- ops.push({ t: "update", ns: "signupControls", id: id6, attrs: {
5811
- id: id6,
6103
+ const id7 = `signup-control:${document.target.environment}:${document.revision}`;
6104
+ ops.push({ t: "update", ns: "signupControls", id: id7, attrs: {
6105
+ id: id7,
5812
6106
  sourceId: document.source.appId,
5813
6107
  targetId: document.target.chapterId,
5814
6108
  environment: document.target.environment,
@@ -5847,6 +6141,8 @@ function foundingOf(offer) {
5847
6141
  async function createPaymentQuote(input) {
5848
6142
  const { group, tier, policy: policy2 } = input;
5849
6143
  const terms2 = input.billingTerms ? subscriptionBillingTerms(input.billingTerms) : void 0;
6144
+ if (input.seatOffer && (!terms2 || !Number.isSafeInteger(input.seatOffer.amountCents) || input.seatOffer.amountCents < 0) || input.namedSeat && !input.seatOffer) throw new Error("invalid checkout seat offer");
6145
+ const seatCents = input.namedSeat ? input.seatOffer.amountCents : 0;
5850
6146
  const compareAt = input.presentation?.compareAtPriceCents;
5851
6147
  const presentation = Number.isSafeInteger(compareAt) && Number(compareAt) > tier.priceCents ? { compareAtPriceCents: Number(compareAt) } : void 0;
5852
6148
  const dueToday = terms2 ? subscriptionAmountDue({ priceCents: tier.priceCents, billingTerms: terms2 }) : void 0;
@@ -5855,6 +6151,7 @@ async function createPaymentQuote(input) {
5855
6151
  discountCents: tier.priceCents - dueToday,
5856
6152
  dueTodayCents: dueToday
5857
6153
  };
6154
+ if (!Number.isSafeInteger(lines.dueTodayCents + seatCents) || !Number.isSafeInteger(tier.priceCents + seatCents)) throw new Error("invalid checkout total");
5858
6155
  const quote = {
5859
6156
  applicationId: input.applicationId,
5860
6157
  chapterId: input.chapterId,
@@ -5862,11 +6159,17 @@ async function createPaymentQuote(input) {
5862
6159
  tierName: tier.name,
5863
6160
  currency: tier.currency ?? String(group.currency ?? "usd"),
5864
6161
  interval: tier.interval ?? (group.interval === "month" ? "month" : "year"),
5865
- lineItems: presentation ? {
5866
- standardCents: presentation.compareAtPriceCents,
5867
- discountCents: presentation.compareAtPriceCents - lines.dueTodayCents,
5868
- dueTodayCents: lines.dueTodayCents
5869
- } : lines,
6162
+ lineItems: {
6163
+ ...presentation ? {
6164
+ standardCents: presentation.compareAtPriceCents,
6165
+ discountCents: presentation.compareAtPriceCents - lines.dueTodayCents,
6166
+ dueTodayCents: lines.dueTodayCents
6167
+ } : lines,
6168
+ dueTodayCents: lines.dueTodayCents + seatCents,
6169
+ ...input.namedSeat ? { namedSeatCents: seatCents } : {}
6170
+ },
6171
+ ...input.seatOffer ? { seatOffer: input.seatOffer } : {},
6172
+ ...input.namedSeat ? { namedSeat: input.namedSeat } : {},
5870
6173
  ...presentation ? { presentation } : {},
5871
6174
  refundPolicyText: policy2?.refundPolicyText ?? String(group.refundPolicyText ?? ""),
5872
6175
  merchantDisclosureText: String(group.merchantDisclosureText ?? ""),
@@ -5878,7 +6181,7 @@ async function createPaymentQuote(input) {
5878
6181
  discountDuration: terms2.discount.duration === "once" ? "first_term" : "while_active"
5879
6182
  } : {},
5880
6183
  ...terms2.initialTermEndsAt !== void 0 ? { initialTermEndsAt: terms2.initialTermEndsAt } : {},
5881
- renewalCents: terms2.discount?.duration === "forever" ? dueToday : tier.priceCents,
6184
+ renewalCents: (terms2.discount?.duration === "forever" ? dueToday : tier.priceCents) + seatCents,
5882
6185
  autoRenew: true
5883
6186
  } } : {}
5884
6187
  };
@@ -5898,22 +6201,23 @@ async function createPaymentQuote(input) {
5898
6201
  // src/membership-quote-projection.ts
5899
6202
  var MEMBERSHIP_QUOTE_NAMESPACE = "membershipQuoteProjections";
5900
6203
  var keys2 = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId"];
5901
- var fail5 = (code) => {
6204
+ var fail6 = (code) => {
5902
6205
  throw Object.assign(new Error(code), { code });
5903
6206
  };
5904
- var load3 = async (db, ns, id6) => (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
6207
+ var load3 = async (db, ns, id7) => (await db.query({ [ns]: { $: { where: { id: id7 }, limit: 1 } } }))[ns]?.[0];
5905
6208
  async function restore2(row2, target, now, allowExpired = false) {
5906
- if (!row2 || keys2.some((key3) => row2[key3] !== target[key3]) || !Number.isSafeInteger(row2.revision) || Number(row2.revision) < 1 || typeof row2.requestId !== "string") return fail5("membership_quote_unavailable");
5907
- if (row2.status !== "ready") return fail5("membership_quote_pending");
6209
+ if (!row2 || keys2.some((key3) => row2[key3] !== target[key3]) || !Number.isSafeInteger(row2.revision) || Number(row2.revision) < 1 || typeof row2.requestId !== "string") return fail6("membership_quote_unavailable");
6210
+ if (row2.status !== "ready") return fail6("membership_quote_pending");
5908
6211
  const offer = await verifyIssuedMembershipOffer(row2.offer, { ...target, offerId: await membershipOfferIdForRequest({
5909
6212
  ...target,
5910
6213
  requestId: row2.requestId
5911
6214
  }), now, lifetimeMs: 6e4 });
5912
- if (offer.issuedAt > now + 3e4 || !allowExpired && offer.expiresAt <= now) return fail5("membership_quote_expired");
6215
+ if (offer.issuedAt > now + 3e4 || !allowExpired && offer.expiresAt <= now) return fail6("membership_quote_expired");
5913
6216
  const tier = row2.tier, quote = row2.quote;
5914
6217
  const offering = offer.contract.offering;
6218
+ if (quote?.seatOffer && quote.seatOffer.amountCents !== offering.seatPriceCents) return fail6("membership_quote_conflict");
5915
6219
  if (!tier || tier.id !== target.skuId || tier.groupId !== target.chapterId || !tier.stripePriceId || tier.priceCents !== offering.basePriceCents || tier.currency !== offering.currency || tier.interval !== offering.interval || !quote) {
5916
- return fail5("membership_catalog_mismatch");
6220
+ return fail6("membership_catalog_mismatch");
5917
6221
  }
5918
6222
  const expected = await createPaymentQuote({
5919
6223
  ...target,
@@ -5922,19 +6226,21 @@ async function restore2(row2, target, now, allowExpired = false) {
5922
6226
  policy: { refundPolicyText: quote.refundPolicyText, trustCopy: quote.trustCopy },
5923
6227
  billingTerms: offer.contract.billingTerms,
5924
6228
  presentation: quote.presentation,
5925
- founding: foundingOf(offer)
6229
+ founding: foundingOf(offer),
6230
+ seatOffer: quote.seatOffer,
6231
+ namedSeat: quote.namedSeat
5926
6232
  });
5927
- if (quote.digest !== expected.digest) return fail5("membership_quote_conflict");
6233
+ if (quote.digest !== expected.digest) return fail6("membership_quote_conflict");
5928
6234
  return { tier, quote: expected, billingTerms: offer.contract.billingTerms, membershipOffer: offer, projectionRevision: Number(row2.revision) };
5929
6235
  }
5930
6236
  async function readMembershipQuote(db, target, now) {
5931
6237
  return restore2(await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, target.applicationId), target, now);
5932
6238
  }
5933
6239
  async function prepareMembershipQuote(db, input, issue) {
5934
- const { applicationId: id6, now, app } = input;
5935
- if (app.id !== id6 || app.groupId !== input.chapterId || app.tierId !== input.skuId || app.stripeRuntime !== input.runtime || app.status !== "submitted" || app.canceled === true || app.giftOfferId || app.giftClaimId || app.namedSeatId || app.namedSeatClaimPending) return fail5("membership_quote_unavailable");
5936
- let row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
5937
- if (row2 && keys2.some((key3) => row2[key3] !== input[key3])) return fail5("membership_quote_conflict");
6240
+ const { applicationId: id7, now, app } = input;
6241
+ if (app.id !== id7 || app.groupId !== input.chapterId || app.tierId !== input.skuId || app.stripeRuntime !== input.runtime || app.status !== "submitted" || app.canceled === true || app.giftOfferId || app.giftClaimId || app.namedSeatId || app.namedSeatClaimPending) return fail6("membership_quote_unavailable");
6242
+ let row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7);
6243
+ if (row2 && keys2.some((key3) => row2[key3] !== input[key3])) return fail6("membership_quote_conflict");
5938
6244
  if (row2?.status === "ready") {
5939
6245
  try {
5940
6246
  return await restore2(row2, input, now);
@@ -5945,15 +6251,15 @@ async function prepareMembershipQuote(db, input, issue) {
5945
6251
  const identity2 = ["groupId", "tierId", "stripeRuntime", "email", "emailNormalized", "status", "canceled", "membershipRestartId", "namedSeatId", "namedSeatClaimPending", "giftClaimId", "giftOfferId"];
5946
6252
  const guards = [{
5947
6253
  ns: "applications",
5948
- id: id6,
6254
+ id: id7,
5949
6255
  exists: true,
5950
6256
  equals: Object.fromEntries(identity2.filter((key3) => app[key3] !== void 0).map((key3) => [key3, app[key3]])),
5951
6257
  absent: identity2.filter((key3) => app[key3] === void 0)
5952
- }, { ns: "subscriptionCheckoutIntents", id: id6, exists: false }];
6258
+ }, { ns: "subscriptionCheckoutIntents", id: id7, exists: false }];
5953
6259
  if (!row2 || row2.status === "ready" || row2.status === "restart") {
5954
6260
  const revision = Number(row2?.revision ?? 0) + 1;
5955
6261
  const pending = {
5956
- id: id6,
6262
+ id: id7,
5957
6263
  ...Object.fromEntries(keys2.map((key3) => [key3, input[key3]])),
5958
6264
  revision,
5959
6265
  status: "pending",
@@ -5961,25 +6267,25 @@ async function prepareMembershipQuote(db, input, issue) {
5961
6267
  updatedAt: now
5962
6268
  };
5963
6269
  try {
5964
- await db.transact([{ t: "update", ns: MEMBERSHIP_QUOTE_NAMESPACE, id: id6, attrs: pending }], {
5965
- mutationId: `membership-quote:${id6}:${revision}`,
6270
+ await db.transact([{ t: "update", ns: MEMBERSHIP_QUOTE_NAMESPACE, id: id7, attrs: pending }], {
6271
+ mutationId: `membership-quote:${id7}:${revision}`,
5966
6272
  guards: [...guards, {
5967
6273
  ns: MEMBERSHIP_QUOTE_NAMESPACE,
5968
- id: id6,
6274
+ id: id7,
5969
6275
  exists: Boolean(row2),
5970
6276
  ...row2 ? { equals: { revision: row2.revision, status: row2.status } } : {}
5971
6277
  }]
5972
6278
  });
5973
6279
  } catch (error) {
5974
6280
  if (error.code !== "transact_guard_failed") throw error;
5975
- const winner = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
6281
+ const winner = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7);
5976
6282
  if (!winner || winner.revision !== revision) throw error;
5977
6283
  }
5978
- row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
6284
+ row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7);
5979
6285
  }
5980
6286
  if (row2?.status === "ready") return restore2(row2, input, now);
5981
6287
  if (!row2 || row2.status !== "pending" || keys2.some((key3) => row2[key3] !== input[key3]) || typeof row2.requestId !== "string") {
5982
- return fail5("membership_quote_pending");
6288
+ return fail6("membership_quote_pending");
5983
6289
  }
5984
6290
  const target = Object.fromEntries(keys2.map((key3) => [key3, input[key3]]));
5985
6291
  const offer = await verifyIssuedMembershipOffer(await issue({ ...target, requestId: row2.requestId }), {
@@ -6000,7 +6306,8 @@ async function prepareMembershipQuote(db, input, issue) {
6000
6306
  policy: input.policy,
6001
6307
  billingTerms: offer.contract.billingTerms,
6002
6308
  presentation: input.presentation,
6003
- founding: foundingOf(offer)
6309
+ founding: foundingOf(offer),
6310
+ ...input.namedSeatsEnabled ? { seatOffer: { amountCents: offer.contract.offering.seatPriceCents } } : {}
6004
6311
  });
6005
6312
  const ready = { ...row2, offer, tier, quote, status: "ready", updatedAt: now };
6006
6313
  await restore2(ready, target, now, true);
@@ -6008,23 +6315,100 @@ async function prepareMembershipQuote(db, input, issue) {
6008
6315
  await db.transact([{
6009
6316
  t: "update",
6010
6317
  ns: MEMBERSHIP_QUOTE_NAMESPACE,
6011
- id: id6,
6318
+ id: id7,
6012
6319
  attrs: { offer, offerDigest: offer.digest, tier, quote, status: "ready", updatedAt: now }
6013
6320
  }], {
6014
- mutationId: `membership-quote-ready:${id6}:${row2.revision}`,
6321
+ mutationId: `membership-quote-ready:${id7}:${row2.revision}`,
6015
6322
  guards: [...guards, {
6016
6323
  ns: MEMBERSHIP_QUOTE_NAMESPACE,
6017
- id: id6,
6324
+ id: id7,
6018
6325
  exists: true,
6019
6326
  equals: { status: "pending", revision: row2.revision, requestId: row2.requestId }
6020
6327
  }]
6021
6328
  });
6022
6329
  } catch (error) {
6023
6330
  if (error.code !== "transact_guard_failed") throw error;
6024
- const winner = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
6331
+ const winner = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7);
6025
6332
  if (winner?.status !== "ready" || winner.requestId !== row2.requestId || winner.offerDigest !== offer.digest) throw error;
6026
6333
  }
6027
- return restore2(await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6), target, now);
6334
+ return restore2(await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id7), target, now);
6335
+ }
6336
+ async function selectCheckoutNamedSeat(db, target, input) {
6337
+ if (!input.enabled) return fail6("named_seat_purchase_unavailable");
6338
+ const row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, target.applicationId);
6339
+ const current = await restore2(row2, target, input.now);
6340
+ const namedSeat = input.selection === null ? void 0 : checkoutNamedSeatSelection(input.selection, input.purchaserEmail);
6341
+ const quote = await createPaymentQuote({
6342
+ ...target,
6343
+ tier: current.tier,
6344
+ group: { merchantDisclosureText: current.quote.merchantDisclosureText },
6345
+ policy: { refundPolicyText: current.quote.refundPolicyText, trustCopy: current.quote.trustCopy },
6346
+ billingTerms: current.billingTerms,
6347
+ presentation: current.quote.presentation,
6348
+ founding: current.quote.founding,
6349
+ seatOffer: { amountCents: current.membershipOffer.contract.offering.seatPriceCents },
6350
+ namedSeat
6351
+ });
6352
+ if (quote.digest === current.quote.digest) return current;
6353
+ await db.transact([{
6354
+ t: "update",
6355
+ ns: MEMBERSHIP_QUOTE_NAMESPACE,
6356
+ id: target.applicationId,
6357
+ attrs: { quote, revision: current.projectionRevision + 1, updatedAt: input.now }
6358
+ }], {
6359
+ mutationId: `membership-quote-selection:${target.applicationId}:${current.projectionRevision}:${quote.digest}`,
6360
+ guards: [
6361
+ {
6362
+ ns: MEMBERSHIP_QUOTE_NAMESPACE,
6363
+ id: target.applicationId,
6364
+ exists: true,
6365
+ equals: { revision: current.projectionRevision, status: "ready" }
6366
+ },
6367
+ { ns: "subscriptionCheckoutIntents", id: target.applicationId, exists: false }
6368
+ ]
6369
+ });
6370
+ return restore2(await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, target.applicationId), target, input.now);
6371
+ }
6372
+
6373
+ // src/bounded-request-text.ts
6374
+ async function readBoundedRequestText(req, maxBytes) {
6375
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) throw new TypeError("invalid request body cap");
6376
+ const reader = req.body?.getReader();
6377
+ const cancel = () => {
6378
+ void reader?.cancel().catch(() => {
6379
+ });
6380
+ };
6381
+ const invalid2 = () => json({ error: "invalid JSON body" }, 400);
6382
+ let complete = false;
6383
+ req.signal.addEventListener("abort", cancel, { once: true });
6384
+ try {
6385
+ if (req.signal.aborted) return invalid2();
6386
+ const declared = req.headers.get("content-length");
6387
+ if (declared && /^\d+$/.test(declared) && Number(declared) > maxBytes) {
6388
+ return json({ error: "request body too large" }, 413);
6389
+ }
6390
+ if (!reader) return "";
6391
+ const decoder = new TextDecoder();
6392
+ let bytes = 0;
6393
+ let text8 = "";
6394
+ for (; ; ) {
6395
+ const { done, value } = await reader.read();
6396
+ if (req.signal.aborted) return invalid2();
6397
+ if (done) {
6398
+ complete = true;
6399
+ return text8 + decoder.decode();
6400
+ }
6401
+ bytes += value.byteLength;
6402
+ if (bytes > maxBytes) return json({ error: "request body too large" }, 413);
6403
+ text8 += decoder.decode(value, { stream: true });
6404
+ }
6405
+ } catch {
6406
+ return invalid2();
6407
+ } finally {
6408
+ req.signal.removeEventListener("abort", cancel);
6409
+ if (!complete) cancel();
6410
+ reader?.releaseLock();
6411
+ }
6028
6412
  }
6029
6413
 
6030
6414
  // src/payment-quote-route.ts
@@ -6058,8 +6442,11 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
6058
6442
  policy: { refundPolicyText: quote2.refundPolicyText, trustCopy: quote2.trustCopy },
6059
6443
  billingTerms,
6060
6444
  presentation: quote2.presentation,
6061
- founding: foundingOf(intent.membershipOffer)
6445
+ founding: foundingOf(intent.membershipOffer),
6446
+ seatOffer: quote2.seatOffer,
6447
+ namedSeat: quote2.namedSeat
6062
6448
  });
6449
+ if (quote2.seatOffer && quote2.seatOffer.amountCents !== intent.membershipOffer?.contract.offering.seatPriceCents) throw new Error("invalid seat snapshot");
6063
6450
  if (expected.digest !== quote2.digest) throw new Error("invalid commercial snapshot");
6064
6451
  return { tier: tier2, quote: expected, billingTerms, membershipOffer: intent.membershipOffer };
6065
6452
  }
@@ -6088,7 +6475,16 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
6088
6475
  const presentation = tierPresentationFor(control3?.document, tier.id);
6089
6476
  if (ctx.membershipAuthority) return prepareMembershipQuote(
6090
6477
  db,
6091
- { ...target, app, group, tier, policy: policy2, presentation, now: Date.now() },
6478
+ {
6479
+ ...target,
6480
+ app,
6481
+ group,
6482
+ tier,
6483
+ policy: policy2,
6484
+ presentation,
6485
+ namedSeatsEnabled: Boolean(ctx.membershipAuthority.namedSeats) && !app.membershipRestartId,
6486
+ now: Date.now()
6487
+ },
6092
6488
  (input) => ctx.membershipAuthority.issueOffer(input, env)
6093
6489
  );
6094
6490
  const quote = await createPaymentQuote({
@@ -6104,20 +6500,54 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
6104
6500
  });
6105
6501
  return { tier, quote, billingTerms: void 0 };
6106
6502
  }
6107
- async function paymentQuoteRoute(url, env, ctx, prepare = false) {
6108
- const id6 = url.searchParams.get("application");
6109
- if (!id6 || id6.length > 160) return json({ error: "application required" }, 400);
6503
+ async function paymentQuoteRoute(url, env, ctx, prepare = false, request) {
6504
+ let selection;
6505
+ if (request?.body) {
6506
+ try {
6507
+ const text8 = await readBoundedRequestText(request, 2048);
6508
+ if (text8 instanceof Response) return text8;
6509
+ const body = JSON.parse(text8);
6510
+ if (!body || typeof body !== "object" || Array.isArray(body) || Object.keys(body).some((key3) => key3 !== "namedSeat")) throw new Error("invalid body");
6511
+ selection = body.namedSeat;
6512
+ } catch {
6513
+ return json({ error: "invalid seat selection" }, 400);
6514
+ }
6515
+ }
6516
+ const id7 = url.searchParams.get("application");
6517
+ if (!id7 || id7.length > 160) return json({ error: "application required" }, 400);
6110
6518
  const db = ctx.makeDb(env);
6111
- const app = (await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } })).applications?.[0];
6519
+ const app = (await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } })).applications?.[0];
6112
6520
  if (!app || String(app.groupId ?? ctx.chapter.id) !== ctx.chapter.id) return json({ error: "not found" }, 404);
6113
6521
  if (typeof app.stripeRuntime === "string" && app.stripeRuntime !== stripeRuntimeIdentity(env.ODLA_RUNTIME)) return json({ error: "not found" }, 404);
6114
6522
  if (app.status !== "submitted" || app.giftOfferId || app.namedSeatId || app.namedSeatClaimPending || app.giftClaimId || app.canceled === true) return json({ error: "membership checkout unavailable" }, 409);
6115
6523
  const group = (await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups?.[0];
6116
6524
  if (!group) return json({ error: "payments not configured" }, 503);
6117
6525
  try {
6118
- const { quote } = await checkoutQuoteForApplication(db, app, group, env, ctx, prepare);
6119
- return new Response(JSON.stringify(quote), { headers: { "content-type": "application/json", "cache-control": "no-store" } });
6526
+ let { quote } = await checkoutQuoteForApplication(db, app, group, env, ctx, prepare);
6527
+ const intent = (await db.query({ subscriptionCheckoutIntents: { $: { where: { id: id7 }, limit: 1 } } })).subscriptionCheckoutIntents?.[0];
6528
+ if (prepare && !intent && !quote.seatOffer && ctx.membershipAuthority?.namedSeats && !app.membershipRestartId && selection === void 0) selection = null;
6529
+ if (selection !== void 0) {
6530
+ if (intent) return json({ error: "checkout has already started", code: "checkout_intent_mismatch" }, 409);
6531
+ ({ quote } = await selectCheckoutNamedSeat(
6532
+ db,
6533
+ {
6534
+ appId: env.ODLA_APP_ID,
6535
+ chapterId: ctx.chapter.id,
6536
+ environment: env.ODLA_ENV,
6537
+ runtime: stripeRuntimeIdentity(env.ODLA_RUNTIME),
6538
+ applicationId: id7,
6539
+ skuId: String(app.tierId)
6540
+ },
6541
+ { selection, purchaserEmail: String(app.email ?? ""), now: Date.now(), enabled: Boolean(ctx.membershipAuthority?.namedSeats) && !app.membershipRestartId }
6542
+ ));
6543
+ }
6544
+ return new Response(JSON.stringify(quote), { headers: {
6545
+ "content-type": "application/json",
6546
+ "cache-control": "no-store",
6547
+ "x-chapter-checkout-started": intent ? "true" : "false"
6548
+ } });
6120
6549
  } catch (error) {
6550
+ if (error.code === "invalid_named_seat_recipient") return json({ error: "Enter a different recipient's name and email." }, 400);
6121
6551
  const expired = error.code === "membership_quote_expired";
6122
6552
  return json({ error: "payment quote unavailable", ...expired ? { code: "payment_quote_expired" } : {} }, expired ? 409 : 503);
6123
6553
  }
@@ -6223,47 +6653,6 @@ async function resumeJoin(url, env, ctx) {
6223
6653
  return json({ error: "Application continuation is unavailable. Retry when its payment or membership configuration is restored." }, 409);
6224
6654
  }
6225
6655
 
6226
- // src/bounded-request-text.ts
6227
- async function readBoundedRequestText(req, maxBytes) {
6228
- if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) throw new TypeError("invalid request body cap");
6229
- const reader = req.body?.getReader();
6230
- const cancel = () => {
6231
- void reader?.cancel().catch(() => {
6232
- });
6233
- };
6234
- const invalid2 = () => json({ error: "invalid JSON body" }, 400);
6235
- let complete = false;
6236
- req.signal.addEventListener("abort", cancel, { once: true });
6237
- try {
6238
- if (req.signal.aborted) return invalid2();
6239
- const declared = req.headers.get("content-length");
6240
- if (declared && /^\d+$/.test(declared) && Number(declared) > maxBytes) {
6241
- return json({ error: "request body too large" }, 413);
6242
- }
6243
- if (!reader) return "";
6244
- const decoder = new TextDecoder();
6245
- let bytes = 0;
6246
- let text8 = "";
6247
- for (; ; ) {
6248
- const { done, value } = await reader.read();
6249
- if (req.signal.aborted) return invalid2();
6250
- if (done) {
6251
- complete = true;
6252
- return text8 + decoder.decode();
6253
- }
6254
- bytes += value.byteLength;
6255
- if (bytes > maxBytes) return json({ error: "request body too large" }, 413);
6256
- text8 += decoder.decode(value, { stream: true });
6257
- }
6258
- } catch {
6259
- return invalid2();
6260
- } finally {
6261
- req.signal.removeEventListener("abort", cancel);
6262
- if (!complete) cancel();
6263
- reader?.releaseLock();
6264
- }
6265
- }
6266
-
6267
6656
  // src/named-seat-application.ts
6268
6657
  async function namedSeatPurchaserName(db, applicationId, chapterId, runtime) {
6269
6658
  const app = await membershipLoad(db, "applications", applicationId);
@@ -6778,11 +7167,12 @@ var handleMember = async (req, url, env, ctx) => {
6778
7167
  group,
6779
7168
  Boolean(stripeKey)
6780
7169
  );
6781
- const founding = await foundingPreviews(offered, env, ctx);
7170
+ const seats = /* @__PURE__ */ new Map();
7171
+ const founding = await foundingPreviews(offered, env, ctx, seats);
6782
7172
  const activeControl = chapter.signupControl ? await loadActiveSignupControl(db, chapter.id, env.ODLA_ENV) : null;
6783
7173
  const application = controlledApplication(chapter.application, activeControl?.document);
6784
7174
  return json({
6785
- ...joinConfig(group, paymentsReady, joinConfigTiers(offered, { currency: typeof group.currency === "string" ? group.currency : void 0, interval: group.interval === "month" ? "month" : "year" }, founding), application.conditions),
7175
+ ...joinConfig(group, paymentsReady, joinConfigTiers(offered, { currency: typeof group.currency === "string" ? group.currency : void 0, interval: group.interval === "month" ? "month" : "year" }, founding, seats), application.conditions),
6786
7176
  fieldContract: chapterFlowFieldContract(chapter, "join", application),
6787
7177
  copy: { ...chapter.copy.join, ...activeControl?.document.application?.copy ?? {} },
6788
7178
  ...activeControl ? { signupControl: {
@@ -7158,10 +7548,10 @@ function tiersOf(value, errors) {
7158
7548
  return [];
7159
7549
  }
7160
7550
  unknownKeys(item, TIER_KEYS2, path, errors);
7161
- const id6 = text4(item.id, `${path}.id`, errors, 80);
7162
- if (id6 && !TIER.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7163
- if (seen.has(id6)) errors.push(`${path}.id duplicates "${id6}"`);
7164
- seen.add(id6);
7551
+ const id7 = text4(item.id, `${path}.id`, errors, 80);
7552
+ if (id7 && !TIER.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7553
+ if (seen.has(id7)) errors.push(`${path}.id duplicates "${id7}"`);
7554
+ seen.add(id7);
7165
7555
  const name = text4(item.name, `${path}.name`, errors, 160);
7166
7556
  if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
7167
7557
  if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
@@ -7182,7 +7572,7 @@ function tiersOf(value, errors) {
7182
7572
  }
7183
7573
  const blurb = item.blurb === void 0 ? void 0 : text4(item.blurb, `${path}.blurb`, errors, 4e3);
7184
7574
  return [{
7185
- id: id6,
7575
+ id: id7,
7186
7576
  name,
7187
7577
  available: item.available === true,
7188
7578
  sortOrder: Number(item.sortOrder) || 0,
@@ -7244,15 +7634,15 @@ function policyOf(value, errors, tiers) {
7244
7634
  if (input.tierPolicies !== void 0) {
7245
7635
  const overrides = recordOf(input.tierPolicies);
7246
7636
  if (!overrides || Object.keys(overrides).length > 20) errors.push("document.policy.tierPolicies must be a bounded tier map");
7247
- else for (const [id6, raw2] of Object.entries(overrides)) {
7248
- const path = `document.policy.tierPolicies.${id6}`;
7637
+ else for (const [id7, raw2] of Object.entries(overrides)) {
7638
+ const path = `document.policy.tierPolicies.${id7}`;
7249
7639
  const policy2 = recordOf(raw2);
7250
- if (!tiers.some((tier) => tier.id === id6) || !policy2) {
7640
+ if (!tiers.some((tier) => tier.id === id7) || !policy2) {
7251
7641
  errors.push(`${path} must name a declared tier and policy`);
7252
7642
  continue;
7253
7643
  }
7254
7644
  unknownKeys(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
7255
- tierPolicies[id6] = {
7645
+ tierPolicies[id7] = {
7256
7646
  refundPolicyText: text4(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
7257
7647
  ...policy2.trustCopy !== void 0 ? { trustCopy: text4(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
7258
7648
  };
@@ -7545,14 +7935,14 @@ function validateMemberContentDocument(input, options = {}) {
7545
7935
  continue;
7546
7936
  }
7547
7937
  unknownKeys2(raw2, PAGE_KEYS, path, errors);
7548
- const id6 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
7938
+ const id7 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
7549
7939
  const slug = textValue(raw2.slug, `${path}.slug`, errors, 120, true) ?? "";
7550
7940
  const title = textValue(raw2.title, `${path}.title`, errors, MAX_TEXT, true) ?? "";
7551
- if (id6 && !ID4.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7941
+ if (id7 && !ID4.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
7552
7942
  if (slug && !SLUG2.test(slug)) errors.push(`${path}.slug must be a lowercase URL slug`);
7553
- if (ids2.has(id6) || knownParentIds.has(id6)) errors.push(`${path}.id duplicates page "${id6}"`);
7943
+ if (ids2.has(id7) || knownParentIds.has(id7)) errors.push(`${path}.id duplicates page "${id7}"`);
7554
7944
  if (slugs.has(slug) || (options.parentPages ?? []).some((page) => page.slug === slug)) errors.push(`${path}.slug duplicates "${slug}"`);
7555
- ids2.add(id6);
7945
+ ids2.add(id7);
7556
7946
  slugs.add(slug);
7557
7947
  const navRaw = record2(raw2.navigation);
7558
7948
  let navigation = { section: "General", order: index2 };
@@ -7608,7 +7998,7 @@ function validateMemberContentDocument(input, options = {}) {
7608
7998
  else if (owner === "chapter") upsell = { owner, ...offer ? { offer } : {} };
7609
7999
  }
7610
8000
  }
7611
- pages.push({ id: id6, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
8001
+ pages.push({ id: id7, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
7612
8002
  }
7613
8003
  const allIds = /* @__PURE__ */ new Set([...knownParentIds, ...ids2]);
7614
8004
  for (const [index2, page] of pages.entries()) {
@@ -7751,20 +8141,20 @@ async function loadLatestMemberContentControl(db, targetId, environment) {
7751
8141
  return control(Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
7752
8142
  }
7753
8143
  async function loadLocalMemberContent(db, chapterId, environment) {
7754
- const id6 = `member-content-local:${environment}`;
7755
- const rows5 = (await db.query({ memberContentLocal: { $: { where: { id: id6 }, limit: 1 } } })).memberContentLocal;
8144
+ const id7 = `member-content-local:${environment}`;
8145
+ const rows5 = (await db.query({ memberContentLocal: { $: { where: { id: id7 }, limit: 1 } } })).memberContentLocal;
7756
8146
  return local2(Array.isArray(rows5) ? rows5[0] : void 0);
7757
8147
  }
7758
8148
  async function materializeMemberContentControl(db, envelope, previous, receivedAt) {
7759
8149
  const { document, digest } = envelope;
7760
8150
  const target = document.target;
7761
- const id6 = `member-content-control:${target.environment}:${document.revision}`;
8151
+ const id7 = `member-content-control:${target.environment}:${document.revision}`;
7762
8152
  const ops = [];
7763
8153
  if (previous?.active) {
7764
8154
  ops.push({ t: "update", ns: "memberContentControls", id: previous.id, attrs: { active: false } });
7765
8155
  }
7766
- ops.push({ t: "update", ns: "memberContentControls", id: id6, attrs: {
7767
- id: id6,
8156
+ ops.push({ t: "update", ns: "memberContentControls", id: id7, attrs: {
8157
+ id: id7,
7768
8158
  sourceId: document.source.appId,
7769
8159
  targetId: target.chapterId,
7770
8160
  environment: target.environment,
@@ -7777,11 +8167,11 @@ async function materializeMemberContentControl(db, envelope, previous, receivedA
7777
8167
  await db.transact(ops, { mutationId: `member-content-control:${target.environment}:${document.revision}:${digest}` });
7778
8168
  }
7779
8169
  async function saveLocalMemberContent(db, document, environment, expectedRevision, updatedAt, updatedBy) {
7780
- const id6 = `member-content-local:${environment}`;
8170
+ const id7 = `member-content-local:${environment}`;
7781
8171
  const current = await loadLocalMemberContent(db, document.source.appId, environment);
7782
8172
  if ((current?.revision ?? 0) !== expectedRevision) throw new Error("member_content_revision_conflict");
7783
8173
  const row2 = {
7784
- id: id6,
8174
+ id: id7,
7785
8175
  chapterId: document.source.appId,
7786
8176
  environment,
7787
8177
  revision: document.revision,
@@ -7789,9 +8179,9 @@ async function saveLocalMemberContent(db, document, environment, expectedRevisio
7789
8179
  updatedAt,
7790
8180
  ...updatedBy ? { updatedBy } : {}
7791
8181
  };
7792
- await db.transact([{ t: "update", ns: "memberContentLocal", id: id6, attrs: row2 }], {
8182
+ await db.transact([{ t: "update", ns: "memberContentLocal", id: id7, attrs: row2 }], {
7793
8183
  mutationId: `member-content-local:${environment}:${document.revision}`,
7794
- guards: current ? [{ ns: "memberContentLocal", id: id6, exists: true, equals: { revision: expectedRevision } }] : [{ ns: "memberContentLocal", id: id6, exists: false }]
8184
+ guards: current ? [{ ns: "memberContentLocal", id: id7, exists: true, equals: { revision: expectedRevision } }] : [{ ns: "memberContentLocal", id: id7, exists: false }]
7795
8185
  });
7796
8186
  return row2;
7797
8187
  }
@@ -7928,7 +8318,7 @@ var handleAdminMemberContent = async (req, url, env, ctx) => {
7928
8318
  parent: parent ? { revision: parent.revision, digest: parent.digest, document: parent.document } : null,
7929
8319
  chapter: chapter ? { revision: chapter.revision, document: chapter.document } : null,
7930
8320
  effective,
7931
- tiers: tiers.map(({ id: id6, name, priceCents, active }) => ({ id: id6, name, priceCents, active })),
8321
+ tiers: tiers.map(({ id: id7, name, priceCents, active }) => ({ id: id7, name, priceCents, active })),
7932
8322
  localAuthoring: ctx.chapter.memberContent.localAuthoring
7933
8323
  });
7934
8324
  }
@@ -8086,11 +8476,11 @@ var handleAdminAdmissionGrants = async (req, url, env, ctx) => {
8086
8476
  return json({ error: "expiresAt must be a future epoch timestamp" }, 400);
8087
8477
  }
8088
8478
  const sourceGrantId = crypto.randomUUID();
8089
- const id6 = `admission:local:${ctx.chapter.id}:${sourceGrantId}`;
8479
+ const id7 = `admission:local:${ctx.chapter.id}:${sourceGrantId}`;
8090
8480
  const createdAt = Date.now();
8091
8481
  const actor = auth.user.email ?? auth.user.userId;
8092
- await auth.db.transact([{ t: "update", ns: "admissionGrants", id: id6, attrs: {
8093
- id: id6,
8482
+ await auth.db.transact([{ t: "update", ns: "admissionGrants", id: id7, attrs: {
8483
+ id: id7,
8094
8484
  sourceGrantId,
8095
8485
  sourceScope: "local",
8096
8486
  sourceId: ctx.chapter.id,
@@ -8106,7 +8496,7 @@ var handleAdminAdmissionGrants = async (req, url, env, ctx) => {
8106
8496
  updatedAt: createdAt,
8107
8497
  updatedBy: actor
8108
8498
  } }], { mutationId: `admission-local-create:${sourceGrantId}` });
8109
- return json({ grant: { id: id6, sourceGrantId, sourceScope: "local", email, decision, active: true, createdAt, ...note ? { note } : {}, ...expiresAt ? { expiresAt } : {} } }, 201);
8499
+ return json({ grant: { id: id7, sourceGrantId, sourceScope: "local", email, decision, active: true, createdAt, ...note ? { note } : {}, ...expiresAt ? { expiresAt } : {} } }, 201);
8110
8500
  }
8111
8501
  const prefix = "/api/admin/admission-grants/";
8112
8502
  let revokeId = "";
@@ -8120,20 +8510,20 @@ var handleAdminAdmissionGrants = async (req, url, env, ctx) => {
8120
8510
  if (/^admission:local:[a-z0-9-]+:[0-9a-f-]+$/.test(revokeId) && req.method === "DELETE") {
8121
8511
  const auth = await gate(req, env, ctx);
8122
8512
  if (auth instanceof Response) return auth;
8123
- const id6 = revokeId;
8124
- const found = await auth.db.query({ admissionGrants: { $: { where: { id: id6 }, limit: 1 } } });
8513
+ const id7 = revokeId;
8514
+ const found = await auth.db.query({ admissionGrants: { $: { where: { id: id7 }, limit: 1 } } });
8125
8515
  const row2 = Array.isArray(found.admissionGrants) ? found.admissionGrants[0] : void 0;
8126
8516
  if (!row2 || row2.targetId !== ctx.chapter.id || row2.sourceScope !== "local") return json({ error: "not found" }, 404);
8127
8517
  if (row2.active !== true) return json({ ok: true, replayed: true });
8128
8518
  const revokedAt = Date.now();
8129
8519
  const actor = auth.user.email ?? auth.user.userId;
8130
- await auth.db.transact([{ t: "update", ns: "admissionGrants", id: id6, attrs: {
8520
+ await auth.db.transact([{ t: "update", ns: "admissionGrants", id: id7, attrs: {
8131
8521
  active: false,
8132
8522
  revokedAt,
8133
8523
  revokedBy: actor,
8134
8524
  updatedAt: revokedAt,
8135
8525
  updatedBy: actor
8136
- } }], { mutationId: `admission-local-revoke:${id6}` });
8526
+ } }], { mutationId: `admission-local-revoke:${id7}` });
8137
8527
  return json({ ok: true, replayed: false });
8138
8528
  }
8139
8529
  return null;
@@ -8195,8 +8585,8 @@ async function loadLeaderAdmissionAssignments(db, targetId) {
8195
8585
  return await readAdmissionRows(db, "leaderAdmissionAssignments", { targetId }, MAX_ASSIGNMENTS);
8196
8586
  }
8197
8587
  async function loadLeaderAdmissionDelivery(db, targetId, environment) {
8198
- const id6 = deliveryId(targetId, environment);
8199
- const result = await db.query({ leaderAdmissionDeliveries: { $: { where: { id: id6 }, limit: 1 } } });
8588
+ const id7 = deliveryId(targetId, environment);
8589
+ const result = await db.query({ leaderAdmissionDeliveries: { $: { where: { id: id7 }, limit: 1 } } });
8200
8590
  return admissionRows(result.leaderAdmissionDeliveries)[0] ?? null;
8201
8591
  }
8202
8592
  function personAdmissionEmail(ctx, record4) {
@@ -8224,8 +8614,8 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8224
8614
  const now = Date.now();
8225
8615
  const previous = await loadLeaderAdmissionDelivery(opts.db, opts.target.id, opts.env.ODLA_ENV);
8226
8616
  const current = await loadLeaderAdmissionAssignments(opts.db, opts.target.id);
8227
- const id6 = assignmentId(opts.target.id, opts.personId);
8228
- const existing = current.find((row2) => row2.id === id6);
8617
+ const id7 = assignmentId(opts.target.id, opts.personId);
8618
+ const existing = current.find((row2) => row2.id === id7);
8229
8619
  const digest = await signupControlDigest({
8230
8620
  targetId: opts.target.id,
8231
8621
  personId: opts.personId,
@@ -8255,7 +8645,7 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8255
8645
  return { delivery: saved, duplicate: true };
8256
8646
  }
8257
8647
  const assignment = {
8258
- id: id6,
8648
+ id: id7,
8259
8649
  targetId: opts.target.id,
8260
8650
  personId: opts.personId,
8261
8651
  grantId: existing?.grantId ?? crypto.randomUUID().replaceAll("-", ""),
@@ -8270,7 +8660,7 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8270
8660
  updatedAt: now,
8271
8661
  updatedBy: opts.actor
8272
8662
  };
8273
- const desired = current.filter((row2) => row2.id !== id6 && row2.active).concat(assignment.active ? [assignment] : []);
8663
+ const desired = current.filter((row2) => row2.id !== id7 && row2.active).concat(assignment.active ? [assignment] : []);
8274
8664
  const grants = await Promise.all(desired.map((row2) => grantInput(opts.db, opts.ctx, row2, opts.resolveEmail)));
8275
8665
  const revision = (previous?.revision ?? 0) + 1;
8276
8666
  const envelope = await createAdmissionGrantEnvelope({
@@ -8293,7 +8683,7 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8293
8683
  updatedAt: now
8294
8684
  };
8295
8685
  const ops = [
8296
- { t: "update", ns: "leaderAdmissionAssignments", id: id6, attrs: assignment },
8686
+ { t: "update", ns: "leaderAdmissionAssignments", id: id7, attrs: assignment },
8297
8687
  { t: "update", ns: "leaderAdmissionDeliveries", id: delivery.id, attrs: delivery },
8298
8688
  { t: "update", ns: "leaderAdmissionMutations", id: mutationReceiptId, attrs: {
8299
8689
  id: mutationReceiptId,
@@ -8308,7 +8698,7 @@ async function persistLeaderAdmissionDesiredSet(opts) {
8308
8698
  ...!opts.note && existing?.note !== void 0 ? ["note"] : [],
8309
8699
  ...opts.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
8310
8700
  ];
8311
- if (retract.length) ops.push({ t: "retract", ns: "leaderAdmissionAssignments", id: id6, attrs: retract });
8701
+ if (retract.length) ops.push({ t: "retract", ns: "leaderAdmissionAssignments", id: id7, attrs: retract });
8312
8702
  const tx = await opts.db.transact(ops, {
8313
8703
  mutationId: `leader-admission:${opts.target.id}:${opts.mutationId}`,
8314
8704
  guards: previous ? [{ ns: "leaderAdmissionDeliveries", id: previous.id, exists: true, equals: { revision: previous.revision } }] : [{ ns: "leaderAdmissionDeliveries", id: delivery.id, exists: false }]
@@ -8368,11 +8758,11 @@ function projectRecord(record4, allowed2, editable, stagesEnabled, crm) {
8368
8758
  allowed2.flatMap((name) => Object.prototype.hasOwnProperty.call(record4.fields, name) ? [[name, record4.fields[name]]] : [])
8369
8759
  );
8370
8760
  const def = crm.type(record4.type);
8371
- const editFields = editable.map((id6) => {
8372
- const field = def.fields[id6];
8761
+ const editFields = editable.map((id7) => {
8762
+ const field = def.fields[id7];
8373
8763
  return {
8374
- id: id6,
8375
- label: field.label ?? id6,
8764
+ id: id7,
8765
+ label: field.label ?? id7,
8376
8766
  type: field.type,
8377
8767
  required: field.required ?? false,
8378
8768
  ...field.options ? { options: field.options } : {}
@@ -8489,8 +8879,8 @@ async function gate2(req, env, ctx) {
8489
8879
  if (!await networkWriteAllowed(req, db, user, ctx, true)) return { response: reject6(403, "super_admin_required", "super-admin required") };
8490
8880
  return { db, user };
8491
8881
  }
8492
- function targetFor(ctx, id6) {
8493
- return ctx.chapter.network.targets.find((target) => target.id === id6 && target.admissionGrants);
8882
+ function targetFor(ctx, id7) {
8883
+ return ctx.chapter.network.targets.find((target) => target.id === id7 && target.admissionGrants);
8494
8884
  }
8495
8885
  async function deliverSaved(db, env, ctx, target, delivery) {
8496
8886
  if (delivery.status === "acknowledged") return delivery;
@@ -8594,7 +8984,7 @@ var handleAdminNetworkAdmissions = async (req, url, env, ctx) => {
8594
8984
  if (!personId || !MUTATION_ID2.test(mutationId)) {
8595
8985
  return reject6(422, "invalid_request", "personId and a stable mutationId are required");
8596
8986
  }
8597
- const resolveEmail = (id6) => resolveAdmissionPersonEmail(db, env, ctx, target, id6);
8987
+ const resolveEmail = (id7) => resolveAdmissionPersonEmail(db, env, ctx, target, id7);
8598
8988
  let email;
8599
8989
  try {
8600
8990
  email = await resolveEmail(personId);
@@ -8833,28 +9223,28 @@ var handleSchedule = async (req, url, env, ctx) => {
8833
9223
  };
8834
9224
 
8835
9225
  // src/payments-stripe.ts
8836
- import { stripeRequest as stripeRequest9, verifyStripeSignature as verifyStripeSignature2 } from "@odla-ai/stripe";
9226
+ import { stripeRequest as stripeRequest10, verifyStripeSignature as verifyStripeSignature2 } from "@odla-ai/stripe";
8837
9227
 
8838
9228
  // src/payments-refund.ts
8839
- import { stripeRequest as stripeRequest7 } from "@odla-ai/stripe";
9229
+ import { stripeRequest as stripeRequest8 } from "@odla-ai/stripe";
8840
9230
  var row = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
8841
- var id2 = (value) => typeof value === "string" ? value : String(row(value).id ?? "");
9231
+ var id3 = (value) => typeof value === "string" ? value : String(row(value).id ?? "");
8842
9232
  var providerId2 = (value, prefix) => new RegExp(`^${prefix}_[A-Za-z0-9]+$`).test(value);
8843
- function fail6(code = "refund_target_mismatch") {
9233
+ function fail7(code = "refund_target_mismatch") {
8844
9234
  throw Object.assign(new Error(code), { code });
8845
9235
  }
8846
9236
  async function refundSubscriptionPayment(secretKey, input) {
8847
- if (!providerId2(input.customerId, "cus") || !providerId2(input.subscriptionId, "sub")) fail6();
9237
+ if (!providerId2(input.customerId, "cus") || !providerId2(input.subscriptionId, "sub")) fail7();
8848
9238
  const call = async (method, path, params, key3) => {
8849
- const result = await stripeRequest7({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
8850
- if (!result.ok) fail6("stripe_error");
8851
- if (!result.body || typeof result.body !== "object" || Array.isArray(result.body)) fail6("stripe_invalid_response");
9239
+ const result = await stripeRequest8({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
9240
+ if (!result.ok) fail7("stripe_error");
9241
+ if (!result.body || typeof result.body !== "object" || Array.isArray(result.body)) fail7("stripe_invalid_response");
8852
9242
  return result.body;
8853
9243
  };
8854
9244
  const sub = await call("GET", `/v1/subscriptions/${input.subscriptionId}`);
8855
- if (id2(sub) !== input.subscriptionId || id2(sub.customer) !== input.customerId) fail6();
9245
+ if (id3(sub) !== input.subscriptionId || id3(sub.customer) !== input.customerId) fail7();
8856
9246
  for (const [key3, value] of Object.entries(input.metadata ?? {})) {
8857
- if (row(sub.metadata)[key3] !== value) fail6();
9247
+ if (row(sub.metadata)[key3] !== value) fail7();
8858
9248
  }
8859
9249
  let cursor;
8860
9250
  let earliest;
@@ -8867,79 +9257,79 @@ async function refundSubscriptionPayment(secretKey, input) {
8867
9257
  limit: 100,
8868
9258
  ...cursor ? { starting_after: cursor } : {}
8869
9259
  });
8870
- if (!Array.isArray(list.data) || typeof list.has_more !== "boolean") fail6("stripe_invalid_response");
9260
+ if (!Array.isArray(list.data) || typeof list.has_more !== "boolean") fail7("stripe_invalid_response");
8871
9261
  for (const value of list.data) {
8872
9262
  const invoice2 = row(value);
8873
9263
  const subscription = invoice2.subscription ?? row(row(invoice2.parent).subscription_details).subscription;
8874
- if (!providerId2(id2(invoice2), "in") || seen.has(id2(invoice2)) || id2(subscription) !== input.subscriptionId || id2(invoice2.customer) !== input.customerId || invoice2.status !== "paid" || !Number.isSafeInteger(invoice2.created) || !Number.isSafeInteger(invoice2.amount_paid) || Number(invoice2.amount_paid) < 0) fail6();
8875
- seen.add(id2(invoice2));
9264
+ 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();
9265
+ seen.add(id3(invoice2));
8876
9266
  if (Number(invoice2.amount_paid) > 0 && (!earliest || Number(invoice2.created) < Number(earliest.created))) earliest = invoice2;
8877
9267
  }
8878
9268
  if (!list.has_more) break;
8879
- if (!list.data.length || page === 9) fail6("refund_history_incomplete");
8880
- cursor = id2(list.data[list.data.length - 1]);
9269
+ if (!list.data.length || page === 9) fail7("refund_history_incomplete");
9270
+ cursor = id3(list.data[list.data.length - 1]);
8881
9271
  }
8882
- if (!earliest) fail6("no_charge");
9272
+ if (!earliest) fail7("no_charge");
8883
9273
  const invoice = earliest;
8884
- if (input.expectedInvoiceId !== void 0 && id2(invoice) !== input.expectedInvoiceId || input.expectedAmountCents !== void 0 && invoice.amount_paid !== input.expectedAmountCents) fail6();
8885
- let chargeId = id2(invoice.charge);
8886
- let paymentIntentId = id2(invoice.payment_intent);
9274
+ if (input.expectedInvoiceId !== void 0 && id3(invoice) !== input.expectedInvoiceId || input.expectedAmountCents !== void 0 && invoice.amount_paid !== input.expectedAmountCents) fail7();
9275
+ let chargeId = id3(invoice.charge);
9276
+ let paymentIntentId = id3(invoice.payment_intent);
8887
9277
  if (!chargeId && !paymentIntentId) {
8888
- const payments = await call("GET", "/v1/invoice_payments", { invoice: id2(invoice), status: "paid", limit: 100 });
8889
- if (payments.has_more !== false || !Array.isArray(payments.data) || payments.data.length !== 1) fail6("refund_payment_ambiguous");
9278
+ const payments = await call("GET", "/v1/invoice_payments", { invoice: id3(invoice), status: "paid", limit: 100 });
9279
+ if (payments.has_more !== false || !Array.isArray(payments.data) || payments.data.length !== 1) fail7("refund_payment_ambiguous");
8890
9280
  const payment = row(payments.data[0]);
8891
- if (id2(payment.invoice) !== id2(invoice) || payment.status !== "paid" || row(payment.payment).type !== "payment_intent") fail6();
8892
- paymentIntentId = id2(row(payment.payment).payment_intent);
9281
+ if (id3(payment.invoice) !== id3(invoice) || payment.status !== "paid" || row(payment.payment).type !== "payment_intent") fail7();
9282
+ paymentIntentId = id3(row(payment.payment).payment_intent);
8893
9283
  }
8894
9284
  if (!chargeId) {
8895
- if (!providerId2(paymentIntentId, "pi")) fail6();
9285
+ if (!providerId2(paymentIntentId, "pi")) fail7();
8896
9286
  const payment = await call("GET", `/v1/payment_intents/${paymentIntentId}`);
8897
- if (id2(payment) !== paymentIntentId || id2(payment.customer) !== input.customerId || payment.status !== "succeeded") fail6();
8898
- chargeId = id2(payment.latest_charge);
9287
+ if (id3(payment) !== paymentIntentId || id3(payment.customer) !== input.customerId || payment.status !== "succeeded") fail7();
9288
+ chargeId = id3(payment.latest_charge);
8899
9289
  }
8900
- if (!providerId2(chargeId, "ch")) fail6();
9290
+ if (!providerId2(chargeId, "ch")) fail7();
8901
9291
  const charge = await call("GET", `/v1/charges/${chargeId}`);
8902
- if (id2(charge) !== chargeId || id2(charge.customer) !== input.customerId || charge.status !== "succeeded" || charge.invoice != null && id2(charge.invoice) !== id2(invoice) || paymentIntentId && id2(charge.payment_intent) !== paymentIntentId || !Number.isSafeInteger(charge.amount) || Number(charge.amount) <= 0 || !Number.isSafeInteger(charge.amount_refunded) || Number(charge.amount_refunded) < 0 || Number(charge.amount_refunded) > Number(charge.amount) || charge.currency !== invoice.currency || Number(charge.amount) !== Number(invoice.amount_paid)) fail6();
8903
- if (input.alreadyRefundedChargeId !== void 0 && (chargeId !== input.alreadyRefundedChargeId || charge.amount_refunded !== charge.amount || charge.refunded !== true)) fail6();
9292
+ 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();
9293
+ if (input.alreadyRefundedChargeId !== void 0 && (chargeId !== input.alreadyRefundedChargeId || charge.amount_refunded !== charge.amount || charge.refunded !== true)) fail7();
8904
9294
  if (charge.amount_refunded !== charge.amount) {
8905
9295
  const refund = await call("POST", "/v1/refunds", {
8906
9296
  charge: chargeId,
8907
9297
  ...input.metadata ? { metadata: input.metadata } : {}
8908
9298
  }, `chapter-initial-refund:${input.subscriptionId}:${chargeId}`);
8909
- if (id2(refund.charge) !== chargeId || !["succeeded", "pending"].includes(String(refund.status)) || !Number.isSafeInteger(refund.amount) || Number(refund.amount) !== Number(charge.amount) - Number(charge.amount_refunded)) fail6("refund_not_confirmed");
9299
+ if (id3(refund.charge) !== chargeId || !["succeeded", "pending"].includes(String(refund.status)) || !Number.isSafeInteger(refund.amount) || Number(refund.amount) !== Number(charge.amount) - Number(charge.amount_refunded)) fail7("refund_not_confirmed");
8910
9300
  }
8911
9301
  let subscriptionCanceled = sub.status === "canceled";
8912
9302
  if (input.cancelSubscription !== false && !subscriptionCanceled) {
8913
9303
  const canceled = await call("DELETE", `/v1/subscriptions/${input.subscriptionId}`);
8914
- if (id2(canceled) !== input.subscriptionId || canceled.status !== "canceled") fail6("refund_cancel_incomplete");
9304
+ if (id3(canceled) !== input.subscriptionId || canceled.status !== "canceled") fail7("refund_cancel_incomplete");
8915
9305
  subscriptionCanceled = true;
8916
9306
  }
8917
9307
  const confirmed = await call("GET", `/v1/charges/${chargeId}`);
8918
- if (id2(confirmed) !== chargeId || id2(confirmed.customer) !== input.customerId || confirmed.amount !== charge.amount || confirmed.currency !== charge.currency || confirmed.amount_refunded !== charge.amount || confirmed.refunded !== true) fail6("refund_not_confirmed");
9308
+ if (id3(confirmed) !== chargeId || id3(confirmed.customer) !== input.customerId || confirmed.amount !== charge.amount || confirmed.currency !== charge.currency || confirmed.amount_refunded !== charge.amount || confirmed.refunded !== true) fail7("refund_not_confirmed");
8919
9309
  return { refundedCents: Number(charge.amount), subscriptionCanceled };
8920
9310
  }
8921
9311
 
8922
9312
  // src/subscription-payment-identity.ts
8923
- import { stripeRequest as stripeRequest8 } from "@odla-ai/stripe";
8924
- var id3 = (value) => typeof value === "string" ? value : String(value?.id ?? "");
9313
+ import { stripeRequest as stripeRequest9 } from "@odla-ai/stripe";
9314
+ var id4 = (value) => typeof value === "string" ? value : String(value?.id ?? "");
8925
9315
  async function tagSubscriptionPayment(secretKey, invoice, customerId, metadata, clientSecret2, applicationId) {
8926
- let paymentId = id3(invoice.payment_intent);
9316
+ let paymentId = id4(invoice.payment_intent);
8927
9317
  if (!paymentId) {
8928
- if (!/^in_[A-Za-z0-9]+$/.test(id3(invoice))) subscriptionContractMismatch();
8929
- const result2 = await stripeRequest8({ secretKey }, "GET", "/v1/invoice_payments", { invoice: id3(invoice), limit: 100 });
9318
+ if (!/^in_[A-Za-z0-9]+$/.test(id4(invoice))) subscriptionContractMismatch();
9319
+ const result2 = await stripeRequest9({ secretKey }, "GET", "/v1/invoice_payments", { invoice: id4(invoice), limit: 100 });
8930
9320
  const list = result2.body;
8931
9321
  if (!result2.ok || list?.has_more !== false || !Array.isArray(list.data) || list.data.length !== 1) subscriptionContractMismatch();
8932
9322
  const link = list.data[0];
8933
- if (id3(link.invoice) !== id3(invoice) || !["open", "paid"].includes(link.status) || link.payment?.type !== "payment_intent") subscriptionContractMismatch();
8934
- paymentId = id3(link.payment.payment_intent);
9323
+ if (id4(link.invoice) !== id4(invoice) || !["open", "paid"].includes(link.status) || link.payment?.type !== "payment_intent") subscriptionContractMismatch();
9324
+ paymentId = id4(link.payment.payment_intent);
8935
9325
  }
8936
9326
  if (!/^pi_[A-Za-z0-9]+$/.test(paymentId)) subscriptionContractMismatch();
8937
9327
  const path = `/v1/payment_intents/${paymentId}`;
8938
- const result = await stripeRequest8({ secretKey }, "GET", path);
9328
+ const result = await stripeRequest9({ secretKey }, "GET", path);
8939
9329
  const payment = result.body;
8940
- if (!result.ok || payment?.id !== paymentId || id3(payment.customer) !== customerId || payment.amount !== invoice.total || payment.currency !== invoice.currency || payment.client_secret !== clientSecret2 || payment.livemode !== (metadata.odlaEnvironment === "prod") || !Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === void 0 || payment.metadata[key3] === value)) subscriptionContractMismatch();
9330
+ if (!result.ok || payment?.id !== paymentId || id4(payment.customer) !== customerId || payment.amount !== invoice.total || payment.currency !== invoice.currency || payment.client_secret !== clientSecret2 || payment.livemode !== (metadata.odlaEnvironment === "prod") || !Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === void 0 || payment.metadata[key3] === value)) subscriptionContractMismatch();
8941
9331
  if (Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === value)) return;
8942
- const tagged = await stripeRequest8(
9332
+ const tagged = await stripeRequest9(
8943
9333
  { secretKey },
8944
9334
  "POST",
8945
9335
  path,
@@ -8951,7 +9341,7 @@ async function tagSubscriptionPayment(secretKey, invoice, customerId, metadata,
8951
9341
 
8952
9342
  // src/payments-stripe.ts
8953
9343
  async function stripeCall(sk, method, path, params, idempotencyKey) {
8954
- return stripeRequest9(
9344
+ return stripeRequest10(
8955
9345
  { secretKey: sk },
8956
9346
  method,
8957
9347
  path,
@@ -8959,7 +9349,7 @@ async function stripeCall(sk, method, path, params, idempotencyKey) {
8959
9349
  idempotencyKey ? { idempotencyKey } : {}
8960
9350
  );
8961
9351
  }
8962
- function fail7(op, r) {
9352
+ function fail8(op, r) {
8963
9353
  const err = new Error(`stripe ${op} failed: ${r.status}`);
8964
9354
  err.code = "stripe_error";
8965
9355
  throw err;
@@ -8998,6 +9388,15 @@ function createStripeProvider(config) {
8998
9388
  let customerId = input.existingCustomerId;
8999
9389
  if (customerId && !/^cus_[A-Za-z0-9]+$/.test(customerId) || input.existingSubscriptionId && (!customerId || !/^sub_[A-Za-z0-9]+$/.test(input.existingSubscriptionId))) subscriptionContractMismatch();
9000
9390
  const productId = await verifySubscriptionPrice(sk, input, Boolean(input.existingSubscriptionId));
9391
+ const seat = input.namedSeat?.amountCents ? input.namedSeat : void 0;
9392
+ const seatProductId = seat ? await verifySubscriptionPrice(sk, {
9393
+ ...input,
9394
+ priceId: seat.priceId,
9395
+ priceCents: seat.amountCents,
9396
+ billingTerms: void 0,
9397
+ namedSeat: void 0
9398
+ }, Boolean(input.existingSubscriptionId)) : void 0;
9399
+ if (seat && (!seatProductId || !productId || seatProductId === productId)) subscriptionContractMismatch();
9001
9400
  if (input.billingTerms?.discount && !productId) subscriptionContractMismatch();
9002
9401
  await prepareSubscriptionBillingTerms(sk, { ...input, discountProductId: productId }, Boolean(input.existingSubscriptionId));
9003
9402
  const billingParams = subscriptionBillingParams(input, productId);
@@ -9009,7 +9408,7 @@ function createStripeProvider(config) {
9009
9408
  { email: input.email, name: input.name, metadata: meta },
9010
9409
  `cus:${input.applicationId}`
9011
9410
  );
9012
- if (!cust.ok) fail7("customer create", cust);
9411
+ if (!cust.ok) fail8("customer create", cust);
9013
9412
  if (typeof cust.body.id !== "string" || !/^cus_[A-Za-z0-9]+$/.test(cust.body.id)) subscriptionContractMismatch();
9014
9413
  customerId = cust.body.id;
9015
9414
  }
@@ -9029,19 +9428,32 @@ function createStripeProvider(config) {
9029
9428
  {
9030
9429
  customer: customerId,
9031
9430
  "items[0][price]": input.priceId,
9431
+ ...seat ? {
9432
+ "items[1][price]": seat.priceId,
9433
+ "items[1][quantity]": 1,
9434
+ "items[1][metadata][seatId]": seat.seatId,
9435
+ "items[1][metadata][membershipId]": seat.membershipId
9436
+ } : {},
9032
9437
  payment_behavior: "default_incomplete",
9033
9438
  "payment_settings[save_default_payment_method]": "on_subscription",
9034
9439
  "payment_settings[payment_method_types][0]": "card",
9035
9440
  "expand[0]": "latest_invoice.confirmation_secret",
9036
9441
  "expand[1]": "latest_invoice.payment_intent",
9037
9442
  metadata: meta,
9038
- ...billingParams
9443
+ ...billingParams,
9444
+ ...seat && input.billingTerms?.initialTermEndsAt ? {
9445
+ "add_invoice_items[1][price_data][product]": seatProductId,
9446
+ "add_invoice_items[1][price_data][currency]": input.currency,
9447
+ "add_invoice_items[1][price_data][unit_amount]": seat.amountCents,
9448
+ "add_invoice_items[1][quantity]": 1,
9449
+ "add_invoice_items[1][discountable]": false
9450
+ } : {}
9039
9451
  },
9040
9452
  // Hardening: one subscription per application, so a client
9041
9453
  // retry between create and the db write can't orphan a second one.
9042
9454
  `sub:${input.applicationId}${input.purchaseAttemptId ? `:${input.purchaseAttemptId}` : ""}`
9043
9455
  );
9044
- if (!sub.ok) fail7("subscription create", sub);
9456
+ if (!sub.ok) fail8("subscription create", sub);
9045
9457
  if (input.billingTerms?.discount?.duration === "once" && Array.isArray(sub.body.discounts) && sub.body.discounts.length === 0) {
9046
9458
  const subscriptionId = sub.body.id;
9047
9459
  if (typeof subscriptionId !== "string" || !/^sub_[A-Za-z0-9]+$/.test(subscriptionId)) subscriptionContractMismatch();
@@ -9051,14 +9463,14 @@ function createStripeProvider(config) {
9051
9463
  "expand[2]": "discounts",
9052
9464
  "expand[3]": "latest_invoice.discounts"
9053
9465
  });
9054
- if (!expanded.ok) fail7("subscription invoice evidence", expanded);
9466
+ if (!expanded.ok) fail8("subscription invoice evidence", expanded);
9055
9467
  if (expanded.body.id !== subscriptionId) subscriptionContractMismatch();
9056
9468
  sub = expanded;
9057
9469
  }
9058
9470
  verifySubscriptionResult(sub.body, input, customerId, meta);
9059
9471
  const invoice = sub.body.latest_invoice;
9060
9472
  const clientSecret2 = clientSecretOf(sub.body);
9061
- if (!clientSecret2) fail7("subscription confirmation-secret missing", sub);
9473
+ if (!clientSecret2) fail8("subscription confirmation-secret missing", sub);
9062
9474
  await tagSubscriptionPayment(
9063
9475
  sk,
9064
9476
  invoice,
@@ -9270,9 +9682,9 @@ function matchesIntent(stored, expected) {
9270
9682
  "purchaseAttemptId"
9271
9683
  ].every((key3) => stored[key3] === expected[key3]) && customerMatches && JSON.stringify(storedTerms) === JSON.stringify(expected.billingTerms) && stored.membershipOffer?.digest === expected.membershipOffer?.digest && stored.paymentQuote?.digest === expected.paymentQuote?.digest;
9272
9684
  }
9273
- async function loadIntent(db, id6) {
9685
+ async function loadIntent(db, id7) {
9274
9686
  return first3((await db.query({
9275
- subscriptionCheckoutIntents: { $: { where: { id: id6 }, limit: 1 } }
9687
+ subscriptionCheckoutIntents: { $: { where: { id: id7 }, limit: 1 } }
9276
9688
  })).subscriptionCheckoutIntents);
9277
9689
  }
9278
9690
  async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCheckout) {
@@ -9293,16 +9705,22 @@ async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCh
9293
9705
  if (input.purchaseAttemptId !== application?.membershipRestartId) mismatch();
9294
9706
  if (application?.namedSeatId || application?.namedSeatClaimPending || application?.giftClaimId) mismatch();
9295
9707
  if (input.membershipOffer && (!application || application.canceled === true || application.status !== "submitted")) mismatch();
9708
+ if (input.paymentQuote?.namedSeat && application?.email !== input.email) mismatch();
9296
9709
  const applicationGuard = {
9297
9710
  ns: "applications",
9298
9711
  id: input.applicationId,
9299
9712
  exists: true,
9300
- equals: { status: "submitted", ...input.purchaseAttemptId ? { membershipRestartId: input.purchaseAttemptId } : {}, ...input.membershipOffer ? {
9301
- groupId: input.groupId,
9302
- tierId: input.tierId,
9303
- stripeRuntime: stripeRuntimeIdentity(input.runtime),
9304
- ...application?.canceled !== void 0 ? { canceled: application.canceled } : {}
9305
- } : {} },
9713
+ equals: {
9714
+ status: "submitted",
9715
+ ...input.paymentQuote?.namedSeat ? { email: input.email } : {},
9716
+ ...input.purchaseAttemptId ? { membershipRestartId: input.purchaseAttemptId } : {},
9717
+ ...input.membershipOffer ? {
9718
+ groupId: input.groupId,
9719
+ tierId: input.tierId,
9720
+ stripeRuntime: stripeRuntimeIdentity(input.runtime),
9721
+ ...application?.canceled !== void 0 ? { canceled: application.canceled } : {}
9722
+ } : {}
9723
+ },
9306
9724
  absent: ["namedSeatId", "namedSeatClaimPending", "giftClaimId", ...!input.purchaseAttemptId ? ["membershipRestartId"] : [], ...input.membershipOffer && application?.canceled === void 0 ? ["canceled"] : []]
9307
9725
  };
9308
9726
  let stored = await loadIntent(db, input.applicationId);
@@ -9326,7 +9744,11 @@ async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCh
9326
9744
  ns: MEMBERSHIP_QUOTE_NAMESPACE,
9327
9745
  id: input.applicationId,
9328
9746
  exists: true,
9329
- equals: { status: "ready", offerDigest: input.membershipOffer.digest }
9747
+ equals: {
9748
+ status: "ready",
9749
+ offerDigest: input.membershipOffer.digest,
9750
+ ...input.projectionRevision !== void 0 ? { revision: input.projectionRevision } : {}
9751
+ }
9330
9752
  }
9331
9753
  ] : []
9332
9754
  ]
@@ -9341,7 +9763,8 @@ async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCh
9341
9763
  if (!stored.providerSubscriptionId && input.now - Number(stored.createdAt) >= 23 * 60 * 6e4) {
9342
9764
  throw Object.assign(new Error("checkout requires provider reconciliation"), { code: "checkout_recovery_required" });
9343
9765
  }
9344
- if (input.membershipOffer) await authorizeNetworkCheckout();
9766
+ const binding = input.membershipOffer ? await authorizeNetworkCheckout() : void 0;
9767
+ if (input.paymentQuote?.namedSeat && (!binding?.namedSeat || binding.namedSeat.amountCents !== input.paymentQuote.lineItems.namedSeatCents || binding.namedSeat.recipientEmail !== input.paymentQuote.namedSeat.recipientEmail || binding.namedSeat.recipientName !== input.paymentQuote.namedSeat.recipientName || binding.namedSeat.purchaserEmail !== input.emailNormalized)) mismatch();
9345
9768
  const result = await provider.createSubscription({
9346
9769
  appId: input.appId,
9347
9770
  chapterId: input.chapterId,
@@ -9356,6 +9779,7 @@ async function startSubscriptionCheckout(db, provider, input, authorizeNetworkCh
9356
9779
  priceCents: input.priceCents,
9357
9780
  currency: input.currency,
9358
9781
  interval: input.interval,
9782
+ ...binding?.namedSeat ? { namedSeat: binding.namedSeat } : {},
9359
9783
  ...stored.billingTerms ? { billingTerms: stored.billingTerms } : {},
9360
9784
  ...typeof stored.providerSubscriptionId === "string" ? { existingSubscriptionId: stored.providerSubscriptionId } : {},
9361
9785
  existingCustomerId: typeof stored.providerCustomerId === "string" ? stored.providerCustomerId : input.existingCustomerId
@@ -9472,6 +9896,7 @@ async function startSubscription(req, env, ctx) {
9472
9896
  return json({ error: "payment quote unavailable", ...expired ? { code: "payment_quote_changed" } : {} }, expired ? 409 : 503);
9473
9897
  }
9474
9898
  const { tier, quote, billingTerms } = checkout2;
9899
+ if (quote.namedSeat && body.seatTermsAck !== true) return json({ error: "seatTermsAck required" }, 400);
9475
9900
  const membershipOffer = "membershipOffer" in checkout2 ? checkout2.membershipOffer : void 0;
9476
9901
  if (ctx.membershipAuthority && !membershipOffer) return json({ error: "checkout requires reconciliation", code: "checkout_recovery_required" }, 409);
9477
9902
  if (!tier) return json({ error: "tier not found", code: "tier_unresolved" }, 400);
@@ -9496,6 +9921,7 @@ async function startSubscription(req, env, ctx) {
9496
9921
  priceCents: billingTerms ? tier.priceCents : quote.lineItems.dueTodayCents,
9497
9922
  ...billingTerms ? { billingTerms } : {},
9498
9923
  ...membershipOffer ? { membershipOffer } : {},
9924
+ ..."projectionRevision" in checkout2 ? { projectionRevision: checkout2.projectionRevision } : {},
9499
9925
  ...ctx.requirePaymentQuote || ctx.membershipAuthority || body.quoteDigest ? { paymentQuote: quote } : {},
9500
9926
  currency: tier.currency ?? String(group.currency ?? "usd"),
9501
9927
  interval: tier.interval ?? (group.interval === "month" ? "month" : "year"),
@@ -9517,9 +9943,10 @@ async function startSubscription(req, env, ctx) {
9517
9943
  offerId: membershipOffer.id,
9518
9944
  offerDigest: membershipOffer.digest,
9519
9945
  quoteDigest: quote.digest,
9520
- priceId: String(priceId)
9946
+ priceId: String(priceId),
9947
+ ...quote.namedSeat ? { namedSeat: { ...quote.namedSeat, purchaserEmail: normalizeChapterEmail(String(app.email ?? "")) } } : {}
9521
9948
  };
9522
- await verifyMembershipCheckoutBinding(await ctx.membershipAuthority.bindCheckout(binding, env), binding);
9949
+ return verifyMembershipCheckoutBinding(await ctx.membershipAuthority.bindCheckout(binding, env), binding);
9523
9950
  } : void 0);
9524
9951
  } catch (err) {
9525
9952
  const code = paymentErrorCode(err);
@@ -9528,6 +9955,58 @@ async function startSubscription(req, env, ctx) {
9528
9955
  return json({ clientSecret: result.clientSecret, publishableKey: group.stripePublishableKey ?? null, lineItems: quote.lineItems });
9529
9956
  }
9530
9957
 
9958
+ // src/worker-payment-receipt.ts
9959
+ var json2 = (value, status2 = 200) => Response.json(value, { status: status2, headers: { "cache-control": "no-store", "referrer-policy": "no-referrer" } });
9960
+ var escape = (text8) => text8.replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[char]);
9961
+ async function paymentReceiptRoute(req, url, env, ctx) {
9962
+ const id7 = url.searchParams.get("application");
9963
+ if (!id7 || id7.length > 160) return json2({ error: "application required" }, 400);
9964
+ if (req.method === "POST" && req.headers.has("origin") && req.headers.get("origin") !== url.origin) return json2({ error: "forbidden" }, 403);
9965
+ const db = ctx.makeDb(env);
9966
+ const rows5 = await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } }, subscriptionCheckoutIntents: { $: { where: { id: id7 }, limit: 1 } } });
9967
+ const app = rows5.applications?.[0], intent = rows5.subscriptionCheckoutIntents?.[0];
9968
+ 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);
9969
+ const receipt = intent.paymentReceipt;
9970
+ if (!receipt) return json2({ pending: true }, 202);
9971
+ const text8 = paymentReceiptText(receipt, ctx.chapter.name);
9972
+ if (req.method === "POST") {
9973
+ if (!env.SEND_EMAIL || !env.EMAIL_FROM) return json2({ error: "Email is unavailable. You can print or save your receipt." }, 503);
9974
+ const group = (await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups?.[0];
9975
+ if (!group) return json2({ error: "Receipt email is unavailable." }, 503);
9976
+ const config = emailGroupFrom(group);
9977
+ const debugEmail = (env.ODLA_DEBUG_EMAIL ?? config.debugEmail)?.trim();
9978
+ if (env.ODLA_ENV !== "prod" && (!debugEmail || !/^[^\s@,;<>]+@[^\s@,;<>]+\.[^\s@,;<>]+$/.test(debugEmail))) {
9979
+ return json2({ error: "A test inbox is required to email receipts in this environment." }, 503);
9980
+ }
9981
+ const result = await sendTemplated(lifecycleNotifyDeps(db, env), {
9982
+ group: { ...config, emailTemplates: {
9983
+ ...config.emailTemplates,
9984
+ paymentReceipt: { enabled: true, subject: "Your {{chapterName}} payment receipt", text: "{{receiptText}}" }
9985
+ } },
9986
+ template: "paymentReceipt",
9987
+ to: String(intent.purchaserEmailNormalized),
9988
+ applicationId: id7,
9989
+ vars: { chapterName: ctx.chapter.name, receiptText: text8 },
9990
+ dedupeKey: `requested-payment-receipt:${receipt.invoiceId}`,
9991
+ event: { label: "Payment receipt", trigger: "Purchaser requests their receipt", audience: "Purchaser", variables: ["chapterName", "receiptText"] }
9992
+ });
9993
+ 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);
9994
+ }
9995
+ if (url.searchParams.get("format") !== "html") return json2({ receipt, chapterName: ctx.chapter.name });
9996
+ const nonce = crypto.randomUUID();
9997
+ return new Response(`<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Payment receipt</title>
9998
+ <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>
9999
+ <h1>Payment receipt</h1><pre>${escape(text8)}</pre><button id="print">Print / save PDF</button>
10000
+ <script nonce="${nonce}">document.getElementById('print').addEventListener('click',()=>window.print())</script></html>`, {
10001
+ headers: {
10002
+ "content-type": "text/html; charset=utf-8",
10003
+ "cache-control": "no-store",
10004
+ "referrer-policy": "no-referrer",
10005
+ "content-security-policy": `default-src 'none'; style-src 'unsafe-inline'; script-src 'nonce-${nonce}'; frame-ancestors 'none'; base-uri 'none'`
10006
+ }
10007
+ });
10008
+ }
10009
+
9531
10010
  // src/worker-routes-payments.ts
9532
10011
  async function ingestWebhook(req, env, ctx) {
9533
10012
  const db = ctx.makeDb(env);
@@ -9540,8 +10019,9 @@ async function ingestWebhook(req, env, ctx) {
9540
10019
  }
9541
10020
  var handlePayments = async (req, url, env, ctx) => {
9542
10021
  if (ctx.chapter.mode !== "chapter") return null;
10022
+ if (["GET", "POST"].includes(req.method) && url.pathname === "/api/payments/receipt") return paymentReceiptRoute(req, url, env, ctx);
9543
10023
  if (req.method === "GET" && url.pathname === "/api/payments/quote") return paymentQuoteRoute(url, env, ctx);
9544
- if (req.method === "POST" && url.pathname === "/api/payments/quote") return paymentQuoteRoute(url, env, ctx, true);
10024
+ if (req.method === "POST" && url.pathname === "/api/payments/quote") return paymentQuoteRoute(url, env, ctx, true, req);
9545
10025
  if (req.method === "POST" && url.pathname === "/api/payments/subscription") return startSubscription(req, env, ctx);
9546
10026
  if (req.method === "POST" && url.pathname === "/api/webhooks/stripe") return ingestWebhook(req, env, ctx);
9547
10027
  return null;
@@ -9555,16 +10035,16 @@ var userFor = async (req, env, ctx) => {
9555
10035
  const user = await ctx.verifyUser(req, env);
9556
10036
  return user?.email ? user : null;
9557
10037
  };
9558
- var loadClaim = async (db, id6) => first4((await db.query({ giftClaims: { $: { where: { id: id6 }, limit: 1 } } })).giftClaims);
10038
+ var loadClaim = async (db, id7) => first4((await db.query({ giftClaims: { $: { where: { id: id7 }, limit: 1 } } })).giftClaims);
9559
10039
  async function consumeRateLimit(db, chapterId, requester, now) {
9560
10040
  const bucket = await giftRequesterBucket(chapterId, requester);
9561
- const id6 = `gift-rate:${chapterId}:${bucket}`;
9562
- const current = first4((await db.query({ giftRedemptionAttempts: { $: { where: { id: id6 }, limit: 1 } } })).giftRedemptionAttempts);
10041
+ const id7 = `gift-rate:${chapterId}:${bucket}`;
10042
+ const current = first4((await db.query({ giftRedemptionAttempts: { $: { where: { id: id7 }, limit: 1 } } })).giftRedemptionAttempts);
9563
10043
  const within = Boolean(current && now - Number(current.windowStartedAt) < 15 * 6e4);
9564
10044
  const attemptCount = within ? Number(current?.attemptCount ?? 0) + 1 : 1;
9565
10045
  if (attemptCount > 10) return false;
9566
- await db.transact([{ t: "update", ns: "giftRedemptionAttempts", id: id6, attrs: {
9567
- id: id6,
10046
+ await db.transact([{ t: "update", ns: "giftRedemptionAttempts", id: id7, attrs: {
10047
+ id: id7,
9568
10048
  chapterId,
9569
10049
  bucket,
9570
10050
  attemptCount,
@@ -9738,14 +10218,14 @@ var at = (value) => Number.isSafeInteger(value) ? Number(value) : void 0;
9738
10218
  async function readNamedSeatOffer(db, input, now) {
9739
10219
  membershipClock(now);
9740
10220
  await loadMembershipAuthorityState(db, input);
9741
- const id6 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id6);
10221
+ const id7 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id7);
9742
10222
  requireMembershipScope(member, input);
9743
10223
  const offering = membershipOfferingVersion(member.offeringSnapshot);
9744
10224
  const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
9745
10225
  if (seat) requireMembershipScope(seat, input);
9746
10226
  const purchaseEndsAt = seatPurchaseDeadline(offering, member);
9747
10227
  const offer = {
9748
- membershipId: id6,
10228
+ membershipId: id7,
9749
10229
  amountCents: offering.seatPriceCents,
9750
10230
  currency: offering.currency,
9751
10231
  interval: offering.interval,
@@ -10114,18 +10594,18 @@ var handleEmailDeliveryResolution = async (req, url, env, ctx) => {
10114
10594
  if (!user) return json({ error: "unauthorized" }, 401);
10115
10595
  if (!await ctx.isAdmin(db, user)) return json({ error: "forbidden" }, 403);
10116
10596
  try {
10117
- const id6 = decodeURIComponent(match[1]);
10597
+ const id7 = decodeURIComponent(match[1]);
10118
10598
  const body = await namedSeatRequest(req);
10119
10599
  if (Object.keys(body).some((key3) => !["expectedAttempt", "expectedState", "decision", "reason", "messageId"].includes(key3)) || body.messageId !== void 0 && (typeof body.messageId !== "string" || body.messageId.length > 255 || /[\r\n]/.test(body.messageId))) {
10120
10600
  return json({ error: "invalid delivery evidence" }, 400);
10121
10601
  }
10122
- const chapterDb = db, row2 = await membershipLoad(chapterDb, "emailLog", id6);
10602
+ const chapterDb = db, row2 = await membershipLoad(chapterDb, "emailLog", id7);
10123
10603
  if (!row2 || row2.groupId !== ctx.chapter.id) return json({ error: "not found" }, 404);
10124
10604
  if (row2.applicationId) {
10125
10605
  const app = await membershipLoad(chapterDb, "applications", String(row2.applicationId));
10126
10606
  if (!app || !chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME)) return json({ error: "not found" }, 404);
10127
10607
  }
10128
- await reconcileLifecycleDelivery(chapterDb, { ...body, id: id6, groupId: ctx.chapter.id, actorId: user.userId }, Date.now());
10608
+ await reconcileLifecycleDelivery(chapterDb, { ...body, id: id7, groupId: ctx.chapter.id, actorId: user.userId }, Date.now());
10129
10609
  return json({ ok: true, sent: false });
10130
10610
  } catch {
10131
10611
  return json({ error: "delivery evidence is invalid, stale, or still in progress; reload the exact attempt" }, 409);
@@ -10228,13 +10708,13 @@ var handlePeopleProfile = async (req, url, env, ctx) => {
10228
10708
  const user = await ctx.verifyUser(req, env);
10229
10709
  if (!user) return json({ error: "unauthorized" }, 401);
10230
10710
  if (!await ctx.isAdmin(db, user)) return json({ error: "forbidden" }, 403);
10231
- let id6;
10711
+ let id7;
10232
10712
  try {
10233
- id6 = dedicated ? url.searchParams.get("recordId") ?? "" : decodeURIComponent(match[1]);
10713
+ id7 = dedicated ? url.searchParams.get("recordId") ?? "" : decodeURIComponent(match[1]);
10234
10714
  } catch {
10235
10715
  return json({ error: "invalid record id" }, 422);
10236
10716
  }
10237
- const record4 = await getRecord3({ crm: ctx.chapter.crm, db }, id6);
10717
+ const record4 = await getRecord3({ crm: ctx.chapter.crm, db }, id7);
10238
10718
  if (!record4) return json({ error: "not found" }, 404);
10239
10719
  if (record4.type !== "person") return dedicated ? json({ error: "person required" }, 422) : null;
10240
10720
  if (match?.[2]) {
@@ -10249,9 +10729,9 @@ var handlePeopleProfile = async (req, url, env, ctx) => {
10249
10729
  }
10250
10730
  if (req.method === "GET" && dedicated) {
10251
10731
  const excluded = profileReadOnlyFields(record4, ctx);
10252
- const fields = Object.entries(ctx.chapter.crm.type(record4.type).fields).filter(([id7]) => !excluded.includes(id7)).map(([id7, field]) => ({
10253
- id: id7,
10254
- label: field.label ?? id7,
10732
+ const fields = Object.entries(ctx.chapter.crm.type(record4.type).fields).filter(([id8]) => !excluded.includes(id8)).map(([id8, field]) => ({
10733
+ id: id8,
10734
+ label: field.label ?? id8,
10255
10735
  type: field.type,
10256
10736
  required: field.required ?? false,
10257
10737
  ...field.options ? { options: field.options } : {}
@@ -11022,12 +11502,12 @@ var readJson = async (req) => {
11022
11502
  return null;
11023
11503
  }
11024
11504
  };
11025
- async function loadApp(db, id6) {
11026
- const { applications } = await db.query({ applications: { $: { where: { id: id6 }, limit: 1 } } });
11505
+ async function loadApp(db, id7) {
11506
+ const { applications } = await db.query({ applications: { $: { where: { id: id7 }, limit: 1 } } });
11027
11507
  return applications?.[0] ?? null;
11028
11508
  }
11029
- async function loadGroup(db, id6) {
11030
- const { groups } = await db.query({ groups: { $: { where: { id: id6 }, limit: 1 } } });
11509
+ async function loadGroup(db, id7) {
11510
+ const { groups } = await db.query({ groups: { $: { where: { id: id7 }, limit: 1 } } });
11031
11511
  return groups?.[0] ?? null;
11032
11512
  }
11033
11513
  var handleAdminApplicationActions = async (req, url, env, ctx) => {
@@ -11113,8 +11593,8 @@ var handleAdminApprove = async (req, url, env, ctx) => {
11113
11593
  const got = await gate4(req, env, ctx);
11114
11594
  if (got instanceof Response) return got;
11115
11595
  const db = got;
11116
- const id6 = m[1];
11117
- const app = await loadApp(db, id6);
11596
+ const id7 = m[1];
11597
+ const app = await loadApp(db, id7);
11118
11598
  if (!app) return json({ error: "not found" }, 404);
11119
11599
  const result = await approveApplication({ db, env, ctx, app, origin: url.origin, reason: "manual" });
11120
11600
  if (!result.ok) return json({ error: result.error }, result.status);
@@ -11143,10 +11623,10 @@ var handleAdminApplicationPatch = async (req, url, env, ctx) => {
11143
11623
  const got = await gate4(req, env, ctx);
11144
11624
  if (got instanceof Response) return got;
11145
11625
  const db = got;
11146
- const id6 = m[1];
11626
+ const id7 = m[1];
11147
11627
  const body = await readJson(req);
11148
11628
  if (!body) return json({ error: "invalid JSON body" }, 400);
11149
- const app = await loadApp(db, id6);
11629
+ const app = await loadApp(db, id7);
11150
11630
  if (!app) return json({ error: "not found" }, 404);
11151
11631
  return patchApplication(db, env, ctx, app, body);
11152
11632
  };
@@ -11256,10 +11736,10 @@ async function updateEmailSettings(db, ctx, env, body) {
11256
11736
  const missing = Object.fromEntries(Object.keys(events).filter((key3) => !group?.emailTemplates?.[key3]).map((key3) => [key3, { subject: "", text: "", enabled: false }]));
11257
11737
  await saveConfig({ db, crm: ctx.chapter.crm }, { emailTemplates: { ...missing, ...clean }, notificationEmail, replyTo, debugEmail });
11258
11738
  } else {
11259
- const id6 = { ns: "groups", attr: "id", value: ctx.chapter.id };
11739
+ const id7 = { ns: "groups", attr: "id", value: ctx.chapter.id };
11260
11740
  await db.transact([
11261
- { t: "merge", ns: "groups", id: id6, attrs: { emailTemplates: clean } },
11262
- { t: "update", ns: "groups", id: id6, attrs: { commitmentText, normsText, notificationEmail, replyTo, debugEmail, fromName } }
11741
+ { t: "merge", ns: "groups", id: id7, attrs: { emailTemplates: clean } },
11742
+ { t: "update", ns: "groups", id: id7, attrs: { commitmentText, normsText, notificationEmail, replyTo, debugEmail, fromName } }
11263
11743
  ]);
11264
11744
  }
11265
11745
  return json({ ok: true });
@@ -11274,8 +11754,8 @@ async function gate5(req, env, ctx) {
11274
11754
  if (!await ctx.isAdmin(rawDb, u)) return json({ error: "forbidden" }, 403);
11275
11755
  return rawDb;
11276
11756
  }
11277
- async function loadGroup2(db, id6) {
11278
- const { groups } = await db.query({ groups: { $: { where: { id: id6 }, limit: 1 } } });
11757
+ async function loadGroup2(db, id7) {
11758
+ const { groups } = await db.query({ groups: { $: { where: { id: id7 }, limit: 1 } } });
11279
11759
  return groups?.[0] ?? null;
11280
11760
  }
11281
11761
  var str3 = (v, d = "") => typeof v === "string" ? v : d;
@@ -11529,7 +12009,7 @@ var handleAdminNetworkTargets = async (req, url, env, ctx) => {
11529
12009
  if ("response" in got) return got.response;
11530
12010
  return json({
11531
12011
  targets: ctx.chapter.network.targets.map(({
11532
- id: id6,
12012
+ id: id7,
11533
12013
  name,
11534
12014
  url: targetUrl,
11535
12015
  fields,
@@ -11538,7 +12018,7 @@ var handleAdminNetworkTargets = async (req, url, env, ctx) => {
11538
12018
  stageTransitions,
11539
12019
  commercialParity
11540
12020
  }) => ({
11541
- id: id6,
12021
+ id: id7,
11542
12022
  name,
11543
12023
  url: targetUrl,
11544
12024
  types: Object.keys(fields ?? DEFAULT_SHARE_FIELDS),
@@ -11700,7 +12180,7 @@ var handleAdminNetworkPush = async (req, url, env, ctx) => {
11700
12180
  if (typeof body.recordId !== "string" || !Array.isArray(body.targetIds) || body.targetIds.length === 0) {
11701
12181
  return json({ error: "recordId and a non-empty targetIds array are required" }, 400);
11702
12182
  }
11703
- const requested = new Set(body.targetIds.filter((id6) => typeof id6 === "string"));
12183
+ const requested = new Set(body.targetIds.filter((id7) => typeof id7 === "string"));
11704
12184
  if (requested.size !== body.targetIds.length) return json({ error: "targetIds must contain unique strings" }, 400);
11705
12185
  const targets = ctx.chapter.network.targets.filter((target) => requested.has(target.id));
11706
12186
  if (targets.length !== requested.size) return json({ error: "one or more targetIds are not configured" }, 400);
@@ -11833,7 +12313,7 @@ async function operationalReaderRecord(record4, reader, db, env, ctx) {
11833
12313
  const appId = record4.fields.applicationId;
11834
12314
  const app = typeof appId === "string" ? (await db.query({ applications: { $: { where: { id: appId }, limit: 1 } } })).applications?.[0] : void 0;
11835
12315
  if (reader.stageTransitions.includes(record4.type)) {
11836
- result.edit.allowedStages = app ? applicationActionStages(app, env, ctx).map((id6) => ({ id: id6, label: id6.replaceAll("_", " ") })) : result.edit.allowedStages.filter((stage) => !isApplicationStage(ctx, stage.id));
12316
+ result.edit.allowedStages = app ? applicationActionStages(app, env, ctx).map((id7) => ({ id: id7, label: id7.replaceAll("_", " ") })) : result.edit.allowedStages.filter((stage) => !isApplicationStage(ctx, stage.id));
11837
12317
  const lock = (await db.query({ crm_activity: { $: { where: { id: `network-action-lock:${record4.id}` }, limit: 1 } } })).crm_activity?.[0];
11838
12318
  const meta = lock?.meta;
11839
12319
  if (meta?.state === "pending" && typeof meta.activityId === "string") {
@@ -11938,9 +12418,9 @@ async function listReaderRecords(db, type, allowed2, emailField, window) {
11938
12418
  // src/network-mutation-receipt.ts
11939
12419
  import { CRM_NS as CRM_NS2 } from "@odla-ai/crm";
11940
12420
  var NETWORK_MUTATION_META = "chapterNetworkMutation";
11941
- async function priorMutation(db, id6) {
12421
+ async function priorMutation(db, id7) {
11942
12422
  const result = await db.query({
11943
- [CRM_NS2.activity]: { $: { where: { id: id6 }, limit: 1 } }
12423
+ [CRM_NS2.activity]: { $: { where: { id: id7 }, limit: 1 } }
11944
12424
  });
11945
12425
  const row2 = (result[CRM_NS2.activity] ?? [])[0];
11946
12426
  const meta = row2?.meta;
@@ -12200,8 +12680,8 @@ async function gate7(req, env, ctx) {
12200
12680
  if (!await networkWriteAllowed(req, db, user, ctx)) return { response: json({ error: "super-admin required", code: "super_admin_required" }, 403) };
12201
12681
  return { db, user };
12202
12682
  }
12203
- function targetFor2(ctx, id6) {
12204
- return ctx.chapter.network.targets.find((target) => target.id === id6);
12683
+ function targetFor2(ctx, id7) {
12684
+ return ctx.chapter.network.targets.find((target) => target.id === id7);
12205
12685
  }
12206
12686
  async function readTargetRecords(db, env, ctx, target, query) {
12207
12687
  const secret = await getVaultSecret(db, target.secretName);
@@ -12336,7 +12816,7 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
12336
12816
  if (ctx.chapter.network.requireSuperAdminWrites && !await ctx.isSuperAdminEmail(got.db, got.user.email, got.user.userId)) {
12337
12817
  return json({ ...result.page, records: result.page.records.map(({ edit: _edit, ...record4 }) => ({
12338
12818
  ...record4,
12339
- fields: Object.fromEntries(Object.entries(record4.fields).filter(([id6]) => id6 === "name" || id6 === "email"))
12819
+ fields: Object.fromEntries(Object.entries(record4.fields).filter(([id7]) => id7 === "name" || id7 === "email"))
12340
12820
  })) });
12341
12821
  }
12342
12822
  return json(result.page);
@@ -12406,9 +12886,9 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
12406
12886
  }
12407
12887
  const body = typeof input.body === "string" ? input.body.trim() : "";
12408
12888
  if (!body || body.length > MAX_NOTE3) return json({ error: `note must be 1\u2013${MAX_NOTE3} characters` }, 400);
12409
- const id6 = crypto.randomUUID();
12889
+ const id7 = crypto.randomUUID();
12410
12890
  const note = {
12411
- id: id6,
12891
+ id: id7,
12412
12892
  targetId: identity2.targetId,
12413
12893
  recordType: identity2.recordType,
12414
12894
  recordId: identity2.recordId,
@@ -12420,7 +12900,7 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
12420
12900
  await got.db.transact([{
12421
12901
  t: "update",
12422
12902
  ns: "networkNotes",
12423
- id: id6,
12903
+ id: id7,
12424
12904
  attrs: { ...note, recordKey: identity2.recordKey }
12425
12905
  }]);
12426
12906
  return json({ note }, 201);
@@ -12524,13 +13004,13 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
12524
13004
  return json({ error: `body must be 1\u2013${MAX_NOTE4} characters and mutationId must be valid` }, 400);
12525
13005
  }
12526
13006
  const createdAt = Date.now();
12527
- const id6 = `network:${verified.sender}:${mutationId}`;
13007
+ const id7 = `network:${verified.sender}:${mutationId}`;
12528
13008
  const result = await addActivity(
12529
13009
  {
12530
13010
  crm: ctx.chapter.crm,
12531
13011
  db,
12532
13012
  now: () => createdAt,
12533
- newId: () => id6
13013
+ newId: () => id7
12534
13014
  },
12535
13015
  {
12536
13016
  recordId,
@@ -12545,11 +13025,11 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
12545
13025
  body
12546
13026
  }
12547
13027
  },
12548
- mutationId: id6
13028
+ mutationId: id7
12549
13029
  }
12550
13030
  );
12551
13031
  const note = {
12552
- id: id6,
13032
+ id: id7,
12553
13033
  sourceId: verified.sender,
12554
13034
  recordType,
12555
13035
  recordId,
@@ -12558,8 +13038,8 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
12558
13038
  };
12559
13039
  return json({ version: 1, site: { id: ctx.chapter.id, name: ctx.chapter.name }, note, duplicate: result.duplicate }, 201);
12560
13040
  };
12561
- function targetFor3(ctx, id6) {
12562
- return ctx.chapter.network.targets.find((target) => target.id === id6);
13041
+ function targetFor3(ctx, id7) {
13042
+ return ctx.chapter.network.targets.find((target) => target.id === id7);
12563
13043
  }
12564
13044
  var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
12565
13045
  if (url.pathname !== "/api/admin/network/shared-notes" || req.method !== "GET" && req.method !== "POST") return null;
@@ -12828,10 +13308,10 @@ async function linkNetworkChapterPerson(db, input, mutationId, actor, now = Date
12828
13308
  if (!state.claims.some((claim) => claim.id === expectedClaim.id && claim.personId === person.id && claim.kind === "email")) {
12829
13309
  throw new NetworkPersonRegistryError("identity_mismatch", "source email is not an exact verified identity on this Person");
12830
13310
  }
12831
- const id6 = await networkChapterLinkId(person.id, input.chapterId, input.chapterPersonId);
12832
- const previous = state.links.find((link2) => link2.id === id6);
13311
+ const id7 = await networkChapterLinkId(person.id, input.chapterId, input.chapterPersonId);
13312
+ const previous = state.links.find((link2) => link2.id === id7);
12833
13313
  const link = {
12834
- id: id6,
13314
+ id: id7,
12835
13315
  personId: person.id,
12836
13316
  chapterId: input.chapterId,
12837
13317
  chapterPersonId: input.chapterPersonId,
@@ -12842,11 +13322,11 @@ async function linkNetworkChapterPerson(db, input, mutationId, actor, now = Date
12842
13322
  const nextPerson = { ...person, revision: person.revision + 1, updatedAt: now };
12843
13323
  await db.transact([
12844
13324
  { t: "update", ns: "networkPersons", id: person.id, attrs: { ...nextPerson } },
12845
- { t: "update", ns: "networkChapterPersonLinks", id: id6, attrs: { ...link } },
13325
+ { t: "update", ns: "networkChapterPersonLinks", id: id7, attrs: { ...link } },
12846
13326
  networkPersonEventOp("chapter_source_linked", mutationId, actor, now, { chapterId: input.chapterId }, { personId: person.id })
12847
13327
  ], { mutationId, guards: [
12848
13328
  { ns: "networkPersons", id: person.id, exists: true, equals: { revision: person.revision, state: "active" } },
12849
- { ns: "networkChapterPersonLinks", id: id6, exists: Boolean(previous), ...previous ? { equals: { state: "revoked" } } : {} }
13329
+ { ns: "networkChapterPersonLinks", id: id7, exists: Boolean(previous), ...previous ? { equals: { state: "revoked" } } : {} }
12850
13330
  ] });
12851
13331
  return { person: nextPerson, link };
12852
13332
  }
@@ -12883,9 +13363,9 @@ var defaultPolicy = (now, consent) => ({
12883
13363
  var isGuardConflict = (error) => error instanceof Error && error.code === "transact_guard_failed";
12884
13364
  async function linkOps(personId, links, now) {
12885
13365
  return Promise.all(links.map(async (link) => {
12886
- const id6 = await networkChapterLinkId(personId, link.chapterId, link.chapterPersonId);
13366
+ const id7 = await networkChapterLinkId(personId, link.chapterId, link.chapterPersonId);
12887
13367
  const sourceKey = await networkChapterPersonSourceKey(link.chapterId, link.chapterPersonId);
12888
- return { t: "update", ns: "networkChapterPersonLinks", id: id6, attrs: { id: id6, personId, chapterId: link.chapterId, chapterPersonId: link.chapterPersonId, sourceKey, state: "shadow", createdAt: now } };
13368
+ return { t: "update", ns: "networkChapterPersonLinks", id: id7, attrs: { id: id7, personId, chapterId: link.chapterId, chapterPersonId: link.chapterPersonId, sourceKey, state: "shadow", createdAt: now } };
12889
13369
  }));
12890
13370
  }
12891
13371
  async function ambiguousAlias(db, input, aliasId, claims, candidates, mutationId, actor, now) {
@@ -13104,10 +13584,10 @@ async function networkPersonBulkBatchDigest(rows5) {
13104
13584
  }
13105
13585
  async function networkPersonRegistryStateDigest(state) {
13106
13586
  const snapshot = {
13107
- persons: state.persons.map(({ id: id6, revision, state: personState, mergedIntoId }) => ({ id: id6, revision, state: personState, mergedIntoId })).sort((a, b) => a.id.localeCompare(b.id)),
13108
- claims: state.claims.map(({ id: id6, personId }) => ({ id: id6, personId })).sort((a, b) => a.id.localeCompare(b.id)),
13109
- aliases: state.aliases.map(({ id: id6, revision, status: status2, personId }) => ({ id: id6, revision, status: status2, personId })).sort((a, b) => a.id.localeCompare(b.id)),
13110
- links: state.links.map(({ id: id6, personId, state: linkState }) => ({ id: id6, personId, state: linkState })).sort((a, b) => a.id.localeCompare(b.id))
13587
+ persons: state.persons.map(({ id: id7, revision, state: personState, mergedIntoId }) => ({ id: id7, revision, state: personState, mergedIntoId })).sort((a, b) => a.id.localeCompare(b.id)),
13588
+ claims: state.claims.map(({ id: id7, personId }) => ({ id: id7, personId })).sort((a, b) => a.id.localeCompare(b.id)),
13589
+ aliases: state.aliases.map(({ id: id7, revision, status: status2, personId }) => ({ id: id7, revision, status: status2, personId })).sort((a, b) => a.id.localeCompare(b.id)),
13590
+ links: state.links.map(({ id: id7, personId, state: linkState }) => ({ id: id7, personId, state: linkState })).sort((a, b) => a.id.localeCompare(b.id))
13111
13591
  };
13112
13592
  return networkPersonBulkSha256(JSON.stringify(stableValue(snapshot)));
13113
13593
  }
@@ -13332,7 +13812,7 @@ var safeNetworkPersonAlias = async (alias) => ({
13332
13812
  recordedAt: alias.consent.recordedAt,
13333
13813
  hasBasis: Boolean(alias.consent.basis)
13334
13814
  },
13335
- identityClaims: alias.identityClaims.map(({ id: id6, kind, hint, verifiedAt }) => ({ id: id6, kind, hint: hint ?? null, verifiedAt })),
13815
+ identityClaims: alias.identityClaims.map(({ id: id7, kind, hint, verifiedAt }) => ({ id: id7, kind, hint: hint ?? null, verifiedAt })),
13336
13816
  chapterIds: [...new Set(alias.chapterLinks.map((link) => link.chapterId))],
13337
13817
  chapterSources: await Promise.all(alias.chapterLinks.map(async (link) => ({
13338
13818
  chapterId: link.chapterId,
@@ -13361,7 +13841,7 @@ async function projectNetworkPersonRegistryState(state) {
13361
13841
  return {
13362
13842
  ...safeNetworkPerson(person, claims),
13363
13843
  canonicalPersonId: canonicalId,
13364
- identityClaims: claims.map(({ id: id6, kind, hint, verifiedAt }) => ({ id: id6, kind, hint: hint ?? null, verifiedAt })),
13844
+ identityClaims: claims.map(({ id: id7, kind, hint, verifiedAt }) => ({ id: id7, kind, hint: hint ?? null, verifiedAt })),
13365
13845
  chapterLinks: safeLinks,
13366
13846
  chapterProjections: projectNetworkContactPolicy(canonical3.contactPolicy, safeLinks).map(({ contactPolicy, ...projection }) => ({ ...projection, contactPolicy: safeNetworkContactPolicy(contactPolicy) }))
13367
13847
  };
@@ -14154,14 +14634,14 @@ var handleFormation = async (req, url, env, ctx) => {
14154
14634
  }
14155
14635
  }
14156
14636
  const db = ctx.makeDb(env);
14157
- const id6 = submissionId ? await applicationIdForSubmission(`formation:${ctx.chapter.id}:${submissionId}`) : crypto.randomUUID();
14637
+ const id7 = submissionId ? await applicationIdForSubmission(`formation:${ctx.chapter.id}:${submissionId}`) : crypto.randomUUID();
14158
14638
  try {
14159
14639
  const created = await createRecord3(
14160
14640
  {
14161
14641
  crm: ctx.chapter.crm,
14162
14642
  db,
14163
14643
  now: () => Date.now(),
14164
- newId: () => id6
14644
+ newId: () => id7
14165
14645
  },
14166
14646
  {
14167
14647
  type: formation.type,
@@ -14170,7 +14650,7 @@ var handleFormation = async (req, url, env, ctx) => {
14170
14650
  }
14171
14651
  );
14172
14652
  return json({
14173
- id: id6,
14653
+ id: id7,
14174
14654
  duplicate: created.duplicate,
14175
14655
  status: created.record.stage ?? null
14176
14656
  }, created.duplicate ? 200 : 201);
@@ -14247,8 +14727,8 @@ function resourceValue(value, path, errors) {
14247
14727
  return null;
14248
14728
  }
14249
14729
  unknownKeys3(raw2, RESOURCE_KEYS, path, errors);
14250
- const id6 = text7(raw2.id, `${path}.id`, errors, 80) ?? "";
14251
- if (id6 && !ID5.test(id6)) errors.push(`${path}.id must be lowercase kebab-case`);
14730
+ const id7 = text7(raw2.id, `${path}.id`, errors, 80) ?? "";
14731
+ if (id7 && !ID5.test(id7)) errors.push(`${path}.id must be lowercase kebab-case`);
14252
14732
  const kind = text7(raw2.kind, `${path}.kind`, errors) ?? "";
14253
14733
  if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
14254
14734
  const title = text7(raw2.title, `${path}.title`, errors, 240) ?? "";
@@ -14305,7 +14785,7 @@ function resourceValue(value, path, errors) {
14305
14785
  updatedAt: integer4(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
14306
14786
  ...promotedFrom ? { promotedFrom } : {}
14307
14787
  };
14308
- return { id: id6, kind, title, ...summary ? { summary } : {}, tags, scope: scope2, status: status2, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance: provenance2 };
14788
+ return { id: id7, kind, title, ...summary ? { summary } : {}, tags, scope: scope2, status: status2, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance: provenance2 };
14309
14789
  }
14310
14790
  function validateBuilderLibraryDocument(value, options = {}) {
14311
14791
  const errors = [];
@@ -14336,7 +14816,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
14336
14816
  const inputs = Array.isArray(raw2.resources) ? raw2.resources : [];
14337
14817
  if (inputs.length > MAX_RESOURCES) errors.push(`document.resources may contain at most ${MAX_RESOURCES} items`);
14338
14818
  const resources = inputs.map((item, index2) => resourceValue(item, `document.resources[${index2}]`, errors)).filter(Boolean);
14339
- const ids2 = resources.map(({ id: id6 }) => id6);
14819
+ const ids2 = resources.map(({ id: id7 }) => id7);
14340
14820
  if (new Set(ids2).size !== ids2.length) errors.push("document.resources ids must be unique");
14341
14821
  for (const resource of resources) {
14342
14822
  if (sourceType === "chapter" && (resource.scope.kind !== "chapter" || resource.scope.chapterId !== sourceAppId)) {
@@ -14425,18 +14905,18 @@ async function loadLatestBuilderLibraryControl(db, sourceId, targetId, environme
14425
14905
  return control2(Array.isArray(rows5) ? [...rows5].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
14426
14906
  }
14427
14907
  async function loadLocalBuilderLibrary(db, appId, environment) {
14428
- const id6 = `builder-library-local:${environment}`;
14429
- const rows5 = (await db.query({ builderLibraryLocal: { $: { where: { id: id6 }, limit: 1 } } })).builderLibraryLocal;
14908
+ const id7 = `builder-library-local:${environment}`;
14909
+ const rows5 = (await db.query({ builderLibraryLocal: { $: { where: { id: id7 }, limit: 1 } } })).builderLibraryLocal;
14430
14910
  return local3(Array.isArray(rows5) ? rows5[0] : void 0);
14431
14911
  }
14432
14912
  async function materializeBuilderLibraryControl(db, envelope, previous, receivedAt) {
14433
14913
  const { document, digest } = envelope;
14434
14914
  const target = document.target;
14435
- const id6 = `builder-library-control:${document.source.appId}:${target.environment}:${document.revision}`;
14915
+ const id7 = `builder-library-control:${document.source.appId}:${target.environment}:${document.revision}`;
14436
14916
  const ops = [];
14437
14917
  if (previous?.active) ops.push({ t: "update", ns: "builderLibraryControls", id: previous.id, attrs: { active: false } });
14438
- ops.push({ t: "update", ns: "builderLibraryControls", id: id6, attrs: {
14439
- id: id6,
14918
+ ops.push({ t: "update", ns: "builderLibraryControls", id: id7, attrs: {
14919
+ id: id7,
14440
14920
  sourceId: document.source.appId,
14441
14921
  targetId: target.appId,
14442
14922
  environment: target.environment,
@@ -14449,13 +14929,13 @@ async function materializeBuilderLibraryControl(db, envelope, previous, received
14449
14929
  await db.transact(ops, { mutationId: `builder-library-control:${document.source.appId}:${target.environment}:${document.revision}:${digest}` });
14450
14930
  }
14451
14931
  async function saveLocalBuilderLibrary(db, document, environment, expectedRevision, updatedAt, updatedBy) {
14452
- const id6 = `builder-library-local:${environment}`;
14932
+ const id7 = `builder-library-local:${environment}`;
14453
14933
  const current = await loadLocalBuilderLibrary(db, document.source.appId, environment);
14454
14934
  if ((current?.revision ?? 0) !== expectedRevision) throw new Error("builder_library_revision_conflict");
14455
- const row2 = { id: id6, appId: document.source.appId, environment, revision: document.revision, document, updatedAt, ...updatedBy ? { updatedBy } : {} };
14456
- await db.transact([{ t: "update", ns: "builderLibraryLocal", id: id6, attrs: row2 }], {
14935
+ const row2 = { id: id7, appId: document.source.appId, environment, revision: document.revision, document, updatedAt, ...updatedBy ? { updatedBy } : {} };
14936
+ await db.transact([{ t: "update", ns: "builderLibraryLocal", id: id7, attrs: row2 }], {
14457
14937
  mutationId: `builder-library-local:${environment}:${document.revision}`,
14458
- guards: current ? [{ ns: "builderLibraryLocal", id: id6, exists: true, equals: { revision: expectedRevision } }] : [{ ns: "builderLibraryLocal", id: id6, exists: false }]
14938
+ guards: current ? [{ ns: "builderLibraryLocal", id: id7, exists: true, equals: { revision: expectedRevision } }] : [{ ns: "builderLibraryLocal", id: id7, exists: false }]
14459
14939
  });
14460
14940
  return row2;
14461
14941
  }
@@ -14670,30 +15150,30 @@ var handleAdminBuilderLibrary = async (req, url, env, ctx) => {
14670
15150
  };
14671
15151
 
14672
15152
  // src/membership-cancel-authority.ts
14673
- import { stripeRequest as stripeRequest10 } from "@odla-ai/stripe";
15153
+ import { stripeRequest as stripeRequest11 } from "@odla-ai/stripe";
14674
15154
  async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
14675
15155
  membershipClock(now);
14676
- const id6 = await networkMembershipId(target);
15156
+ const id7 = await networkMembershipId(target);
14677
15157
  const state = await loadMembershipAuthorityState(db, target);
14678
- const member = await membershipLoad(db, "networkMembership", id6);
15158
+ const member = await membershipLoad(db, "networkMembership", id7);
14679
15159
  requireMembershipScope(member, target);
14680
15160
  if (!["active", "cancel_at_term_end", "payment_grace", "paid_pending_approval", "lapsed"].includes(String(member.status))) {
14681
15161
  membershipFail("membership_cancellation_unavailable");
14682
15162
  }
14683
15163
  const path = `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`;
14684
- const read2 = await stripeRequest10({ secretKey: stripeSecret }, "GET", path);
15164
+ const read2 = await stripeRequest11({ secretKey: stripeSecret }, "GET", path);
14685
15165
  const sub = read2.body;
14686
15166
  if (!read2.ok || sub.id !== member.providerSubscriptionId || sub.customer !== member.providerCustomerId || sub.livemode !== (target.environment === "prod") || !Object.entries({
14687
15167
  ...stripePartitionMetadata(target),
14688
15168
  applicationId: target.applicationId
14689
15169
  }).every(([key3, value]) => sub.metadata?.[key3] === value)) membershipFail("membership_cancellation_provider_mismatch");
14690
15170
  if (sub.status !== "canceled" && sub.cancel_at_period_end !== true) {
14691
- const write = await stripeRequest10(
15171
+ const write = await stripeRequest11(
14692
15172
  { secretKey: stripeSecret },
14693
15173
  "POST",
14694
15174
  path,
14695
15175
  { cancel_at_period_end: true, proration_behavior: "none" },
14696
- { idempotencyKey: `membership-cancel-at-term:${id6}:${member.providerSubscriptionId}` }
15176
+ { idempotencyKey: `membership-cancel-at-term:${id7}:${member.providerSubscriptionId}` }
14697
15177
  );
14698
15178
  if (!write.ok || write.body.id !== member.providerSubscriptionId || write.body.cancel_at_period_end !== true) {
14699
15179
  membershipFail("membership_cancellation_pending");
@@ -14701,7 +15181,7 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
14701
15181
  }
14702
15182
  const status2 = Number(member.termEndsAt) <= now ? "lapsed" : member.journeyApprovedAt ? "cancel_at_term_end" : "paid_pending_approval";
14703
15183
  if (member.status !== status2 || member.autoRenew !== false || Number(member.graceEndsAt ?? 0) !== 0) {
14704
- await db.transact([membershipUpdate("networkMembership", id6, {
15184
+ await db.transact([membershipUpdate("networkMembership", id7, {
14705
15185
  status: status2,
14706
15186
  autoRenew: false,
14707
15187
  graceEndsAt: 0,
@@ -14710,12 +15190,12 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
14710
15190
  updatedAt: now
14711
15191
  })], {
14712
15192
  guards: [
14713
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
15193
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
14714
15194
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
14715
15195
  ]
14716
15196
  });
14717
15197
  }
14718
- return { membershipId: id6, status: status2, termEndsAt: Number(member.termEndsAt), graceEndsAt: 0, autoRenew: false };
15198
+ return { membershipId: id7, status: status2, termEndsAt: Number(member.termEndsAt), graceEndsAt: 0, autoRenew: false };
14719
15199
  }
14720
15200
 
14721
15201
  // src/membership-authority-handler.ts
@@ -14754,17 +15234,17 @@ async function namedSeatPrimaryActivation(db, member, now) {
14754
15234
  // src/membership-approval-authority.ts
14755
15235
  async function approveNetworkMembership(db, input, now) {
14756
15236
  membershipClock(now);
14757
- const id6 = await networkMembershipId(input), entitlementId = `${id6}:primary`;
15237
+ const id7 = await networkMembershipId(input), entitlementId = `${id7}:primary`;
14758
15238
  for (let attempt = 0; attempt < 8; attempt++) {
14759
15239
  const state = await loadMembershipAuthorityState(db, input);
14760
- const member = await membershipLoad(db, "networkMembership", id6);
15240
+ const member = await membershipLoad(db, "networkMembership", id7);
14761
15241
  requireMembershipScope(member, input);
14762
15242
  const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
14763
15243
  requireMembershipScope(entitlement, input);
14764
- if (entitlement.membershipId !== id6 || entitlement.kind !== "primary") membershipFail("membership_entitlement_conflict");
15244
+ if (entitlement.membershipId !== id7 || entitlement.kind !== "primary") membershipFail("membership_entitlement_conflict");
14765
15245
  if (["active", "cancel_at_term_end"].includes(String(member.status)) && entitlement.status === "active" && Number(member.refundedCents) === 0 && Number.isSafeInteger(member.memberNumber) && Number(member.memberNumber) > 0 && Number(member.termEndsAt) > now) {
14766
15246
  return {
14767
- membershipId: id6,
15247
+ membershipId: id7,
14768
15248
  status: String(member.status),
14769
15249
  cohort: String(member.cohort),
14770
15250
  memberNumber: Number(member.memberNumber),
@@ -14784,7 +15264,7 @@ async function approveNetworkMembership(db, input, now) {
14784
15264
  const seatActivation = await namedSeatPrimaryActivation(db, member, now);
14785
15265
  try {
14786
15266
  await db.transact([
14787
- membershipUpdate("networkMembership", id6, {
15267
+ membershipUpdate("networkMembership", id7, {
14788
15268
  status: member.autoRenew ? "active" : "cancel_at_term_end",
14789
15269
  memberNumber,
14790
15270
  journeyApprovedAt: now,
@@ -14799,8 +15279,8 @@ async function approveNetworkMembership(db, input, now) {
14799
15279
  updatedAt: now
14800
15280
  }),
14801
15281
  ...seatActivation.ops
14802
- ], { mutationId: `membership-approve:${id6}`, guards: [
14803
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision, status: "paid_pending_approval" }, absent: ["memberNumber"] },
15282
+ ], { mutationId: `membership-approve:${id7}`, guards: [
15283
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision, status: "paid_pending_approval" }, absent: ["memberNumber"] },
14804
15284
  { ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision, status: "pending_approval" } },
14805
15285
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
14806
15286
  ...seatActivation.guards
@@ -14815,41 +15295,41 @@ async function approveNetworkMembership(db, input, now) {
14815
15295
  // src/membership-refund-authority.ts
14816
15296
  async function refundNetworkMembership(db, input, secretKey, now) {
14817
15297
  membershipClock(now);
14818
- const id6 = await networkMembershipId(input), entitlementId = `${id6}:primary`;
14819
- let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id6);
15298
+ const id7 = await networkMembershipId(input), entitlementId = `${id7}:primary`;
15299
+ let state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id7);
14820
15300
  requireMembershipScope(member, input);
14821
- if (member.status === "refunded") return { membershipId: id6, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
15301
+ if (member.status === "refunded") return { membershipId: id7, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
14822
15302
  if (!["paid_pending_approval", "active", "cancel_at_term_end", "payment_grace", "lapsed", "refund_pending"].includes(String(member.status))) membershipFail("membership_refund_not_eligible");
14823
15303
  if (member.status !== "refund_pending") {
14824
15304
  const entitlement = await membershipLoad(db, "networkEntitlement", entitlementId);
14825
15305
  requireMembershipScope(entitlement, input);
14826
15306
  await db.transact([
14827
- membershipUpdate("networkMembership", id6, {
15307
+ membershipUpdate("networkMembership", id7, {
14828
15308
  status: "refund_pending",
14829
15309
  refundRequestedAt: now,
14830
15310
  revision: Number(member.revision) + 1,
14831
15311
  updatedAt: now
14832
15312
  }),
14833
15313
  membershipUpdate("networkEntitlement", entitlementId, { status: "closed", revision: Number(entitlement.revision) + 1, updatedAt: now }),
14834
- membershipUpdate("membershipEffect", `${id6}:refund`, {
14835
- id: `${id6}:refund`,
15314
+ membershipUpdate("membershipEffect", `${id7}:refund`, {
15315
+ id: `${id7}:refund`,
14836
15316
  chapterId: input.chapterId,
14837
15317
  environment: input.environment,
14838
15318
  runtime: input.runtime,
14839
- membershipId: id6,
15319
+ membershipId: id7,
14840
15320
  kind: "refund_membership",
14841
15321
  status: "pending",
14842
15322
  attempts: 0,
14843
15323
  createdAt: now,
14844
15324
  updatedAt: now
14845
15325
  })
14846
- ], { mutationId: `membership-refund-request:${id6}`, guards: [
14847
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
15326
+ ], { mutationId: `membership-refund-request:${id7}`, guards: [
15327
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
14848
15328
  { ns: "networkEntitlement", id: entitlementId, exists: true, equals: { revision: entitlement.revision } },
14849
15329
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } }
14850
15330
  ] });
14851
15331
  }
14852
- member = await membershipLoad(db, "networkMembership", id6);
15332
+ member = await membershipLoad(db, "networkMembership", id7);
14853
15333
  requireMembershipScope(member, input);
14854
15334
  if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
14855
15335
  const result = await refundSubscriptionPayment(secretKey, {
@@ -14863,23 +15343,23 @@ async function refundNetworkMembership(db, input, secretKey, now) {
14863
15343
  if (!result.subscriptionCanceled || result.refundedCents !== member.amountCents) membershipFail("membership_refund_unconfirmed");
14864
15344
  for (let attempt = 0; attempt < 8; attempt++) {
14865
15345
  state = await loadMembershipAuthorityState(db, input);
14866
- member = await membershipLoad(db, "networkMembership", id6);
15346
+ member = await membershipLoad(db, "networkMembership", id7);
14867
15347
  requireMembershipScope(member, input);
14868
- if (member.status === "refunded") return { membershipId: id6, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
15348
+ if (member.status === "refunded") return { membershipId: id7, status: "refunded", refundedCents: Number(member.refundedCents), subscriptionCanceled: true };
14869
15349
  if (member.status !== "refund_pending") membershipFail("membership_refund_retry_required");
14870
15350
  const counted = member.founding === true && member.memberNumber !== void 0;
14871
15351
  const counts = state.cohortCounts;
14872
15352
  if (!counts || counted && (!Number.isSafeInteger(counts[String(member.cohort)]) || counts[String(member.cohort)] < 1)) membershipFail("invalid_cohort_count");
14873
15353
  try {
14874
15354
  await db.transact([
14875
- membershipUpdate("networkMembership", id6, {
15355
+ membershipUpdate("networkMembership", id7, {
14876
15356
  status: "refunded",
14877
15357
  refundedCents: result.refundedCents,
14878
15358
  autoRenew: false,
14879
15359
  revision: Number(member.revision) + 1,
14880
15360
  updatedAt: now
14881
15361
  }),
14882
- membershipUpdate("membershipEffect", `${id6}:refund`, { status: "complete", updatedAt: now }),
15362
+ membershipUpdate("membershipEffect", `${id7}:refund`, { status: "complete", updatedAt: now }),
14883
15363
  ...counted ? [membershipUpdate("membershipAuthorityState", String(state.id), {
14884
15364
  cohortCounts: { ...counts, [String(member.cohort)]: counts[String(member.cohort)] - 1 },
14885
15365
  revision: Number(state.revision) + 1,
@@ -14890,7 +15370,7 @@ async function refundNetworkMembership(db, input, secretKey, now) {
14890
15370
  chapterId: input.chapterId,
14891
15371
  environment: input.environment,
14892
15372
  runtime: input.runtime,
14893
- membershipId: id6,
15373
+ membershipId: id7,
14894
15374
  seatId: member.namedSeatId,
14895
15375
  kind: "refund_seat_with_primary",
14896
15376
  status: "pending",
@@ -14898,8 +15378,8 @@ async function refundNetworkMembership(db, input, secretKey, now) {
14898
15378
  createdAt: now,
14899
15379
  updatedAt: now
14900
15380
  })] : []
14901
- ], { mutationId: `membership-refunded:${id6}`, guards: [
14902
- { ns: "networkMembership", id: id6, exists: true, equals: { status: "refund_pending", revision: member.revision } },
15381
+ ], { mutationId: `membership-refunded:${id7}`, guards: [
15382
+ { ns: "networkMembership", id: id7, exists: true, equals: { status: "refund_pending", revision: member.revision } },
14903
15383
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
14904
15384
  ] });
14905
15385
  } catch (error) {
@@ -14910,12 +15390,12 @@ async function refundNetworkMembership(db, input, secretKey, now) {
14910
15390
  }
14911
15391
 
14912
15392
  // src/membership-provider-lifecycle.ts
14913
- import { stripeRequest as stripeRequest11 } from "@odla-ai/stripe";
15393
+ import { stripeRequest as stripeRequest12 } from "@odla-ai/stripe";
14914
15394
 
14915
15395
  // src/membership-renewal-authority.ts
14916
15396
  async function reconcileMembershipRenewal(db, input, now) {
14917
15397
  membershipClock(now);
14918
- const id6 = await networkMembershipId(input), eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
15398
+ const id7 = await networkMembershipId(input), eventRow = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
14919
15399
  const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, eventRow?.digest, "membership_renewal_evidence_pending");
14920
15400
  const fact = eventRow?.fact;
14921
15401
  if (!fact || !/^evt_[A-Za-z0-9_]+$/.test(input.eventId) || !/^[a-f0-9]{64}$/.test(String(eventRow.digest)) || fact.digest !== eventRow.digest || fact.eventId !== input.eventId || fact.objectKind !== "invoice" || !["invoice.payment_succeeded", "invoice.payment_failed"].includes(fact.eventType) || fact.billingReason !== "subscription_cycle" || fact.livemode !== (input.environment === "prod") || fact.applicationId !== input.applicationId || Object.entries({
@@ -14928,10 +15408,10 @@ async function reconcileMembershipRenewal(db, input, now) {
14928
15408
  return membershipFail("membership_renewal_evidence_pending");
14929
15409
  }
14930
15410
  for (let attempt = 0; attempt < 8; attempt++) {
14931
- const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id6);
15411
+ const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id7);
14932
15412
  requireMembershipScope(member, input);
14933
15413
  const receipt = () => ({
14934
- membershipId: id6,
15414
+ membershipId: id7,
14935
15415
  status: String(member.status),
14936
15416
  termEndsAt: Number(member.termEndsAt),
14937
15417
  graceEndsAt: Number(member.graceEndsAt ?? 0),
@@ -14956,13 +15436,13 @@ async function reconcileMembershipRenewal(db, input, now) {
14956
15436
  const paid = fact.eventType === "invoice.payment_succeeded";
14957
15437
  if (!paid && (fact.status !== "open" || !Number.isSafeInteger(fact.amountPaidCents) || Number(fact.amountPaidCents) < 0 || Number(fact.amountPaidCents) >= amount)) membershipFail("membership_renewal_failure_mismatch");
14958
15438
  if (paid && (fact.status !== "paid" || fact.amountPaidCents !== amount || !Number.isSafeInteger(fact.invoicePaidAt) || Number(fact.invoicePaidAt) < start || Number(fact.invoicePaidAt) > now + 3e4)) membershipFail("membership_renewal_unpaid");
14959
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id6}:primary`);
15439
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id7}:primary`);
14960
15440
  requireMembershipScope(entitlement, input);
14961
15441
  const graceEndsAt = paid ? 0 : start + 14 * 864e5;
14962
15442
  const status2 = paid ? member.journeyApprovedAt ? member.autoRenew ? "active" : "cancel_at_term_end" : "paid_pending_approval" : now < graceEndsAt ? "payment_grace" : "lapsed";
14963
15443
  const entitlementStatus = member.journeyApprovedAt && (paid || now < graceEndsAt) ? "active" : member.journeyApprovedAt ? "closed" : "pending_approval";
14964
15444
  const ops = [
14965
- membershipUpdate("networkMembership", id6, {
15445
+ membershipUpdate("networkMembership", id7, {
14966
15446
  status: status2,
14967
15447
  graceEndsAt,
14968
15448
  ...paid ? {
@@ -14973,7 +15453,7 @@ async function reconcileMembershipRenewal(db, input, now) {
14973
15453
  revision: Number(member.revision) + 1,
14974
15454
  updatedAt: now
14975
15455
  }),
14976
- membershipUpdate("networkEntitlement", `${id6}:primary`, {
15456
+ membershipUpdate("networkEntitlement", `${id7}:primary`, {
14977
15457
  status: entitlementStatus,
14978
15458
  ...paid ? { termStartedAt: start, termEndsAt: end } : {},
14979
15459
  sourceEventId: input.eventId,
@@ -14982,8 +15462,8 @@ async function reconcileMembershipRenewal(db, input, now) {
14982
15462
  })
14983
15463
  ];
14984
15464
  const guards = [
14985
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
14986
- { ns: "networkEntitlement", id: `${id6}:primary`, exists: true, equals: { revision: entitlement.revision } },
15465
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
15466
+ { ns: "networkEntitlement", id: `${id7}:primary`, exists: true, equals: { revision: entitlement.revision } },
14987
15467
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { revision: state.revision, mode: "authoritative" } },
14988
15468
  { ns: "networkCommercialEvents", id: String(eventRow.id), exists: true, equals: { digest: eventRow.digest } },
14989
15469
  evidenceGuard
@@ -15008,15 +15488,15 @@ async function reconcileMembershipRenewal(db, input, now) {
15008
15488
  );
15009
15489
  }
15010
15490
  try {
15011
- await db.transact(ops, { mutationId: `membership-renewal:${id6}:${input.eventId}`, guards });
15491
+ await db.transact(ops, { mutationId: `membership-renewal:${id7}:${input.eventId}`, guards });
15012
15492
  } catch (error) {
15013
15493
  if (error.code !== "transact_guard_failed") throw error;
15014
15494
  continue;
15015
15495
  }
15016
- const saved = await membershipLoad(db, "networkMembership", id6);
15496
+ const saved = await membershipLoad(db, "networkMembership", id7);
15017
15497
  requireMembershipScope(saved, input);
15018
15498
  return {
15019
- membershipId: id6,
15499
+ membershipId: id7,
15020
15500
  status: String(saved.status),
15021
15501
  termEndsAt: Number(saved.termEndsAt),
15022
15502
  graceEndsAt: Number(saved.graceEndsAt ?? 0),
@@ -15042,12 +15522,12 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15042
15522
  }).some(([key3, value]) => fact.identity[key3] !== value)) membershipFail("membership_billing_evidence_pending");
15043
15523
  const sk = await getVaultSecret(db, stripeSecretName);
15044
15524
  if (!sk) membershipFail("membership_billing_provider_unavailable");
15045
- const id6 = await networkMembershipId(input);
15525
+ const id7 = await networkMembershipId(input);
15046
15526
  for (let attempt = 0; attempt < 8; attempt++) {
15047
- const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id6);
15527
+ const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id7);
15048
15528
  requireMembershipScope(member, input);
15049
15529
  const receipt = (value = member) => ({
15050
- membershipId: id6,
15530
+ membershipId: id7,
15051
15531
  status: String(value.status),
15052
15532
  termEndsAt: Number(value.termEndsAt),
15053
15533
  graceEndsAt: Number(value.graceEndsAt ?? 0),
@@ -15068,13 +15548,13 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15068
15548
  });
15069
15549
  await commercialEvidenceGuard(db, input.eventId, row2?.digest, "membership_billing_evidence_pending");
15070
15550
  await refundNetworkMembership(db, input, sk, now);
15071
- const saved2 = await membershipLoad(db, "networkMembership", id6);
15551
+ const saved2 = await membershipLoad(db, "networkMembership", id7);
15072
15552
  requireMembershipScope(saved2, input);
15073
15553
  return receipt(saved2);
15074
15554
  }
15075
15555
  if (fact.subscriptionId !== member.providerSubscriptionId || fact.objectKind !== "subscription") membershipFail("membership_billing_scope_mismatch");
15076
15556
  if (["refund_pending", "refunded", "declined", "canceled", "restarting"].includes(String(member.status))) return receipt();
15077
- const result = await stripeRequest11({ secretKey: sk }, "GET", `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`);
15557
+ const result = await stripeRequest12({ secretKey: sk }, "GET", `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`);
15078
15558
  const sub = result.body;
15079
15559
  if (!result.ok || sub.id !== member.providerSubscriptionId || sub.customer !== member.providerCustomerId || sub.livemode !== (input.environment === "prod") || typeof sub.cancel_at_period_end !== "boolean" || !["active", "trialing", "past_due", "unpaid", "canceled"].includes(sub.status) || !Object.entries({ ...stripePartitionMetadata(input), applicationId: input.applicationId }).every(([key3, value]) => sub.metadata?.[key3] === value)) {
15080
15560
  membershipFail("membership_billing_provider_mismatch");
@@ -15083,18 +15563,18 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15083
15563
  const status2 = autoRenew ? member.status === "cancel_at_term_end" ? "active" : String(member.status) : now >= Number(member.termEndsAt) ? "lapsed" : member.journeyApprovedAt ? "cancel_at_term_end" : "paid_pending_approval";
15084
15564
  const graceEndsAt = autoRenew ? Number(member.graceEndsAt ?? 0) : 0;
15085
15565
  if (member.autoRenew === autoRenew && member.status === status2 && Number(member.graceEndsAt ?? 0) === graceEndsAt) return receipt();
15086
- const entitlement = await membershipLoad(db, "networkEntitlement", `${id6}:primary`);
15566
+ const entitlement = await membershipLoad(db, "networkEntitlement", `${id7}:primary`);
15087
15567
  requireMembershipScope(entitlement, input);
15088
15568
  try {
15089
15569
  await db.transact([
15090
- membershipUpdate("networkMembership", id6, { status: status2, autoRenew, graceEndsAt, revision: Number(member.revision) + 1, updatedAt: now }),
15570
+ membershipUpdate("networkMembership", id7, { status: status2, autoRenew, graceEndsAt, revision: Number(member.revision) + 1, updatedAt: now }),
15091
15571
  membershipUpdate("networkEntitlement", String(entitlement.id), {
15092
15572
  status: status2 === "lapsed" ? "closed" : entitlement.status,
15093
15573
  revision: Number(entitlement.revision) + 1,
15094
15574
  updatedAt: now
15095
15575
  })
15096
15576
  ], { guards: [
15097
- { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
15577
+ { ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
15098
15578
  { ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision } },
15099
15579
  { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
15100
15580
  { ns: "networkCommercialEvents", id: String(row2.id), exists: true, equals: { digest: fact.digest } },
@@ -15104,7 +15584,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15104
15584
  if (error.code !== "transact_guard_failed") throw error;
15105
15585
  continue;
15106
15586
  }
15107
- const saved = await membershipLoad(db, "networkMembership", id6);
15587
+ const saved = await membershipLoad(db, "networkMembership", id7);
15108
15588
  requireMembershipScope(saved, input);
15109
15589
  return receipt(saved);
15110
15590
  }
@@ -15112,7 +15592,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
15112
15592
  }
15113
15593
 
15114
15594
  // src/membership-seat-stripe.ts
15115
- import { stripeRequest as stripeRequest12 } from "@odla-ai/stripe";
15595
+ import { stripeRequest as stripeRequest13 } from "@odla-ai/stripe";
15116
15596
  var reject7 = () => {
15117
15597
  throw Object.assign(new Error("named seat provider contract mismatch"), { code: "named_seat_provider_mismatch" });
15118
15598
  };
@@ -15124,7 +15604,7 @@ var metadataFor = (seat) => ({
15124
15604
  });
15125
15605
  var matches = (value, expected) => value && Object.entries(expected).every(([key3, item]) => value[key3] === item);
15126
15606
  async function read(sk, path, params) {
15127
- const response = await stripeRequest12({ secretKey: sk }, "GET", path, params);
15607
+ const response = await stripeRequest13({ secretKey: sk }, "GET", path, params);
15128
15608
  if (!response.ok) return reject7();
15129
15609
  return response.body;
15130
15610
  }
@@ -15135,7 +15615,7 @@ async function subscriptionForSeat(sk, seat) {
15135
15615
  currency: seat.currency,
15136
15616
  interval: seat.interval,
15137
15617
  environment: seat.environment
15138
- }, Boolean(seat.providerPaymentIntentId));
15618
+ }, Boolean(seat.providerPaymentIntentId || seat.combinedInvoice));
15139
15619
  if (!productId) reject7();
15140
15620
  const sub = await read(sk, `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}?expand[]=discounts`);
15141
15621
  const items = sub.items?.data;
@@ -15161,11 +15641,11 @@ async function createNamedSeatStripePayment(sk, seat, now) {
15161
15641
  }
15162
15642
  await subscriptionForSeat(sk, seat);
15163
15643
  const metadata = metadataFor(seat);
15164
- const result = seat.providerPaymentIntentId ? await stripeRequest12(
15644
+ const result = seat.providerPaymentIntentId ? await stripeRequest13(
15165
15645
  { secretKey: sk },
15166
15646
  "GET",
15167
15647
  `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`
15168
- ) : await stripeRequest12({ secretKey: sk }, "POST", "/v1/payment_intents", {
15648
+ ) : await stripeRequest13({ secretKey: sk }, "POST", "/v1/payment_intents", {
15169
15649
  amount: seat.amountCents,
15170
15650
  currency: seat.currency,
15171
15651
  customer: seat.providerCustomerId,
@@ -15179,11 +15659,15 @@ async function createNamedSeatStripePayment(sk, seat, now) {
15179
15659
  return { paymentIntentId: pi.id, clientSecret: pi.client_secret, status: pi.status };
15180
15660
  }
15181
15661
  async function attachNamedSeatStripeRenewal(sk, seat) {
15182
- if (!seat.providerPaymentIntentId || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject7();
15662
+ if (!seat.providerPaymentIntentId && !seat.combinedInvoice || !["renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) reject7();
15183
15663
  const sub = await subscriptionForSeat(sk, seat);
15184
15664
  const existing = sub.items.data.find((item2) => item2.metadata?.seatId === seat.id);
15185
- if (existing) return String(existing.id);
15186
- const result = await stripeRequest12({ secretKey: sk }, "POST", "/v1/subscription_items", {
15665
+ if (existing) {
15666
+ if (!/^si_[A-Za-z0-9_]+$/.test(existing.id)) reject7();
15667
+ return String(existing.id);
15668
+ }
15669
+ if (seat.combinedInvoice) reject7();
15670
+ const result = await stripeRequest13({ secretKey: sk }, "POST", "/v1/subscription_items", {
15187
15671
  subscription: seat.providerSubscriptionId,
15188
15672
  price: seat.priceId,
15189
15673
  quantity: 1,
@@ -15400,7 +15884,7 @@ async function recordRecipient(db, input, now, approve) {
15400
15884
  const state = await loadMembershipAuthorityState(db, input);
15401
15885
  const seat = await membershipLoad(db, "networkSeat", input.seatId);
15402
15886
  requireMembershipScope(seat, input);
15403
- if (!seat.purchaserUserId || seat.purchaserUserId === input.recipientUserId) membershipFail("named_seat_recipient_must_differ_from_purchaser");
15887
+ if (!seat.purchaserUserId && !seat.purchaserEmailNormalized || seat.purchaserUserId === input.recipientUserId || seat.purchaserEmailNormalized === normalizeChapterEmail(input.verifiedRecipientEmail)) membershipFail("named_seat_recipient_must_differ_from_purchaser");
15404
15888
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
15405
15889
  requireMembershipScope(member, input);
15406
15890
  const entitlement = await membershipLoad(db, "networkEntitlement", String(seat.entitlementId));
@@ -15720,9 +16204,14 @@ function createMembershipAuthorityHandler(options) {
15720
16204
  try {
15721
16205
  input = JSON.parse(verified.body);
15722
16206
  const fields = action === "seat-invitation" ? MEMBERSHIP_SEAT_ACTION_FIELDS[action] : action === "offer-preview" ? ["skuId"] : ["applicationId", "skuId", ...action === "offers" ? ["requestId"] : action === "checkouts" ? ["offerId", "offerDigest", "quoteDigest", "priceId"] : action === "payments" ? ["eventId", "providerSubscriptionId", "providerCustomerId"] : action === "renewals" ? ["eventId"] : action === "restarts" ? ["previousSubscriptionId"] : MEMBERSHIP_SEAT_ACTION_FIELDS[action] ?? []];
15723
- if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key3) => !fields.includes(key3)) || !fields.every((key3) => typeof input[key3] === "string" && (["recipientName", "recipientEmail", "purchaserEmail", "verifiedRecipientEmail"].includes(key3) ? input[key3].length > 0 && input[key3].length <= 254 && !/[\u0000-\u001f\u007f]/.test(input[key3]) : SAFE2.test(input[key3])))) {
16207
+ if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key3) => !fields.includes(key3) && !(action === "checkouts" && key3 === "namedSeat")) || !fields.every((key3) => typeof input[key3] === "string" && (["recipientName", "recipientEmail", "purchaserEmail", "verifiedRecipientEmail"].includes(key3) ? input[key3].length > 0 && input[key3].length <= 254 && !/[\u0000-\u001f\u007f]/.test(input[key3]) : SAFE2.test(input[key3])))) {
15724
16208
  throw new TypeError("invalid request");
15725
16209
  }
16210
+ if (action === "checkouts" && input.namedSeat !== void 0) {
16211
+ const seat = input.namedSeat;
16212
+ 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");
16213
+ checkoutNamedSeatSelection(seat, seat.purchaserEmail);
16214
+ }
15726
16215
  } catch {
15727
16216
  return respond({ error: "invalid_offer_request" }, 400);
15728
16217
  }
@@ -15795,8 +16284,9 @@ function createMembershipAuthorityHandler(options) {
15795
16284
  offerId: String(input.offerId),
15796
16285
  offerDigest: String(input.offerDigest),
15797
16286
  quoteDigest: String(input.quoteDigest),
15798
- priceId: String(input.priceId)
15799
- }, options.now?.() ?? Date.now());
16287
+ priceId: String(input.priceId),
16288
+ ...input.namedSeat ? { namedSeat: input.namedSeat } : {}
16289
+ }, options.now?.() ?? Date.now(), peer.seatPriceId);
15800
16290
  return respond({ ok: true, binding });
15801
16291
  }
15802
16292
  const offerId = await membershipOfferIdForRequest({ ...peer, applicationId: String(input.applicationId), requestId: String(input.requestId) });
@@ -15824,13 +16314,13 @@ function createMembershipAuthorityHandler(options) {
15824
16314
 
15825
16315
  // src/schema-membership-authority.ts
15826
16316
  var attr = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
15827
- var id4 = () => attr("string", false, true, true);
16317
+ var id5 = () => attr("string", false, true, true);
15828
16318
  var key = (optional = false) => attr("string", optional, false, true);
15829
16319
  var scope = () => ({ appId: key(), chapterId: key(), environment: key(), runtime: key(), applicationId: key(), skuId: key() });
15830
16320
  var times = () => ({ revision: attr("number"), createdAt: attr("number"), updatedAt: attr("number") });
15831
16321
  var membershipAuthorityEntities = {
15832
16322
  networkMembership: { attrs: {
15833
- id: id4(),
16323
+ id: id5(),
15834
16324
  membershipId: key(),
15835
16325
  ...scope(),
15836
16326
  personId: key(true),
@@ -15870,7 +16360,7 @@ var membershipAuthorityEntities = {
15870
16360
  restartId: key(true)
15871
16361
  } },
15872
16362
  membershipRestart: { attrs: {
15873
- id: id4(),
16363
+ id: id5(),
15874
16364
  ...scope(),
15875
16365
  membershipId: key(),
15876
16366
  restartId: key(),
@@ -15880,7 +16370,7 @@ var membershipAuthorityEntities = {
15880
16370
  createdAt: attr("number")
15881
16371
  } },
15882
16372
  membershipPurchaseHistory: { attrs: {
15883
- id: id4(),
16373
+ id: id5(),
15884
16374
  ...scope(),
15885
16375
  membershipId: key(),
15886
16376
  providerSubscriptionId: key(),
@@ -15889,7 +16379,7 @@ var membershipAuthorityEntities = {
15889
16379
  archivedAt: attr("number")
15890
16380
  } },
15891
16381
  networkEntitlement: { attrs: {
15892
- id: id4(),
16382
+ id: id5(),
15893
16383
  ...scope(),
15894
16384
  membershipId: key(),
15895
16385
  seatId: key(true),
@@ -15906,10 +16396,12 @@ var membershipAuthorityEntities = {
15906
16396
  journeyApprovedAt: attr("number", true)
15907
16397
  } },
15908
16398
  networkSeat: { attrs: {
15909
- id: id4(),
16399
+ id: id5(),
15910
16400
  ...scope(),
15911
16401
  primaryMembershipId: key(),
15912
- purchaserUserId: key(),
16402
+ purchaserUserId: key(true),
16403
+ purchaserEmailNormalized: key(true),
16404
+ combinedInvoice: attr("json", true),
15913
16405
  recipientName: attr("string"),
15914
16406
  recipientEmailNormalized: key(),
15915
16407
  recipientClaimId: key(),
@@ -15941,9 +16433,9 @@ var membershipAuthorityEntities = {
15941
16433
  refundedCents: attr("number", true),
15942
16434
  providerRefundIds: attr("json", true)
15943
16435
  } },
15944
- networkSeatRecipient: { attrs: { id: id4(), appId: key(), chapterId: key(), environment: key(), runtime: key(), seatId: key(), recipientEmailNormalized: key(), createdAt: attr("number") } },
16436
+ networkSeatRecipient: { attrs: { id: id5(), appId: key(), chapterId: key(), environment: key(), runtime: key(), seatId: key(), recipientEmailNormalized: key(), createdAt: attr("number") } },
15945
16437
  membershipEffect: { attrs: {
15946
- id: id4(),
16438
+ id: id5(),
15947
16439
  chapterId: key(),
15948
16440
  environment: key(),
15949
16441
  runtime: key(),
@@ -15962,11 +16454,11 @@ var membershipAuthorityRules = Object.fromEntries(Object.keys(membershipAuthorit
15962
16454
 
15963
16455
  // src/schema-membership-offers.ts
15964
16456
  var attr2 = (type, optional = false, unique = false, indexed = false) => ({ type, optional, unique, indexed });
15965
- var id5 = () => attr2("string", false, true, true);
16457
+ var id6 = () => attr2("string", false, true, true);
15966
16458
  var key2 = () => attr2("string", false, false, true);
15967
16459
  var membershipOfferEntities = {
15968
16460
  membershipAuthorityState: { attrs: {
15969
- id: id5(),
16461
+ id: id6(),
15970
16462
  chapterId: key2(),
15971
16463
  environment: key2(),
15972
16464
  mode: key2(),
@@ -15976,7 +16468,7 @@ var membershipOfferEntities = {
15976
16468
  updatedAt: attr2("number")
15977
16469
  } },
15978
16470
  membershipCheckoutOffer: { attrs: {
15979
- id: id5(),
16471
+ id: id6(),
15980
16472
  applicationId: key2(),
15981
16473
  appId: key2(),
15982
16474
  chapterId: key2(),
@@ -15991,7 +16483,7 @@ var membershipOfferEntities = {
15991
16483
  authorityRevision: attr2("number")
15992
16484
  } },
15993
16485
  membershipOfferCheckout: { attrs: {
15994
- id: id5(),
16486
+ id: id6(),
15995
16487
  appId: key2(),
15996
16488
  chapterId: key2(),
15997
16489
  environment: key2(),
@@ -16002,7 +16494,8 @@ var membershipOfferEntities = {
16002
16494
  offerDigest: attr2("string"),
16003
16495
  quoteDigest: attr2("string"),
16004
16496
  priceId: attr2("string"),
16005
- boundAt: attr2("number")
16497
+ boundAt: attr2("number"),
16498
+ namedSeat: attr2("json", true)
16006
16499
  } }
16007
16500
  };
16008
16501
  var membershipOfferRules = Object.fromEntries(Object.keys(membershipOfferEntities).map((ns) => [ns, { view: "false", create: "false", update: "false", delete: "false" }]));
@@ -16057,7 +16550,7 @@ function createMembershipAuthorityStorage(db, secretNames) {
16057
16550
  }
16058
16551
 
16059
16552
  // src/membership-seat-cancel.ts
16060
- import { initStripe as initStripe3, stripeRequest as stripeRequest13 } from "@odla-ai/stripe";
16553
+ import { initStripe as initStripe3, stripeRequest as stripeRequest14 } from "@odla-ai/stripe";
16061
16554
  async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
16062
16555
  membershipClock(now);
16063
16556
  const state = await loadMembershipAuthorityState(db, target);
@@ -16085,7 +16578,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
16085
16578
  }
16086
16579
  const metadata = { ...stripePartitionMetadata(target), applicationId: target.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
16087
16580
  const request = async (method, path, params) => {
16088
- const result = await stripeRequest13(
16581
+ const result = await stripeRequest14(
16089
16582
  { secretKey },
16090
16583
  method,
16091
16584
  path,
@@ -16111,9 +16604,9 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
16111
16604
  if (payment.status === "succeeded" || payment.amount_received > 0) throw new Error("paid seat requires payment evidence and full refund");
16112
16605
  if (payment.status !== "canceled") {
16113
16606
  if (!["requires_payment_method", "requires_confirmation", "requires_action", "processing"].includes(payment.status)) throw new Error("seat payment outcome unresolved");
16114
- const id6 = payment.id;
16115
- payment = await request("POST", `/v1/payment_intents/${encodeURIComponent(id6)}/cancel`, { cancellation_reason: "abandoned" });
16116
- if (payment.id !== id6) throw new Error("seat cancellation receipt mismatch");
16607
+ const id7 = payment.id;
16608
+ payment = await request("POST", `/v1/payment_intents/${encodeURIComponent(id7)}/cancel`, { cancellation_reason: "abandoned" });
16609
+ if (payment.id !== id7) throw new Error("seat cancellation receipt mismatch");
16117
16610
  }
16118
16611
  if (!valid(payment) || payment.status !== "canceled" || payment.amount_received !== 0) throw new Error("seat cancellation unconfirmed");
16119
16612
  await db.transact([membershipUpdate("networkSeat", target.seatId, { status: "canceled", revision: Number(seat.revision) + 1, updatedAt: now })], {
@@ -16435,7 +16928,7 @@ async function requestMembershipCommercialOffer(options) {
16435
16928
  async function requestMembershipOfferPreview(options) {
16436
16929
  const result = await requestAuthority(options, "offer-preview", { skuId: options.skuId });
16437
16930
  const preview = result.preview;
16438
- 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");
16931
+ 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");
16439
16932
  return preview;
16440
16933
  }
16441
16934
  async function requestMembershipCheckoutBinding(options) {
@@ -16445,7 +16938,8 @@ async function requestMembershipCheckoutBinding(options) {
16445
16938
  offerId: options.offerId,
16446
16939
  offerDigest: options.offerDigest,
16447
16940
  quoteDigest: options.quoteDigest,
16448
- priceId: options.priceId
16941
+ priceId: options.priceId,
16942
+ ...options.namedSeat ? { namedSeat: options.namedSeat } : {}
16449
16943
  });
16450
16944
  return verifyMembershipCheckoutBinding(result.binding, options);
16451
16945
  }