@odla-ai/chapter 0.53.0 → 0.54.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.
@@ -36,6 +36,11 @@ __export(worker_exports, {
36
36
  deliverMembershipChapterEffect: () => deliverMembershipChapterEffect,
37
37
  deliverSignupControl: () => deliverSignupControl,
38
38
  drainMembershipEffects: () => drainMembershipEffects,
39
+ grantIncludedNamedSeat: () => grantIncludedNamedSeat,
40
+ lowerSeatPrice: () => lowerSeatPrice,
41
+ namedSeatIncluded: () => namedSeatIncluded,
42
+ namedSeatRecipientState: () => namedSeatRecipientState,
43
+ readNamedSeatOffer: () => readNamedSeatOffer,
39
44
  refundNamedSeatByOperator: () => refundNamedSeatByOperator,
40
45
  renderActiveSignupControlHtml: () => renderActiveSignupControlHtml,
41
46
  resolveNetworkTargetTransport: () => resolveNetworkTargetTransport,
@@ -1803,8 +1808,8 @@ var object = (value) => {
1803
1808
  var id = (value) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9_.:-]{2,159}$/.test(value) ? value : fail2("id");
1804
1809
  var integer2 = (value, min, max) => typeof value === "number" && Number.isSafeInteger(value) && value >= min && value <= max ? value : fail2("integer");
1805
1810
  var time = (value) => {
1806
- const at = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
1807
- return integer2(at, 0, 864e13);
1811
+ const at2 = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
1812
+ return integer2(at2, 0, 864e13);
1808
1813
  };
1809
1814
  var ids = (value, allowEmpty = false) => {
1810
1815
  if (!Array.isArray(value) || value.length > 100 || !allowEmpty && value.length === 0) return fail2("ids");
@@ -1848,7 +1853,7 @@ function membershipOfferingVersion(value) {
1848
1853
  term: normalizedTerm,
1849
1854
  autoRenew: true,
1850
1855
  paymentGraceDays: integer2(input.paymentGraceDays, 0, 60),
1851
- seatPriceCents: integer2(input.seatPriceCents, 1, Number.MAX_SAFE_INTEGER),
1856
+ seatPriceCents: integer2(input.seatPriceCents, 0, Number.MAX_SAFE_INTEGER),
1852
1857
  ...input.seatPurchaseWindowCalendarMonths === void 0 || input.seatPurchaseWindowCalendarMonths === null ? {} : { seatPurchaseWindowCalendarMonths: integer2(input.seatPurchaseWindowCalendarMonths, 1, 120) },
1853
1858
  requiredDisclosures: ids(input.requiredDisclosures)
1854
1859
  };
@@ -2936,7 +2941,7 @@ async function checkoutVariables(db, group, app) {
2936
2941
  const frozen = await frozenCheckoutVariables(db, app);
2937
2942
  return frozen.tierName ? frozen : { ...frozen, tierName: await catalogTierName(db, group, app) };
2938
2943
  }
2939
- function formatInstant(at, timeZone) {
2944
+ function formatInstant(at2, timeZone) {
2940
2945
  return new Intl.DateTimeFormat("en-US", {
2941
2946
  weekday: "long",
2942
2947
  month: "long",
@@ -2946,7 +2951,7 @@ function formatInstant(at, timeZone) {
2946
2951
  minute: "2-digit",
2947
2952
  timeZoneName: "short",
2948
2953
  timeZone
2949
- }).format(new Date(at));
2954
+ }).format(new Date(at2));
2950
2955
  }
2951
2956
  var adminUrlFor = (origin) => `${origin}/admin/`;
2952
2957
  var membersUrlFor = (origin) => `${origin}/members/`;
@@ -3894,6 +3899,181 @@ async function refundNamedSeatStripePayment(secretKey, seat) {
3894
3899
  return { refundedCents, refundIds };
3895
3900
  }
3896
3901
 
3902
+ // src/membership-seat-reservation.ts
3903
+ async function recipientKey(input, email) {
3904
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([input.appId, input.chapterId, input.environment, input.runtime, email])));
3905
+ return `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
3906
+ }
3907
+ async function reserveNamedMembershipSeat(db, input, now) {
3908
+ membershipClock(now);
3909
+ const name = typeof input.recipientName === "string" ? input.recipientName.trim().replace(/\s+/g, " ") : "";
3910
+ const email = normalizeChapterEmail(typeof input.recipientEmail === "string" ? input.recipientEmail : "");
3911
+ const purchaser = normalizeChapterEmail(typeof input.purchaserEmail === "string" ? input.purchaserEmail : "");
3912
+ if (name.length < 2 || name.length > 160 || /[\u0000-\u001f\u007f]/.test(input.recipientName) || email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) || !purchaser || email === purchaser || !/^price_[A-Za-z0-9_]+$/.test(input.priceId) || !/^user_[A-Za-z0-9_]+$/.test(input.purchaserUserId)) membershipFail("invalid_named_seat_recipient");
3913
+ const state = await loadMembershipAuthorityState(db, input);
3914
+ const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
3915
+ requireMembershipScope(member, input);
3916
+ if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
3917
+ const id6 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
3918
+ let seat = await membershipLoad(db, "networkSeat", id6);
3919
+ if (!seat) {
3920
+ const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
3921
+ const purchaseEndsAt = seatPurchaseDeadline(offering, member);
3922
+ if (!["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || Number(member.refundedCents) !== 0 || now < Number(member.initialTermStartedAt) || now >= Number(member.termEndsAt) || now > purchaseEndsAt || !Number.isSafeInteger(purchaseEndsAt) || member.namedSeatId !== void 0) {
3923
+ membershipFail("named_seat_purchase_unavailable");
3924
+ }
3925
+ const attrs = {
3926
+ id: id6,
3927
+ ...membershipScope(input),
3928
+ primaryMembershipId: memberId,
3929
+ recipientName: name,
3930
+ recipientEmailNormalized: email,
3931
+ purchaserUserId: input.purchaserUserId,
3932
+ recipientClaimId: claimId,
3933
+ priceId: input.priceId,
3934
+ primaryPriceId: member.providerPriceId,
3935
+ amountCents: offering.seatPriceCents,
3936
+ currency: offering.currency,
3937
+ interval: offering.interval,
3938
+ providerCustomerId: member.providerCustomerId,
3939
+ providerSubscriptionId: member.providerSubscriptionId,
3940
+ status: "payment_pending",
3941
+ preparedAt: now,
3942
+ termEndsAt: member.termEndsAt,
3943
+ seatPurchaseEndsAt: purchaseEndsAt,
3944
+ offeringSnapshot: offering,
3945
+ revision: 1,
3946
+ createdAt: now,
3947
+ updatedAt: now
3948
+ };
3949
+ try {
3950
+ await db.transact([
3951
+ membershipUpdate("networkSeat", id6, attrs),
3952
+ membershipUpdate("networkSeatRecipient", claimId, {
3953
+ id: claimId,
3954
+ appId: input.appId,
3955
+ chapterId: input.chapterId,
3956
+ environment: input.environment,
3957
+ runtime: input.runtime,
3958
+ seatId: id6,
3959
+ recipientEmailNormalized: email,
3960
+ createdAt: now
3961
+ }),
3962
+ membershipUpdate("networkMembership", memberId, { namedSeatId: id6, revision: Number(member.revision) + 1, updatedAt: now })
3963
+ ], { mutationId: `named-seat-reserve:${id6}`, guards: [
3964
+ { ns: "networkSeat", id: id6, exists: false },
3965
+ { ns: "networkSeatRecipient", id: claimId, exists: false },
3966
+ { ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
3967
+ { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
3968
+ ] });
3969
+ } catch (error) {
3970
+ if (error.code !== "transact_guard_failed") throw error;
3971
+ }
3972
+ seat = await membershipLoad(db, "networkSeat", id6);
3973
+ if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
3974
+ }
3975
+ requireMembershipScope(seat, input);
3976
+ if (seat.primaryMembershipId !== memberId || seat.recipientName !== name || seat.recipientEmailNormalized !== email || seat.priceId !== input.priceId || seat.purchaserUserId !== input.purchaserUserId) membershipFail("named_seat_purchase_conflict");
3977
+ return seat;
3978
+ }
3979
+
3980
+ // src/membership-seat-included.ts
3981
+ var namedSeatIncluded = (seat) => Number(seat.amountCents) === 0;
3982
+ var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean(seat.providerPaymentIntentId && seat.providerSubscriptionItemId);
3983
+ function namedSeatRecipientState(seat) {
3984
+ switch (String(seat.status)) {
3985
+ case "renewal_pending":
3986
+ case "pending_acceptance":
3987
+ return "invited";
3988
+ case "pending_approval":
3989
+ case "pending_primary":
3990
+ return "accepted";
3991
+ case "active":
3992
+ return "joined";
3993
+ case "refund_pending":
3994
+ case "refunded":
3995
+ return seat.refundReason === "declined" ? "declined" : "refunded";
3996
+ case "canceled":
3997
+ return "canceled";
3998
+ default:
3999
+ return "payment_pending";
4000
+ }
4001
+ }
4002
+ var OPEN = ["pending_acceptance", "pending_approval", "pending_primary", "active"];
4003
+ async function grantIncludedNamedSeat(db, input, now) {
4004
+ membershipClock(now);
4005
+ const seat = await reserveNamedMembershipSeat(db, input, now);
4006
+ if (!namedSeatIncluded(seat)) membershipFail("named_seat_not_included");
4007
+ const state = await loadMembershipAuthorityState(db, input);
4008
+ const member = await membershipLoad(db, "networkMembership", seat.primaryMembershipId);
4009
+ requireMembershipScope(member, input);
4010
+ if (!["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || Number(member.refundedCents) !== 0 || member.namedSeatId !== seat.id || now >= Number(member.termEndsAt)) {
4011
+ membershipFail("named_seat_purchase_unavailable");
4012
+ }
4013
+ if (seat.status === "payment_pending") {
4014
+ const entitlementId = `${seat.id}:entitlement`, inviteId = `${seat.id}:invite`, source = `included:${seat.id}`;
4015
+ try {
4016
+ await db.transact([
4017
+ membershipUpdate("networkSeat", seat.id, {
4018
+ status: "pending_acceptance",
4019
+ purchasedAt: now,
4020
+ termStartedAt: now,
4021
+ termEndsAt: member.termEndsAt,
4022
+ acceptanceEndsAt: Number(member.termEndsAt),
4023
+ entitlementId,
4024
+ revision: Number(seat.revision) + 1,
4025
+ updatedAt: now
4026
+ }),
4027
+ membershipUpdate("networkEntitlement", entitlementId, {
4028
+ id: entitlementId,
4029
+ ...membershipScope(input),
4030
+ membershipId: member.id,
4031
+ seatId: seat.id,
4032
+ kind: "named_seat",
4033
+ status: "pending_acceptance",
4034
+ termStartedAt: now,
4035
+ termEndsAt: member.termEndsAt,
4036
+ offeringSnapshot: member.offeringSnapshot,
4037
+ sourceEventId: source,
4038
+ revision: 1,
4039
+ createdAt: now,
4040
+ updatedAt: now
4041
+ }),
4042
+ membershipUpdate("membershipEffect", inviteId, {
4043
+ id: inviteId,
4044
+ chapterId: input.chapterId,
4045
+ environment: input.environment,
4046
+ runtime: input.runtime,
4047
+ membershipId: member.id,
4048
+ seatId: seat.id,
4049
+ kind: "invite_named_recipient",
4050
+ status: "pending",
4051
+ attempts: 0,
4052
+ createdAt: now,
4053
+ updatedAt: now
4054
+ })
4055
+ ], { mutationId: `named-seat-included:${seat.id}`, guards: [
4056
+ {
4057
+ ns: "networkSeat",
4058
+ id: seat.id,
4059
+ exists: true,
4060
+ equals: { status: "payment_pending", revision: seat.revision, amountCents: 0 },
4061
+ absent: ["purchasedAt", "providerPaymentIntentId"]
4062
+ },
4063
+ { ns: "networkEntitlement", id: entitlementId, exists: false },
4064
+ { ns: "networkMembership", id: String(member.id), exists: true, equals: { revision: member.revision, namedSeatId: seat.id } },
4065
+ { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
4066
+ ] });
4067
+ } catch (error) {
4068
+ if (error.code !== "transact_guard_failed") throw error;
4069
+ }
4070
+ }
4071
+ const current = await membershipLoad(db, "networkSeat", seat.id);
4072
+ requireMembershipScope(current, input);
4073
+ if (!namedSeatIncluded(current) || current.purchasedAt === void 0 || !OPEN.includes(String(current.status))) membershipFail("named_seat_purchase_unavailable");
4074
+ return { seatId: seat.id, amountCents: 0, currency: seat.currency, termEndsAt: Number(current.termEndsAt), status: String(current.status), included: true };
4075
+ }
4076
+
3897
4077
  // src/membership-seat-refund.ts
3898
4078
  async function requestNamedSeatRefund(db, input, now) {
3899
4079
  membershipClock(now);
@@ -3903,7 +4083,7 @@ async function requestNamedSeatRefund(db, input, now) {
3903
4083
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
3904
4084
  requireMembershipScope(member, input);
3905
4085
  if (seat.status === "refunded" || seat.status === "refund_pending") return;
3906
- if (!seat.purchasedAt || !seat.providerPaymentIntentId || input.reason === "declined" && seat.journeyApprovedAt !== void 0 || input.reason === "primary_closed" && !["refunded", "declined"].includes(String(member.status))) membershipFail("named_seat_refund_not_eligible");
4086
+ 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");
3907
4087
  const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
3908
4088
  await db.transact([
3909
4089
  membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
@@ -3948,7 +4128,7 @@ async function completeNamedSeatRefund(db, seatId, secretKey, now) {
3948
4128
  await loadMembershipAuthorityState(db, seat);
3949
4129
  if (seat.status === "refunded") return;
3950
4130
  if (seat.status !== "refund_pending") membershipFail("named_seat_refund_not_ready");
3951
- const result = await refundNamedSeatStripePayment(secretKey, seat);
4131
+ const result = namedSeatIncluded(seat) ? { refundedCents: 0, refundIds: [] } : await refundNamedSeatStripePayment(secretKey, seat);
3952
4132
  await db.transact([
3953
4133
  membershipUpdate("networkSeat", seatId, {
3954
4134
  status: "refunded",
@@ -3970,7 +4150,7 @@ async function refundNamedSeatByOperator(db, input, stripeSecretKey, now) {
3970
4150
  await requestNamedSeatRefund(db, { ...input, reason: "operator" }, now);
3971
4151
  await completeNamedSeatRefund(db, input.seatId, stripeSecretKey, now);
3972
4152
  const refunded = await membershipLoad(db, "networkSeat", input.seatId);
3973
- if (refunded?.status !== "refunded" || !Number.isSafeInteger(refunded.refundedCents) || Number(refunded.refundedCents) <= 0) {
4153
+ if (refunded?.status !== "refunded" || !Number.isSafeInteger(refunded.refundedCents) || Number(refunded.refundedCents) !== Number(seat.amountCents)) {
3974
4154
  throw new Error("named seat refund unconfirmed");
3975
4155
  }
3976
4156
  return { seatId: String(seat.id), status: "refunded", refundedCents: Number(refunded.refundedCents), subscriptionCanceled: false };
@@ -4002,9 +4182,56 @@ async function liftSeatPurchaseWindow(db, scope2, now, limit = 100) {
4002
4182
  return { lifted, more: rows5.length > limit };
4003
4183
  }
4004
4184
 
4185
+ // src/membership-seat-reprice.ts
4186
+ async function lowerSeatPrice(db, scope2, now, limit = 100) {
4187
+ membershipClock(now);
4188
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 200) throw new TypeError("invalid reprice limit");
4189
+ const state = await loadMembershipAuthorityState(db, scope2);
4190
+ const versions = (await db.query({ membershipOfferingVersion: { $: { where: { chapterId: scope2.chapterId }, limit: 200 } } })).membershipOfferingVersion ?? [];
4191
+ if (versions.length >= 200) throw Object.assign(new Error("policy_history_incomplete"), { code: "policy_history_incomplete" });
4192
+ const current = versions.map((row2) => membershipOfferingVersion(row2.snapshot)).filter((offer) => offer.baseSkuId === scope2.skuId && offer.status === "published" && now >= offer.effectiveFrom && (offer.effectiveUntil === void 0 || now < offer.effectiveUntil)).sort((a, b) => b.version - a.version)[0];
4193
+ if (!current) throw Object.assign(new Error("ambiguous_membership_policy"), { code: "ambiguous_membership_policy" });
4194
+ const target = current.seatPriceCents;
4195
+ const rows5 = (await db.query({ networkMembership: { $: { where: { ...scope2 }, order: { updatedAt: "asc" }, limit: limit + 1 } } })).networkMembership ?? [];
4196
+ const receipt = { seatPriceCents: target, repriced: 0, skipped: [], more: rows5.length > limit };
4197
+ for (const member of rows5.slice(0, limit)) {
4198
+ if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) throw new Error("membership reprice scope mismatch");
4199
+ const id6 = String(member.id), skip = (reason) => {
4200
+ receipt.skipped.push({ membershipId: id6, reason });
4201
+ };
4202
+ const offering = membershipOfferingVersion(member.offeringSnapshot);
4203
+ if (offering.seatPriceCents <= target) {
4204
+ skip("seat price already at or below the current offering");
4205
+ continue;
4206
+ }
4207
+ if (!["paid_pending_approval", "active", "cancel_at_term_end", "payment_grace"].includes(String(member.status))) {
4208
+ skip(`membership is ${String(member.status)}`);
4209
+ continue;
4210
+ }
4211
+ const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
4212
+ if (seat && (seat.status !== "payment_pending" || seat.providerPaymentIntentId !== void 0)) {
4213
+ skip(`seat is ${String(seat.status)}`);
4214
+ continue;
4215
+ }
4216
+ const ops = [membershipUpdate("networkMembership", id6, { offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(member.revision) + 1, updatedAt: now })];
4217
+ const guards = [
4218
+ { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
4219
+ { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative" } }
4220
+ ];
4221
+ if (seat) {
4222
+ ops.push(membershipUpdate("networkSeat", String(seat.id), { amountCents: target, offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(seat.revision) + 1, updatedAt: now }));
4223
+ guards.push({ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision, status: "payment_pending" } });
4224
+ }
4225
+ await db.transact(ops, { mutationId: `seat-reprice:${id6}:${member.revision}`, guards });
4226
+ receipt.repriced++;
4227
+ }
4228
+ return receipt;
4229
+ }
4230
+
4005
4231
  // src/worker-routes-admin-membership-seats.ts
4006
4232
  var LIST = "/api/admin/membership-seats";
4007
4233
  var LIFT = "/api/admin/membership-seats/lift-purchase-window";
4234
+ var REPRICE = "/api/admin/membership-seats/reprice";
4008
4235
  var ONE = /^\/api\/admin\/membership-seats\/([A-Za-z0-9][A-Za-z0-9_.:-]{2,159})\/refund$/;
4009
4236
  var SCOPE = /^[a-z0-9][a-z0-9-]{1,62}$/;
4010
4237
  var PUBLIC = [
@@ -4031,11 +4258,11 @@ var PUBLIC = [
4031
4258
  ];
4032
4259
  var project = (row2) => Object.fromEntries(PUBLIC.filter((key3) => row2[key3] !== void 0).map((key3) => [key3, row2[key3]]));
4033
4260
  var handleAdminMembershipSeats = async (req, url, env, ctx) => {
4034
- const one = url.pathname.match(ONE), lift = url.pathname === LIFT;
4035
- if (url.pathname !== LIST && !one && !lift) return null;
4261
+ const one = url.pathname.match(ONE), lift = url.pathname === LIFT, reprice = url.pathname === REPRICE;
4262
+ if (url.pathname !== LIST && !one && !lift && !reprice) return null;
4036
4263
  const operator = ctx.membershipAuthorityOperator;
4037
4264
  if (!operator) return json({ error: "not found" }, 404);
4038
- if (req.method !== (one || lift ? "POST" : "GET")) return json({ error: "method not allowed" }, 405);
4265
+ if (req.method !== (one || lift || reprice ? "POST" : "GET")) return json({ error: "method not allowed" }, 405);
4039
4266
  const raw2 = ctx.makeDb(env), user = await ctx.verifyUser(req, env);
4040
4267
  if (!user) return json({ error: "unauthorized" }, 401);
4041
4268
  if (!await ctx.isSuperAdminEmail(raw2, user.email, user.userId)) return json({ error: "super-admin required", code: "super_admin_required" }, 403);
@@ -4045,7 +4272,7 @@ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
4045
4272
  } catch {
4046
4273
  return json({ error: "the membership authority is not hosted by this deployment" }, 404);
4047
4274
  }
4048
- if (lift) {
4275
+ if (lift || reprice) {
4049
4276
  let body;
4050
4277
  try {
4051
4278
  body = await req.json();
@@ -4053,14 +4280,16 @@ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
4053
4280
  return json({ error: "chapterId and runtime are required" }, 400);
4054
4281
  }
4055
4282
  const appId = body.appId ?? body.chapterId;
4056
- if (typeof body.chapterId !== "string" || !SCOPE.test(body.chapterId) || typeof appId !== "string" || !SCOPE.test(appId) || typeof body.runtime !== "string" || !/^[a-z0-9]{2,12}$/.test(body.runtime)) {
4057
- return json({ error: "chapterId and runtime are required" }, 400);
4283
+ if (typeof body.chapterId !== "string" || !SCOPE.test(body.chapterId) || typeof appId !== "string" || !SCOPE.test(appId) || typeof body.runtime !== "string" || !/^[a-z0-9]{2,12}$/.test(body.runtime) || reprice && (typeof body.skuId !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.:-]{2,159}$/.test(body.skuId))) {
4284
+ return json({ error: reprice ? "chapterId, runtime and skuId are required" : "chapterId and runtime are required" }, 400);
4058
4285
  }
4286
+ const scope2 = { appId, chapterId: body.chapterId, environment: env.ODLA_ENV, runtime: body.runtime };
4059
4287
  try {
4060
- return json({ ok: true, ...await liftSeatPurchaseWindow(db, { appId, chapterId: body.chapterId, environment: env.ODLA_ENV, runtime: body.runtime }, Date.now()) });
4288
+ if (reprice) return json({ ok: true, ...await lowerSeatPrice(db, { ...scope2, skuId: String(body.skuId) }, Date.now()) });
4289
+ return json({ ok: true, ...await liftSeatPurchaseWindow(db, scope2, Date.now()) });
4061
4290
  } catch (error) {
4062
4291
  const code = error.code;
4063
- return json({ error: "purchase window lift is pending; retry to confirm", ...code ? { code } : {} }, code ? 409 : 503);
4292
+ return json({ error: `${reprice ? "seat reprice" : "purchase window lift"} is pending; retry to confirm`, ...code ? { code } : {} }, code ? 409 : 503);
4064
4293
  }
4065
4294
  }
4066
4295
  if (!one) {
@@ -4096,8 +4325,8 @@ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
4096
4325
  var LIMIT = 200;
4097
4326
  var text2 = (value) => typeof value === "string" && value.length > 0 && value.length <= 200 ? value : void 0;
4098
4327
  var instant = (value) => {
4099
- const at = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
4100
- return Number.isSafeInteger(at) && at >= 0 ? at : void 0;
4328
+ const at2 = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
4329
+ return Number.isSafeInteger(at2) && at2 >= 0 ? at2 : void 0;
4101
4330
  };
4102
4331
  function provenance(row2) {
4103
4332
  const source = text2(row2.source), actor = text2(row2.actor ?? row2.$createdBy), recordedAt = instant(row2.recordedAt ?? row2.createdAt ?? row2.$createdAt);
@@ -6470,7 +6699,7 @@ var handleMe = async (req, url, env, ctx) => {
6470
6699
  } catch {
6471
6700
  }
6472
6701
  }
6473
- return json({ ...base, memberAccess, membershipRestart, application: member?.application ?? null, namedSeatsEnabled: Boolean(memberAccess && ctx.membershipAuthority?.namedSeats && member?.source.stripeSubscriptionId && member.source.status !== "submitted") });
6702
+ return json({ ...base, memberAccess, membershipRestart, application: member?.application ?? null, namedSeatsEnabled: Boolean(ctx.membershipAuthority?.namedSeats && member?.application.paid && member.source.status !== "submitted") });
6474
6703
  };
6475
6704
  var handleCrm = async (req, url, env, ctx) => {
6476
6705
  const crmBase = ctx.crmBase;
@@ -9468,6 +9697,7 @@ async function namedSeatOfferDigest(offer) {
9468
9697
  ])));
9469
9698
  return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
9470
9699
  }
9700
+ var at = (value) => Number.isSafeInteger(value) ? Number(value) : void 0;
9471
9701
  async function readNamedSeatOffer(db, input, now) {
9472
9702
  membershipClock(now);
9473
9703
  await loadMembershipAuthorityState(db, input);
@@ -9490,10 +9720,19 @@ async function readNamedSeatOffer(db, input, now) {
9490
9720
  seatId: String(seat.id),
9491
9721
  status: String(seat.status),
9492
9722
  recipientName: String(seat.recipientName),
9493
- recipientEmail: String(seat.recipientEmailNormalized)
9723
+ recipientEmail: String(seat.recipientEmailNormalized),
9724
+ recipientState: namedSeatRecipientState(seat),
9725
+ ...at(seat.purchasedAt) !== void 0 ? { purchasedAt: at(seat.purchasedAt) } : {},
9726
+ ...at(seat.acceptedAt) !== void 0 ? { acceptedAt: at(seat.acceptedAt) } : {},
9727
+ ...seat.status === "active" && at(seat.journeyApprovedAt) !== void 0 ? { joinedAt: at(seat.journeyApprovedAt) } : {}
9494
9728
  } } : {}
9495
9729
  };
9496
- return { ...offer, digest: await namedSeatOfferDigest(offer), purchaseWindowed: offering.seatPurchaseWindowCalendarMonths !== void 0 };
9730
+ return {
9731
+ ...offer,
9732
+ digest: await namedSeatOfferDigest(offer),
9733
+ included: offering.seatPriceCents === 0,
9734
+ purchaseWindowed: offering.seatPurchaseWindowCalendarMonths !== void 0
9735
+ };
9497
9736
  }
9498
9737
 
9499
9738
  // src/named-seat-request.ts
@@ -9569,8 +9808,9 @@ var handleNamedSeat = async (req, url, env, ctx) => {
9569
9808
  if (recipientName.length < 2 || /[\u0000-\u001f\u007f]/.test(body.recipientName) || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(recipientEmail) || recipientEmail === normalizeChapterEmail(user.email)) {
9570
9809
  return json({ error: "a valid name and email for someone other than the purchaser are required" }, 400);
9571
9810
  }
9572
- if (!offer.eligible || body.quoteDigest !== offer.digest) return json({ error: "seat offer changed or is unavailable; refresh before paying" }, 409);
9573
- if (!group?.stripePublishableKey || !group.refundPolicyText || !group.merchantDisclosureText) return json({ error: "seat checkout disclosures unavailable" }, 503);
9811
+ const granted = offer.included && offer.seat !== void 0 && offer.seat.recipientEmail === recipientEmail && offer.seat.recipientName === recipientName && ["pending_acceptance", "pending_approval", "pending_primary", "active"].includes(offer.seat.status);
9812
+ if (!offer.eligible && !granted || body.quoteDigest !== offer.digest) return json({ error: "seat offer changed or is unavailable; refresh before paying" }, 409);
9813
+ if (!group?.refundPolicyText || !group.merchantDisclosureText || !offer.included && !group.stripePublishableKey) return json({ error: "seat checkout disclosures unavailable" }, 503);
9574
9814
  const consentId = `${offer.membershipId}:named-seat-consent`;
9575
9815
  const prior = await membershipLoad(db, "namedSeatConsents", consentId);
9576
9816
  const attrs = {
@@ -9609,7 +9849,7 @@ var handleNamedSeat = async (req, url, env, ctx) => {
9609
9849
  }
9610
9850
  return json({
9611
9851
  ...receipt,
9612
- publishableKey: String(group.stripePublishableKey),
9852
+ ...group.stripePublishableKey ? { publishableKey: String(group.stripePublishableKey) } : {},
9613
9853
  lineItems: { standardCents: offer.amountCents, discountCents: 0, dueTodayCents: offer.amountCents }
9614
9854
  });
9615
9855
  } catch {
@@ -11406,7 +11646,7 @@ async function networkApplicationAction(opts) {
11406
11646
  if (app.status !== input.expectedStage || !applicationActionStages(app, env, ctx).includes(input.stage)) {
11407
11647
  return json({ error: "application action is no longer available; reload", code: "invalid_application_action" }, 409);
11408
11648
  }
11409
- const at = Date.now();
11649
+ const at2 = Date.now();
11410
11650
  const meta = { [ACTION_META]: {
11411
11651
  version: 1,
11412
11652
  state: "pending",
@@ -11426,7 +11666,7 @@ async function networkApplicationAction(opts) {
11426
11666
  recordId: record4.id,
11427
11667
  kind: "note",
11428
11668
  authorId: `network:${sourceId}`,
11429
- createdAt: at,
11669
+ createdAt: at2,
11430
11670
  body: `Requested ${input.expectedStage} \u2192 ${input.stage} from ${sourceId}`,
11431
11671
  meta
11432
11672
  } },
@@ -11435,7 +11675,7 @@ async function networkApplicationAction(opts) {
11435
11675
  recordId: record4.id,
11436
11676
  kind: "note",
11437
11677
  authorId: `network:${sourceId}`,
11438
- createdAt: at,
11678
+ createdAt: at2,
11439
11679
  body: "Application operation reconciliation",
11440
11680
  meta: { state: "pending", activityId }
11441
11681
  } }
@@ -12282,9 +12522,9 @@ function normalizeNetworkIdentity(kind, value) {
12282
12522
  return trimmed;
12283
12523
  }
12284
12524
  function emailLooksReal(value) {
12285
- const at = value.lastIndexOf("@");
12286
- if (at <= 0 || at === value.length - 1) return false;
12287
- const domain = value.slice(at + 1).toLowerCase();
12525
+ const at2 = value.lastIndexOf("@");
12526
+ if (at2 <= 0 || at2 === value.length - 1) return false;
12527
+ const domain = value.slice(at2 + 1).toLowerCase();
12288
12528
  return !domain.endsWith(".invalid") && domain !== "example.com" && domain !== "example.test";
12289
12529
  }
12290
12530
  function validateNetworkPersonImport(input, allowedChapterIds) {
@@ -14603,7 +14843,9 @@ async function reconcileMembershipRenewal(db, input, now) {
14603
14843
  if (start !== member.termEndsAt || start > now + 3e4) membershipFail("membership_renewal_period_gap");
14604
14844
  const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
14605
14845
  if (seat) requireMembershipScope(seat, input);
14606
- const billedSeat = seat && seat.providerSubscriptionId === member.providerSubscriptionId && seat.providerSubscriptionItemId && !["refunded", "refund_pending"].includes(String(seat.status)) ? seat : void 0;
14846
+ const openSeat = seat && !["refunded", "refund_pending", "canceled"].includes(String(seat.status)) ? seat : void 0;
14847
+ const billedSeat = openSeat && openSeat.providerSubscriptionId === member.providerSubscriptionId && openSeat.providerSubscriptionItemId ? openSeat : void 0;
14848
+ const carriedSeat = billedSeat ?? (openSeat && namedSeatIncluded(openSeat) && openSeat.purchasedAt !== void 0 ? openSeat : void 0);
14607
14849
  const prices = [String(member.providerPriceId), ...billedSeat ? [String(billedSeat.priceId)] : []].sort();
14608
14850
  const amount = Number(member.renewalAmountCents) + Number(billedSeat?.amountCents ?? 0);
14609
14851
  if (!Number.isSafeInteger(amount) || amount <= 0 || fact.invoiceTotalCents !== amount || JSON.stringify(lines.map((line) => line.priceId).sort()) !== JSON.stringify(prices)) membershipFail("membership_renewal_amount_mismatch");
@@ -14643,19 +14885,19 @@ async function reconcileMembershipRenewal(db, input, now) {
14643
14885
  evidenceGuard
14644
14886
  ];
14645
14887
  if (seat) guards.push({ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision } });
14646
- if (billedSeat?.entitlementId) {
14647
- const seatEntitlement = await membershipLoad(db, "networkEntitlement", String(billedSeat.entitlementId));
14888
+ if (carriedSeat?.entitlementId) {
14889
+ const seatEntitlement = await membershipLoad(db, "networkEntitlement", String(carriedSeat.entitlementId));
14648
14890
  requireMembershipScope(seatEntitlement, input);
14649
14891
  guards.push({ ns: "networkEntitlement", id: String(seatEntitlement.id), exists: true, equals: { revision: seatEntitlement.revision } });
14650
14892
  ops.push(
14651
- membershipUpdate("networkSeat", String(billedSeat.id), {
14893
+ membershipUpdate("networkSeat", String(carriedSeat.id), {
14652
14894
  ...paid ? { termStartedAt: start, termEndsAt: end } : {},
14653
- revision: Number(billedSeat.revision) + 1,
14895
+ revision: Number(carriedSeat.revision) + 1,
14654
14896
  updatedAt: now
14655
14897
  }),
14656
14898
  membershipUpdate("networkEntitlement", String(seatEntitlement.id), {
14657
14899
  ...paid ? { termStartedAt: start, termEndsAt: end } : {},
14658
- status: billedSeat.journeyApprovedAt ? entitlementStatus : seatEntitlement.status,
14900
+ status: carriedSeat.journeyApprovedAt ? entitlementStatus : seatEntitlement.status,
14659
14901
  revision: Number(seatEntitlement.revision) + 1,
14660
14902
  updatedAt: now
14661
14903
  })
@@ -14765,84 +15007,6 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
14765
15007
  return membershipFail("membership_billing_retry_required");
14766
15008
  }
14767
15009
 
14768
- // src/membership-seat-reservation.ts
14769
- async function recipientKey(input, email) {
14770
- const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([input.appId, input.chapterId, input.environment, input.runtime, email])));
14771
- return `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
14772
- }
14773
- async function reserveNamedMembershipSeat(db, input, now) {
14774
- membershipClock(now);
14775
- const name = typeof input.recipientName === "string" ? input.recipientName.trim().replace(/\s+/g, " ") : "";
14776
- const email = normalizeChapterEmail(typeof input.recipientEmail === "string" ? input.recipientEmail : "");
14777
- const purchaser = normalizeChapterEmail(typeof input.purchaserEmail === "string" ? input.purchaserEmail : "");
14778
- if (name.length < 2 || name.length > 160 || /[\u0000-\u001f\u007f]/.test(input.recipientName) || email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) || !purchaser || email === purchaser || !/^price_[A-Za-z0-9_]+$/.test(input.priceId) || !/^user_[A-Za-z0-9_]+$/.test(input.purchaserUserId)) membershipFail("invalid_named_seat_recipient");
14779
- const state = await loadMembershipAuthorityState(db, input);
14780
- const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
14781
- requireMembershipScope(member, input);
14782
- if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
14783
- const id6 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
14784
- let seat = await membershipLoad(db, "networkSeat", id6);
14785
- if (!seat) {
14786
- const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
14787
- const purchaseEndsAt = seatPurchaseDeadline(offering, member);
14788
- if (!["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || Number(member.refundedCents) !== 0 || now < Number(member.initialTermStartedAt) || now >= Number(member.termEndsAt) || now > purchaseEndsAt || !Number.isSafeInteger(purchaseEndsAt) || member.namedSeatId !== void 0) {
14789
- membershipFail("named_seat_purchase_unavailable");
14790
- }
14791
- const attrs = {
14792
- id: id6,
14793
- ...membershipScope(input),
14794
- primaryMembershipId: memberId,
14795
- recipientName: name,
14796
- recipientEmailNormalized: email,
14797
- purchaserUserId: input.purchaserUserId,
14798
- recipientClaimId: claimId,
14799
- priceId: input.priceId,
14800
- primaryPriceId: member.providerPriceId,
14801
- amountCents: offering.seatPriceCents,
14802
- currency: offering.currency,
14803
- interval: offering.interval,
14804
- providerCustomerId: member.providerCustomerId,
14805
- providerSubscriptionId: member.providerSubscriptionId,
14806
- status: "payment_pending",
14807
- preparedAt: now,
14808
- termEndsAt: member.termEndsAt,
14809
- seatPurchaseEndsAt: purchaseEndsAt,
14810
- offeringSnapshot: offering,
14811
- revision: 1,
14812
- createdAt: now,
14813
- updatedAt: now
14814
- };
14815
- try {
14816
- await db.transact([
14817
- membershipUpdate("networkSeat", id6, attrs),
14818
- membershipUpdate("networkSeatRecipient", claimId, {
14819
- id: claimId,
14820
- appId: input.appId,
14821
- chapterId: input.chapterId,
14822
- environment: input.environment,
14823
- runtime: input.runtime,
14824
- seatId: id6,
14825
- recipientEmailNormalized: email,
14826
- createdAt: now
14827
- }),
14828
- membershipUpdate("networkMembership", memberId, { namedSeatId: id6, revision: Number(member.revision) + 1, updatedAt: now })
14829
- ], { mutationId: `named-seat-reserve:${id6}`, guards: [
14830
- { ns: "networkSeat", id: id6, exists: false },
14831
- { ns: "networkSeatRecipient", id: claimId, exists: false },
14832
- { ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
14833
- { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
14834
- ] });
14835
- } catch (error) {
14836
- if (error.code !== "transact_guard_failed") throw error;
14837
- }
14838
- seat = await membershipLoad(db, "networkSeat", id6);
14839
- if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
14840
- }
14841
- requireMembershipScope(seat, input);
14842
- if (seat.primaryMembershipId !== memberId || seat.recipientName !== name || seat.recipientEmailNormalized !== email || seat.priceId !== input.priceId || seat.purchaserUserId !== input.purchaserUserId) membershipFail("named_seat_purchase_conflict");
14843
- return seat;
14844
- }
14845
-
14846
15010
  // src/membership-seat-stripe.ts
14847
15011
  var import_stripe15 = require("@odla-ai/stripe");
14848
15012
  var reject7 = () => {
@@ -14930,6 +15094,7 @@ async function attachNamedSeatStripeRenewal(sk, seat) {
14930
15094
  // src/membership-seat-checkout.ts
14931
15095
  async function prepareNamedMembershipSeatPayment(db, input, secretKey, now) {
14932
15096
  let seat = await reserveNamedMembershipSeat(db, input, now);
15097
+ if (namedSeatIncluded(seat)) membershipFail("named_seat_included");
14933
15098
  const state = await loadMembershipAuthorityState(db, input);
14934
15099
  const member = await membershipLoad(db, "networkMembership", seat.primaryMembershipId);
14935
15100
  requireMembershipScope(member, input);
@@ -15136,7 +15301,7 @@ async function recordRecipient(db, input, now, approve) {
15136
15301
  requireMembershipScope(member, input);
15137
15302
  const entitlement = await membershipLoad(db, "networkEntitlement", String(seat.entitlementId));
15138
15303
  requireMembershipScope(entitlement, input);
15139
- if (!["pending_acceptance", "pending_approval", "pending_primary", "active"].includes(String(seat.status)) || !["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || Number(member.refundedCents) !== 0 || now >= Number(member.termEndsAt) || !seat.providerPaymentIntentId || !seat.providerSubscriptionItemId) membershipFail("named_seat_acceptance_unavailable");
15304
+ if (!["pending_acceptance", "pending_approval", "pending_primary", "active"].includes(String(seat.status)) || !["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || Number(member.refundedCents) !== 0 || now >= Number(member.termEndsAt) || !namedSeatSettled(seat)) membershipFail("named_seat_acceptance_unavailable");
15140
15305
  if (seat.acceptedAt === void 0) {
15141
15306
  if (approve || normalizeChapterEmail(input.verifiedRecipientEmail) !== seat.recipientEmailNormalized) {
15142
15307
  membershipFail("named_seat_recipient_verification_required");
@@ -15323,6 +15488,15 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
15323
15488
  };
15324
15489
  if (action === "seat-accept") return acceptNamedSeatRecipient(db, recipientInput, now);
15325
15490
  if (action === "seat-approve") return approveNamedSeatRecipient(db, recipientInput, now);
15491
+ const checkout = {
15492
+ ...target,
15493
+ priceId: peer.seatPriceId,
15494
+ recipientName: String(input.recipientName),
15495
+ recipientEmail: String(input.recipientEmail),
15496
+ purchaserEmail: String(input.purchaserEmail),
15497
+ purchaserUserId: String(input.purchaserUserId)
15498
+ };
15499
+ if (action === "seat-checkouts" && namedSeatIncluded(await reserveNamedMembershipSeat(db, checkout, now))) return grantIncludedNamedSeat(db, checkout, now);
15326
15500
  const secret = await getVaultSecret(db, stripeSecretName);
15327
15501
  if (!secret) throw new Error("network Stripe provider is unavailable");
15328
15502
  if (action === "seat-decline") {
@@ -15338,14 +15512,7 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
15338
15512
  return { seatId: seat.id, status: "refunded", refundedCents: refunded.refundedCents, subscriptionCanceled: false };
15339
15513
  }
15340
15514
  if (action === "seat-operator-refund") return refundNamedSeatByOperator(db, { ...target, seatId: String(input.seatId) }, secret, now);
15341
- if (action === "seat-checkouts") return prepareNamedMembershipSeatPayment(db, {
15342
- ...target,
15343
- priceId: peer.seatPriceId,
15344
- recipientName: String(input.recipientName),
15345
- recipientEmail: String(input.recipientEmail),
15346
- purchaserEmail: String(input.purchaserEmail),
15347
- purchaserUserId: String(input.purchaserUserId)
15348
- }, secret, now);
15515
+ if (action === "seat-checkouts") return prepareNamedMembershipSeatPayment(db, checkout, secret, now);
15349
15516
  const evidence = { ...target, seatId: String(input.seatId), eventId: String(input.eventId) };
15350
15517
  if (action === "seat-payments") {
15351
15518
  const receipt = await confirmNamedMembershipSeatPayment(db, evidence, now);
@@ -16201,13 +16368,13 @@ async function requestNamedSeatDecline(options) {
16201
16368
  verifiedRecipientEmail: options.verifiedRecipientEmail
16202
16369
  });
16203
16370
  const seat = result.seat;
16204
- if (!seat || seat.seatId !== options.seatId || seat.status !== "refunded" || seat.subscriptionCanceled !== false || !Number.isSafeInteger(seat.refundedCents) || seat.refundedCents <= 0) throw new Error("invalid named seat refund receipt");
16371
+ if (!seat || seat.seatId !== options.seatId || seat.status !== "refunded" || seat.subscriptionCanceled !== false || !Number.isSafeInteger(seat.refundedCents) || seat.refundedCents < 0) throw new Error("invalid named seat refund receipt");
16205
16372
  return seat;
16206
16373
  }
16207
16374
  async function requestNamedSeatOperatorRefund(options) {
16208
16375
  const result = await requestAuthority(options, "seat-operator-refund", { applicationId: options.applicationId, skuId: options.skuId, seatId: options.seatId });
16209
16376
  const seat = result.seat;
16210
- if (!seat || seat.seatId !== options.seatId || seat.status !== "refunded" || seat.subscriptionCanceled !== false || !Number.isSafeInteger(seat.refundedCents) || seat.refundedCents <= 0) throw new Error("invalid named seat refund receipt");
16377
+ if (!seat || seat.seatId !== options.seatId || seat.status !== "refunded" || seat.subscriptionCanceled !== false || !Number.isSafeInteger(seat.refundedCents) || seat.refundedCents < 0) throw new Error("invalid named seat refund receipt");
16211
16378
  return seat;
16212
16379
  }
16213
16380
  async function requestNamedSeatReservation(options) {
@@ -16221,13 +16388,13 @@ async function requestNamedSeatReservation(options) {
16221
16388
  });
16222
16389
  const seat = result.seat;
16223
16390
  const expected = (await membershipOfferIdForRequest({ ...options, requestId: "named_seat" })).replace(/^offer_/, "seat_");
16224
- if (!seat || seat.seatId !== expected || !Number.isSafeInteger(seat.amountCents) || seat.amountCents <= 0 || !/^[a-z]{3}$/.test(seat.currency) || !Number.isSafeInteger(seat.termEndsAt)) throw new Error("invalid seat reservation receipt");
16391
+ if (!seat || seat.seatId !== expected || !Number.isSafeInteger(seat.amountCents) || seat.amountCents < 0 || !/^[a-z]{3}$/.test(seat.currency) || !Number.isSafeInteger(seat.termEndsAt)) throw new Error("invalid seat reservation receipt");
16225
16392
  return { seatId: seat.seatId, amountCents: seat.amountCents, currency: seat.currency, termEndsAt: seat.termEndsAt };
16226
16393
  }
16227
16394
  async function requestNamedSeatOffer(options) {
16228
16395
  const result = await requestAuthority(options, "seat-offer", { applicationId: options.applicationId, skuId: options.skuId });
16229
16396
  const offer = result.seat;
16230
- if (!offer || offer.membershipId !== await networkMembershipId(options) || !Number.isSafeInteger(offer.amountCents) || offer.amountCents <= 0 || !/^[a-z]{3}$/.test(offer.currency) || !["month", "year"].includes(offer.interval) || typeof offer.eligible !== "boolean" || typeof offer.autoRenew !== "boolean" || !Number.isSafeInteger(offer.termEndsAt) || !Number.isSafeInteger(offer.purchaseEndsAt) || offer.purchaseWindowed !== void 0 && typeof offer.purchaseWindowed !== "boolean" || offer.digest !== await namedSeatOfferDigest(offer)) throw new Error("invalid named seat offer");
16397
+ if (!offer || offer.membershipId !== await networkMembershipId(options) || !Number.isSafeInteger(offer.amountCents) || offer.amountCents < 0 || offer.included !== (offer.amountCents === 0) || !/^[a-z]{3}$/.test(offer.currency) || !["month", "year"].includes(offer.interval) || typeof offer.eligible !== "boolean" || typeof offer.autoRenew !== "boolean" || !Number.isSafeInteger(offer.termEndsAt) || !Number.isSafeInteger(offer.purchaseEndsAt) || offer.purchaseWindowed !== void 0 && typeof offer.purchaseWindowed !== "boolean" || offer.digest !== await namedSeatOfferDigest(offer)) throw new Error("invalid named seat offer");
16231
16398
  return offer;
16232
16399
  }
16233
16400
  async function requestNamedSeatInvitation(options) {
@@ -16263,7 +16430,8 @@ async function requestNamedSeatCheckout(options) {
16263
16430
  });
16264
16431
  const seat = result.seat;
16265
16432
  const expected = (await membershipOfferIdForRequest({ ...options, requestId: "named_seat" })).replace(/^offer_/, "seat_");
16266
- if (!seat || seat.seatId !== expected || !/^pi_[A-Za-z0-9_]+$/.test(seat.paymentIntentId) || typeof seat.clientSecret !== "string" || !Number.isSafeInteger(seat.amountCents) || seat.amountCents <= 0 || !/^[a-z]{3}$/.test(seat.currency) || !Number.isSafeInteger(seat.termEndsAt) || !["payment_processing", "renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status)) {
16433
+ const included = seat?.amountCents === 0;
16434
+ if (!seat || seat.seatId !== expected || !Number.isSafeInteger(seat.amountCents) || seat.amountCents < 0 || !/^[a-z]{3}$/.test(seat.currency) || !Number.isSafeInteger(seat.termEndsAt) || (included ? seat.included !== true || seat.paymentIntentId !== void 0 || seat.clientSecret !== void 0 || !["pending_acceptance", "pending_approval", "pending_primary", "active"].includes(seat.status) : seat.included !== void 0 || !/^pi_[A-Za-z0-9_]+$/.test(String(seat.paymentIntentId)) || typeof seat.clientSecret !== "string" || !["payment_processing", "renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status))) {
16267
16435
  throw new Error("invalid named seat checkout receipt");
16268
16436
  }
16269
16437
  return seat;