@odla-ai/chapter 0.53.0 → 0.54.1

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.
@@ -1760,8 +1760,8 @@ var object = (value) => {
1760
1760
  var id = (value) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9_.:-]{2,159}$/.test(value) ? value : fail2("id");
1761
1761
  var integer2 = (value, min, max) => typeof value === "number" && Number.isSafeInteger(value) && value >= min && value <= max ? value : fail2("integer");
1762
1762
  var time = (value) => {
1763
- const at = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
1764
- return integer2(at, 0, 864e13);
1763
+ const at2 = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
1764
+ return integer2(at2, 0, 864e13);
1765
1765
  };
1766
1766
  var ids = (value, allowEmpty = false) => {
1767
1767
  if (!Array.isArray(value) || value.length > 100 || !allowEmpty && value.length === 0) return fail2("ids");
@@ -1805,7 +1805,7 @@ function membershipOfferingVersion(value) {
1805
1805
  term: normalizedTerm,
1806
1806
  autoRenew: true,
1807
1807
  paymentGraceDays: integer2(input.paymentGraceDays, 0, 60),
1808
- seatPriceCents: integer2(input.seatPriceCents, 1, Number.MAX_SAFE_INTEGER),
1808
+ seatPriceCents: integer2(input.seatPriceCents, 0, Number.MAX_SAFE_INTEGER),
1809
1809
  ...input.seatPurchaseWindowCalendarMonths === void 0 || input.seatPurchaseWindowCalendarMonths === null ? {} : { seatPurchaseWindowCalendarMonths: integer2(input.seatPurchaseWindowCalendarMonths, 1, 120) },
1810
1810
  requiredDisclosures: ids(input.requiredDisclosures)
1811
1811
  };
@@ -2893,7 +2893,7 @@ async function checkoutVariables(db, group, app) {
2893
2893
  const frozen = await frozenCheckoutVariables(db, app);
2894
2894
  return frozen.tierName ? frozen : { ...frozen, tierName: await catalogTierName(db, group, app) };
2895
2895
  }
2896
- function formatInstant(at, timeZone) {
2896
+ function formatInstant(at2, timeZone) {
2897
2897
  return new Intl.DateTimeFormat("en-US", {
2898
2898
  weekday: "long",
2899
2899
  month: "long",
@@ -2903,7 +2903,7 @@ function formatInstant(at, timeZone) {
2903
2903
  minute: "2-digit",
2904
2904
  timeZoneName: "short",
2905
2905
  timeZone
2906
- }).format(new Date(at));
2906
+ }).format(new Date(at2));
2907
2907
  }
2908
2908
  var adminUrlFor = (origin) => `${origin}/admin/`;
2909
2909
  var membersUrlFor = (origin) => `${origin}/members/`;
@@ -3851,6 +3851,181 @@ async function refundNamedSeatStripePayment(secretKey, seat) {
3851
3851
  return { refundedCents, refundIds };
3852
3852
  }
3853
3853
 
3854
+ // src/membership-seat-reservation.ts
3855
+ async function recipientKey(input, email) {
3856
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([input.appId, input.chapterId, input.environment, input.runtime, email])));
3857
+ return `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
3858
+ }
3859
+ async function reserveNamedMembershipSeat(db, input, now) {
3860
+ membershipClock(now);
3861
+ const name = typeof input.recipientName === "string" ? input.recipientName.trim().replace(/\s+/g, " ") : "";
3862
+ const email = normalizeChapterEmail(typeof input.recipientEmail === "string" ? input.recipientEmail : "");
3863
+ const purchaser = normalizeChapterEmail(typeof input.purchaserEmail === "string" ? input.purchaserEmail : "");
3864
+ 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");
3865
+ const state = await loadMembershipAuthorityState(db, input);
3866
+ const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
3867
+ requireMembershipScope(member, input);
3868
+ if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
3869
+ const id6 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
3870
+ let seat = await membershipLoad(db, "networkSeat", id6);
3871
+ if (!seat) {
3872
+ const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
3873
+ const purchaseEndsAt = seatPurchaseDeadline(offering, member);
3874
+ 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) {
3875
+ membershipFail("named_seat_purchase_unavailable");
3876
+ }
3877
+ const attrs = {
3878
+ id: id6,
3879
+ ...membershipScope(input),
3880
+ primaryMembershipId: memberId,
3881
+ recipientName: name,
3882
+ recipientEmailNormalized: email,
3883
+ purchaserUserId: input.purchaserUserId,
3884
+ recipientClaimId: claimId,
3885
+ priceId: input.priceId,
3886
+ primaryPriceId: member.providerPriceId,
3887
+ amountCents: offering.seatPriceCents,
3888
+ currency: offering.currency,
3889
+ interval: offering.interval,
3890
+ providerCustomerId: member.providerCustomerId,
3891
+ providerSubscriptionId: member.providerSubscriptionId,
3892
+ status: "payment_pending",
3893
+ preparedAt: now,
3894
+ termEndsAt: member.termEndsAt,
3895
+ seatPurchaseEndsAt: purchaseEndsAt,
3896
+ offeringSnapshot: offering,
3897
+ revision: 1,
3898
+ createdAt: now,
3899
+ updatedAt: now
3900
+ };
3901
+ try {
3902
+ await db.transact([
3903
+ membershipUpdate("networkSeat", id6, attrs),
3904
+ membershipUpdate("networkSeatRecipient", claimId, {
3905
+ id: claimId,
3906
+ appId: input.appId,
3907
+ chapterId: input.chapterId,
3908
+ environment: input.environment,
3909
+ runtime: input.runtime,
3910
+ seatId: id6,
3911
+ recipientEmailNormalized: email,
3912
+ createdAt: now
3913
+ }),
3914
+ membershipUpdate("networkMembership", memberId, { namedSeatId: id6, revision: Number(member.revision) + 1, updatedAt: now })
3915
+ ], { mutationId: `named-seat-reserve:${id6}`, guards: [
3916
+ { ns: "networkSeat", id: id6, exists: false },
3917
+ { ns: "networkSeatRecipient", id: claimId, exists: false },
3918
+ { ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
3919
+ { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
3920
+ ] });
3921
+ } catch (error) {
3922
+ if (error.code !== "transact_guard_failed") throw error;
3923
+ }
3924
+ seat = await membershipLoad(db, "networkSeat", id6);
3925
+ if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
3926
+ }
3927
+ requireMembershipScope(seat, input);
3928
+ if (seat.primaryMembershipId !== memberId || seat.recipientName !== name || seat.recipientEmailNormalized !== email || seat.priceId !== input.priceId || seat.purchaserUserId !== input.purchaserUserId) membershipFail("named_seat_purchase_conflict");
3929
+ return seat;
3930
+ }
3931
+
3932
+ // src/membership-seat-included.ts
3933
+ var namedSeatIncluded = (seat) => Number(seat.amountCents) === 0;
3934
+ var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean(seat.providerPaymentIntentId && seat.providerSubscriptionItemId);
3935
+ function namedSeatRecipientState(seat) {
3936
+ switch (String(seat.status)) {
3937
+ case "renewal_pending":
3938
+ case "pending_acceptance":
3939
+ return "invited";
3940
+ case "pending_approval":
3941
+ case "pending_primary":
3942
+ return "accepted";
3943
+ case "active":
3944
+ return "joined";
3945
+ case "refund_pending":
3946
+ case "refunded":
3947
+ return seat.refundReason === "declined" ? "declined" : "refunded";
3948
+ case "canceled":
3949
+ return "canceled";
3950
+ default:
3951
+ return "payment_pending";
3952
+ }
3953
+ }
3954
+ var OPEN = ["pending_acceptance", "pending_approval", "pending_primary", "active"];
3955
+ async function grantIncludedNamedSeat(db, input, now) {
3956
+ membershipClock(now);
3957
+ const seat = await reserveNamedMembershipSeat(db, input, now);
3958
+ if (!namedSeatIncluded(seat)) membershipFail("named_seat_not_included");
3959
+ const state = await loadMembershipAuthorityState(db, input);
3960
+ const member = await membershipLoad(db, "networkMembership", seat.primaryMembershipId);
3961
+ requireMembershipScope(member, input);
3962
+ 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)) {
3963
+ membershipFail("named_seat_purchase_unavailable");
3964
+ }
3965
+ if (seat.status === "payment_pending") {
3966
+ const entitlementId = `${seat.id}:entitlement`, inviteId = `${seat.id}:invite`, source = `included:${seat.id}`;
3967
+ try {
3968
+ await db.transact([
3969
+ membershipUpdate("networkSeat", seat.id, {
3970
+ status: "pending_acceptance",
3971
+ purchasedAt: now,
3972
+ termStartedAt: now,
3973
+ termEndsAt: member.termEndsAt,
3974
+ acceptanceEndsAt: Number(member.termEndsAt),
3975
+ entitlementId,
3976
+ revision: Number(seat.revision) + 1,
3977
+ updatedAt: now
3978
+ }),
3979
+ membershipUpdate("networkEntitlement", entitlementId, {
3980
+ id: entitlementId,
3981
+ ...membershipScope(input),
3982
+ membershipId: member.id,
3983
+ seatId: seat.id,
3984
+ kind: "named_seat",
3985
+ status: "pending_acceptance",
3986
+ termStartedAt: now,
3987
+ termEndsAt: member.termEndsAt,
3988
+ offeringSnapshot: member.offeringSnapshot,
3989
+ sourceEventId: source,
3990
+ revision: 1,
3991
+ createdAt: now,
3992
+ updatedAt: now
3993
+ }),
3994
+ membershipUpdate("membershipEffect", inviteId, {
3995
+ id: inviteId,
3996
+ chapterId: input.chapterId,
3997
+ environment: input.environment,
3998
+ runtime: input.runtime,
3999
+ membershipId: member.id,
4000
+ seatId: seat.id,
4001
+ kind: "invite_named_recipient",
4002
+ status: "pending",
4003
+ attempts: 0,
4004
+ createdAt: now,
4005
+ updatedAt: now
4006
+ })
4007
+ ], { mutationId: `named-seat-included:${seat.id}`, guards: [
4008
+ {
4009
+ ns: "networkSeat",
4010
+ id: seat.id,
4011
+ exists: true,
4012
+ equals: { status: "payment_pending", revision: seat.revision, amountCents: 0 },
4013
+ absent: ["purchasedAt", "providerPaymentIntentId"]
4014
+ },
4015
+ { ns: "networkEntitlement", id: entitlementId, exists: false },
4016
+ { ns: "networkMembership", id: String(member.id), exists: true, equals: { revision: member.revision, namedSeatId: seat.id } },
4017
+ { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
4018
+ ] });
4019
+ } catch (error) {
4020
+ if (error.code !== "transact_guard_failed") throw error;
4021
+ }
4022
+ }
4023
+ const current = await membershipLoad(db, "networkSeat", seat.id);
4024
+ requireMembershipScope(current, input);
4025
+ if (!namedSeatIncluded(current) || current.purchasedAt === void 0 || !OPEN.includes(String(current.status))) membershipFail("named_seat_purchase_unavailable");
4026
+ return { seatId: seat.id, amountCents: 0, currency: seat.currency, termEndsAt: Number(current.termEndsAt), status: String(current.status), included: true };
4027
+ }
4028
+
3854
4029
  // src/membership-seat-refund.ts
3855
4030
  async function requestNamedSeatRefund(db, input, now) {
3856
4031
  membershipClock(now);
@@ -3860,7 +4035,7 @@ async function requestNamedSeatRefund(db, input, now) {
3860
4035
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
3861
4036
  requireMembershipScope(member, input);
3862
4037
  if (seat.status === "refunded" || seat.status === "refund_pending") return;
3863
- 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");
4038
+ 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");
3864
4039
  const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
3865
4040
  await db.transact([
3866
4041
  membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
@@ -3905,7 +4080,7 @@ async function completeNamedSeatRefund(db, seatId, secretKey, now) {
3905
4080
  await loadMembershipAuthorityState(db, seat);
3906
4081
  if (seat.status === "refunded") return;
3907
4082
  if (seat.status !== "refund_pending") membershipFail("named_seat_refund_not_ready");
3908
- const result = await refundNamedSeatStripePayment(secretKey, seat);
4083
+ const result = namedSeatIncluded(seat) ? { refundedCents: 0, refundIds: [] } : await refundNamedSeatStripePayment(secretKey, seat);
3909
4084
  await db.transact([
3910
4085
  membershipUpdate("networkSeat", seatId, {
3911
4086
  status: "refunded",
@@ -3927,7 +4102,7 @@ async function refundNamedSeatByOperator(db, input, stripeSecretKey, now) {
3927
4102
  await requestNamedSeatRefund(db, { ...input, reason: "operator" }, now);
3928
4103
  await completeNamedSeatRefund(db, input.seatId, stripeSecretKey, now);
3929
4104
  const refunded = await membershipLoad(db, "networkSeat", input.seatId);
3930
- if (refunded?.status !== "refunded" || !Number.isSafeInteger(refunded.refundedCents) || Number(refunded.refundedCents) <= 0) {
4105
+ if (refunded?.status !== "refunded" || !Number.isSafeInteger(refunded.refundedCents) || Number(refunded.refundedCents) !== Number(seat.amountCents)) {
3931
4106
  throw new Error("named seat refund unconfirmed");
3932
4107
  }
3933
4108
  return { seatId: String(seat.id), status: "refunded", refundedCents: Number(refunded.refundedCents), subscriptionCanceled: false };
@@ -3959,9 +4134,56 @@ async function liftSeatPurchaseWindow(db, scope2, now, limit = 100) {
3959
4134
  return { lifted, more: rows5.length > limit };
3960
4135
  }
3961
4136
 
4137
+ // src/membership-seat-reprice.ts
4138
+ async function lowerSeatPrice(db, scope2, now, limit = 100) {
4139
+ membershipClock(now);
4140
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 200) throw new TypeError("invalid reprice limit");
4141
+ const state = await loadMembershipAuthorityState(db, scope2);
4142
+ const versions = (await db.query({ membershipOfferingVersion: { $: { where: { chapterId: scope2.chapterId }, limit: 200 } } })).membershipOfferingVersion ?? [];
4143
+ if (versions.length >= 200) throw Object.assign(new Error("policy_history_incomplete"), { code: "policy_history_incomplete" });
4144
+ 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];
4145
+ if (!current) throw Object.assign(new Error("ambiguous_membership_policy"), { code: "ambiguous_membership_policy" });
4146
+ const target = current.seatPriceCents;
4147
+ const rows5 = (await db.query({ networkMembership: { $: { where: { ...scope2 }, order: { updatedAt: "asc" }, limit: limit + 1 } } })).networkMembership ?? [];
4148
+ const receipt = { seatPriceCents: target, repriced: 0, skipped: [], more: rows5.length > limit };
4149
+ for (const member of rows5.slice(0, limit)) {
4150
+ if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) throw new Error("membership reprice scope mismatch");
4151
+ const id6 = String(member.id), skip = (reason) => {
4152
+ receipt.skipped.push({ membershipId: id6, reason });
4153
+ };
4154
+ const offering = membershipOfferingVersion(member.offeringSnapshot);
4155
+ if (offering.seatPriceCents <= target) {
4156
+ skip("seat price already at or below the current offering");
4157
+ continue;
4158
+ }
4159
+ if (!["paid_pending_approval", "active", "cancel_at_term_end", "payment_grace"].includes(String(member.status))) {
4160
+ skip(`membership is ${String(member.status)}`);
4161
+ continue;
4162
+ }
4163
+ const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
4164
+ if (seat && (seat.status !== "payment_pending" || seat.providerPaymentIntentId !== void 0)) {
4165
+ skip(`seat is ${String(seat.status)}`);
4166
+ continue;
4167
+ }
4168
+ const ops = [membershipUpdate("networkMembership", id6, { offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(member.revision) + 1, updatedAt: now })];
4169
+ const guards = [
4170
+ { ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
4171
+ { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative" } }
4172
+ ];
4173
+ if (seat) {
4174
+ ops.push(membershipUpdate("networkSeat", String(seat.id), { amountCents: target, offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(seat.revision) + 1, updatedAt: now }));
4175
+ guards.push({ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision, status: "payment_pending" } });
4176
+ }
4177
+ await db.transact(ops, { mutationId: `seat-reprice:${id6}:${member.revision}`, guards });
4178
+ receipt.repriced++;
4179
+ }
4180
+ return receipt;
4181
+ }
4182
+
3962
4183
  // src/worker-routes-admin-membership-seats.ts
3963
4184
  var LIST = "/api/admin/membership-seats";
3964
4185
  var LIFT = "/api/admin/membership-seats/lift-purchase-window";
4186
+ var REPRICE = "/api/admin/membership-seats/reprice";
3965
4187
  var ONE = /^\/api\/admin\/membership-seats\/([A-Za-z0-9][A-Za-z0-9_.:-]{2,159})\/refund$/;
3966
4188
  var SCOPE = /^[a-z0-9][a-z0-9-]{1,62}$/;
3967
4189
  var PUBLIC = [
@@ -3988,11 +4210,11 @@ var PUBLIC = [
3988
4210
  ];
3989
4211
  var project = (row2) => Object.fromEntries(PUBLIC.filter((key3) => row2[key3] !== void 0).map((key3) => [key3, row2[key3]]));
3990
4212
  var handleAdminMembershipSeats = async (req, url, env, ctx) => {
3991
- const one = url.pathname.match(ONE), lift = url.pathname === LIFT;
3992
- if (url.pathname !== LIST && !one && !lift) return null;
4213
+ const one = url.pathname.match(ONE), lift = url.pathname === LIFT, reprice = url.pathname === REPRICE;
4214
+ if (url.pathname !== LIST && !one && !lift && !reprice) return null;
3993
4215
  const operator = ctx.membershipAuthorityOperator;
3994
4216
  if (!operator) return json({ error: "not found" }, 404);
3995
- if (req.method !== (one || lift ? "POST" : "GET")) return json({ error: "method not allowed" }, 405);
4217
+ if (req.method !== (one || lift || reprice ? "POST" : "GET")) return json({ error: "method not allowed" }, 405);
3996
4218
  const raw2 = ctx.makeDb(env), user = await ctx.verifyUser(req, env);
3997
4219
  if (!user) return json({ error: "unauthorized" }, 401);
3998
4220
  if (!await ctx.isSuperAdminEmail(raw2, user.email, user.userId)) return json({ error: "super-admin required", code: "super_admin_required" }, 403);
@@ -4002,7 +4224,7 @@ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
4002
4224
  } catch {
4003
4225
  return json({ error: "the membership authority is not hosted by this deployment" }, 404);
4004
4226
  }
4005
- if (lift) {
4227
+ if (lift || reprice) {
4006
4228
  let body;
4007
4229
  try {
4008
4230
  body = await req.json();
@@ -4010,14 +4232,16 @@ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
4010
4232
  return json({ error: "chapterId and runtime are required" }, 400);
4011
4233
  }
4012
4234
  const appId = body.appId ?? body.chapterId;
4013
- 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)) {
4014
- return json({ error: "chapterId and runtime are required" }, 400);
4235
+ 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))) {
4236
+ return json({ error: reprice ? "chapterId, runtime and skuId are required" : "chapterId and runtime are required" }, 400);
4015
4237
  }
4238
+ const scope2 = { appId, chapterId: body.chapterId, environment: env.ODLA_ENV, runtime: body.runtime };
4016
4239
  try {
4017
- return json({ ok: true, ...await liftSeatPurchaseWindow(db, { appId, chapterId: body.chapterId, environment: env.ODLA_ENV, runtime: body.runtime }, Date.now()) });
4240
+ if (reprice) return json({ ok: true, ...await lowerSeatPrice(db, { ...scope2, skuId: String(body.skuId) }, Date.now()) });
4241
+ return json({ ok: true, ...await liftSeatPurchaseWindow(db, scope2, Date.now()) });
4018
4242
  } catch (error) {
4019
4243
  const code = error.code;
4020
- return json({ error: "purchase window lift is pending; retry to confirm", ...code ? { code } : {} }, code ? 409 : 503);
4244
+ return json({ error: `${reprice ? "seat reprice" : "purchase window lift"} is pending; retry to confirm`, ...code ? { code } : {} }, code ? 409 : 503);
4021
4245
  }
4022
4246
  }
4023
4247
  if (!one) {
@@ -4053,8 +4277,8 @@ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
4053
4277
  var LIMIT = 200;
4054
4278
  var text2 = (value) => typeof value === "string" && value.length > 0 && value.length <= 200 ? value : void 0;
4055
4279
  var instant = (value) => {
4056
- const at = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
4057
- return Number.isSafeInteger(at) && at >= 0 ? at : void 0;
4280
+ const at2 = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
4281
+ return Number.isSafeInteger(at2) && at2 >= 0 ? at2 : void 0;
4058
4282
  };
4059
4283
  function provenance(row2) {
4060
4284
  const source = text2(row2.source), actor = text2(row2.actor ?? row2.$createdBy), recordedAt = instant(row2.recordedAt ?? row2.createdAt ?? row2.$createdAt);
@@ -6427,7 +6651,7 @@ var handleMe = async (req, url, env, ctx) => {
6427
6651
  } catch {
6428
6652
  }
6429
6653
  }
6430
- return json({ ...base, memberAccess, membershipRestart, application: member?.application ?? null, namedSeatsEnabled: Boolean(memberAccess && ctx.membershipAuthority?.namedSeats && member?.source.stripeSubscriptionId && member.source.status !== "submitted") });
6654
+ return json({ ...base, memberAccess, membershipRestart, application: member?.application ?? null, namedSeatsEnabled: Boolean(ctx.membershipAuthority?.namedSeats && member?.application.paid && member.source.status !== "submitted") });
6431
6655
  };
6432
6656
  var handleCrm = async (req, url, env, ctx) => {
6433
6657
  const crmBase = ctx.crmBase;
@@ -9425,6 +9649,7 @@ async function namedSeatOfferDigest(offer) {
9425
9649
  ])));
9426
9650
  return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
9427
9651
  }
9652
+ var at = (value) => Number.isSafeInteger(value) ? Number(value) : void 0;
9428
9653
  async function readNamedSeatOffer(db, input, now) {
9429
9654
  membershipClock(now);
9430
9655
  await loadMembershipAuthorityState(db, input);
@@ -9447,10 +9672,19 @@ async function readNamedSeatOffer(db, input, now) {
9447
9672
  seatId: String(seat.id),
9448
9673
  status: String(seat.status),
9449
9674
  recipientName: String(seat.recipientName),
9450
- recipientEmail: String(seat.recipientEmailNormalized)
9675
+ recipientEmail: String(seat.recipientEmailNormalized),
9676
+ recipientState: namedSeatRecipientState(seat),
9677
+ ...at(seat.purchasedAt) !== void 0 ? { purchasedAt: at(seat.purchasedAt) } : {},
9678
+ ...at(seat.acceptedAt) !== void 0 ? { acceptedAt: at(seat.acceptedAt) } : {},
9679
+ ...seat.status === "active" && at(seat.journeyApprovedAt) !== void 0 ? { joinedAt: at(seat.journeyApprovedAt) } : {}
9451
9680
  } } : {}
9452
9681
  };
9453
- return { ...offer, digest: await namedSeatOfferDigest(offer), purchaseWindowed: offering.seatPurchaseWindowCalendarMonths !== void 0 };
9682
+ return {
9683
+ ...offer,
9684
+ digest: await namedSeatOfferDigest(offer),
9685
+ included: offering.seatPriceCents === 0,
9686
+ purchaseWindowed: offering.seatPurchaseWindowCalendarMonths !== void 0
9687
+ };
9454
9688
  }
9455
9689
 
9456
9690
  // src/named-seat-request.ts
@@ -9526,8 +9760,9 @@ var handleNamedSeat = async (req, url, env, ctx) => {
9526
9760
  if (recipientName.length < 2 || /[\u0000-\u001f\u007f]/.test(body.recipientName) || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(recipientEmail) || recipientEmail === normalizeChapterEmail(user.email)) {
9527
9761
  return json({ error: "a valid name and email for someone other than the purchaser are required" }, 400);
9528
9762
  }
9529
- if (!offer.eligible || body.quoteDigest !== offer.digest) return json({ error: "seat offer changed or is unavailable; refresh before paying" }, 409);
9530
- if (!group?.stripePublishableKey || !group.refundPolicyText || !group.merchantDisclosureText) return json({ error: "seat checkout disclosures unavailable" }, 503);
9763
+ 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);
9764
+ if (!offer.eligible && !granted || body.quoteDigest !== offer.digest) return json({ error: "seat offer changed or is unavailable; refresh before paying" }, 409);
9765
+ if (!group?.refundPolicyText || !group.merchantDisclosureText || !offer.included && !group.stripePublishableKey) return json({ error: "seat checkout disclosures unavailable" }, 503);
9531
9766
  const consentId = `${offer.membershipId}:named-seat-consent`;
9532
9767
  const prior = await membershipLoad(db, "namedSeatConsents", consentId);
9533
9768
  const attrs = {
@@ -9566,7 +9801,7 @@ var handleNamedSeat = async (req, url, env, ctx) => {
9566
9801
  }
9567
9802
  return json({
9568
9803
  ...receipt,
9569
- publishableKey: String(group.stripePublishableKey),
9804
+ ...group.stripePublishableKey ? { publishableKey: String(group.stripePublishableKey) } : {},
9570
9805
  lineItems: { standardCents: offer.amountCents, discountCents: 0, dueTodayCents: offer.amountCents }
9571
9806
  });
9572
9807
  } catch {
@@ -11368,7 +11603,7 @@ async function networkApplicationAction(opts) {
11368
11603
  if (app.status !== input.expectedStage || !applicationActionStages(app, env, ctx).includes(input.stage)) {
11369
11604
  return json({ error: "application action is no longer available; reload", code: "invalid_application_action" }, 409);
11370
11605
  }
11371
- const at = Date.now();
11606
+ const at2 = Date.now();
11372
11607
  const meta = { [ACTION_META]: {
11373
11608
  version: 1,
11374
11609
  state: "pending",
@@ -11388,7 +11623,7 @@ async function networkApplicationAction(opts) {
11388
11623
  recordId: record4.id,
11389
11624
  kind: "note",
11390
11625
  authorId: `network:${sourceId}`,
11391
- createdAt: at,
11626
+ createdAt: at2,
11392
11627
  body: `Requested ${input.expectedStage} \u2192 ${input.stage} from ${sourceId}`,
11393
11628
  meta
11394
11629
  } },
@@ -11397,7 +11632,7 @@ async function networkApplicationAction(opts) {
11397
11632
  recordId: record4.id,
11398
11633
  kind: "note",
11399
11634
  authorId: `network:${sourceId}`,
11400
- createdAt: at,
11635
+ createdAt: at2,
11401
11636
  body: "Application operation reconciliation",
11402
11637
  meta: { state: "pending", activityId }
11403
11638
  } }
@@ -12248,9 +12483,9 @@ function normalizeNetworkIdentity(kind, value) {
12248
12483
  return trimmed;
12249
12484
  }
12250
12485
  function emailLooksReal(value) {
12251
- const at = value.lastIndexOf("@");
12252
- if (at <= 0 || at === value.length - 1) return false;
12253
- const domain = value.slice(at + 1).toLowerCase();
12486
+ const at2 = value.lastIndexOf("@");
12487
+ if (at2 <= 0 || at2 === value.length - 1) return false;
12488
+ const domain = value.slice(at2 + 1).toLowerCase();
12254
12489
  return !domain.endsWith(".invalid") && domain !== "example.com" && domain !== "example.test";
12255
12490
  }
12256
12491
  function validateNetworkPersonImport(input, allowedChapterIds) {
@@ -14569,7 +14804,9 @@ async function reconcileMembershipRenewal(db, input, now) {
14569
14804
  if (start !== member.termEndsAt || start > now + 3e4) membershipFail("membership_renewal_period_gap");
14570
14805
  const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
14571
14806
  if (seat) requireMembershipScope(seat, input);
14572
- const billedSeat = seat && seat.providerSubscriptionId === member.providerSubscriptionId && seat.providerSubscriptionItemId && !["refunded", "refund_pending"].includes(String(seat.status)) ? seat : void 0;
14807
+ const openSeat = seat && !["refunded", "refund_pending", "canceled"].includes(String(seat.status)) ? seat : void 0;
14808
+ const billedSeat = openSeat && openSeat.providerSubscriptionId === member.providerSubscriptionId && openSeat.providerSubscriptionItemId ? openSeat : void 0;
14809
+ const carriedSeat = billedSeat ?? (openSeat && namedSeatIncluded(openSeat) && openSeat.purchasedAt !== void 0 ? openSeat : void 0);
14573
14810
  const prices = [String(member.providerPriceId), ...billedSeat ? [String(billedSeat.priceId)] : []].sort();
14574
14811
  const amount = Number(member.renewalAmountCents) + Number(billedSeat?.amountCents ?? 0);
14575
14812
  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");
@@ -14609,19 +14846,19 @@ async function reconcileMembershipRenewal(db, input, now) {
14609
14846
  evidenceGuard
14610
14847
  ];
14611
14848
  if (seat) guards.push({ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision } });
14612
- if (billedSeat?.entitlementId) {
14613
- const seatEntitlement = await membershipLoad(db, "networkEntitlement", String(billedSeat.entitlementId));
14849
+ if (carriedSeat?.entitlementId) {
14850
+ const seatEntitlement = await membershipLoad(db, "networkEntitlement", String(carriedSeat.entitlementId));
14614
14851
  requireMembershipScope(seatEntitlement, input);
14615
14852
  guards.push({ ns: "networkEntitlement", id: String(seatEntitlement.id), exists: true, equals: { revision: seatEntitlement.revision } });
14616
14853
  ops.push(
14617
- membershipUpdate("networkSeat", String(billedSeat.id), {
14854
+ membershipUpdate("networkSeat", String(carriedSeat.id), {
14618
14855
  ...paid ? { termStartedAt: start, termEndsAt: end } : {},
14619
- revision: Number(billedSeat.revision) + 1,
14856
+ revision: Number(carriedSeat.revision) + 1,
14620
14857
  updatedAt: now
14621
14858
  }),
14622
14859
  membershipUpdate("networkEntitlement", String(seatEntitlement.id), {
14623
14860
  ...paid ? { termStartedAt: start, termEndsAt: end } : {},
14624
- status: billedSeat.journeyApprovedAt ? entitlementStatus : seatEntitlement.status,
14861
+ status: carriedSeat.journeyApprovedAt ? entitlementStatus : seatEntitlement.status,
14625
14862
  revision: Number(seatEntitlement.revision) + 1,
14626
14863
  updatedAt: now
14627
14864
  })
@@ -14731,84 +14968,6 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
14731
14968
  return membershipFail("membership_billing_retry_required");
14732
14969
  }
14733
14970
 
14734
- // src/membership-seat-reservation.ts
14735
- async function recipientKey(input, email) {
14736
- const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([input.appId, input.chapterId, input.environment, input.runtime, email])));
14737
- return `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
14738
- }
14739
- async function reserveNamedMembershipSeat(db, input, now) {
14740
- membershipClock(now);
14741
- const name = typeof input.recipientName === "string" ? input.recipientName.trim().replace(/\s+/g, " ") : "";
14742
- const email = normalizeChapterEmail(typeof input.recipientEmail === "string" ? input.recipientEmail : "");
14743
- const purchaser = normalizeChapterEmail(typeof input.purchaserEmail === "string" ? input.purchaserEmail : "");
14744
- 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");
14745
- const state = await loadMembershipAuthorityState(db, input);
14746
- const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
14747
- requireMembershipScope(member, input);
14748
- if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
14749
- const id6 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
14750
- let seat = await membershipLoad(db, "networkSeat", id6);
14751
- if (!seat) {
14752
- const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
14753
- const purchaseEndsAt = seatPurchaseDeadline(offering, member);
14754
- 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) {
14755
- membershipFail("named_seat_purchase_unavailable");
14756
- }
14757
- const attrs = {
14758
- id: id6,
14759
- ...membershipScope(input),
14760
- primaryMembershipId: memberId,
14761
- recipientName: name,
14762
- recipientEmailNormalized: email,
14763
- purchaserUserId: input.purchaserUserId,
14764
- recipientClaimId: claimId,
14765
- priceId: input.priceId,
14766
- primaryPriceId: member.providerPriceId,
14767
- amountCents: offering.seatPriceCents,
14768
- currency: offering.currency,
14769
- interval: offering.interval,
14770
- providerCustomerId: member.providerCustomerId,
14771
- providerSubscriptionId: member.providerSubscriptionId,
14772
- status: "payment_pending",
14773
- preparedAt: now,
14774
- termEndsAt: member.termEndsAt,
14775
- seatPurchaseEndsAt: purchaseEndsAt,
14776
- offeringSnapshot: offering,
14777
- revision: 1,
14778
- createdAt: now,
14779
- updatedAt: now
14780
- };
14781
- try {
14782
- await db.transact([
14783
- membershipUpdate("networkSeat", id6, attrs),
14784
- membershipUpdate("networkSeatRecipient", claimId, {
14785
- id: claimId,
14786
- appId: input.appId,
14787
- chapterId: input.chapterId,
14788
- environment: input.environment,
14789
- runtime: input.runtime,
14790
- seatId: id6,
14791
- recipientEmailNormalized: email,
14792
- createdAt: now
14793
- }),
14794
- membershipUpdate("networkMembership", memberId, { namedSeatId: id6, revision: Number(member.revision) + 1, updatedAt: now })
14795
- ], { mutationId: `named-seat-reserve:${id6}`, guards: [
14796
- { ns: "networkSeat", id: id6, exists: false },
14797
- { ns: "networkSeatRecipient", id: claimId, exists: false },
14798
- { ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
14799
- { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
14800
- ] });
14801
- } catch (error) {
14802
- if (error.code !== "transact_guard_failed") throw error;
14803
- }
14804
- seat = await membershipLoad(db, "networkSeat", id6);
14805
- if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
14806
- }
14807
- requireMembershipScope(seat, input);
14808
- if (seat.primaryMembershipId !== memberId || seat.recipientName !== name || seat.recipientEmailNormalized !== email || seat.priceId !== input.priceId || seat.purchaserUserId !== input.purchaserUserId) membershipFail("named_seat_purchase_conflict");
14809
- return seat;
14810
- }
14811
-
14812
14971
  // src/membership-seat-stripe.ts
14813
14972
  import { stripeRequest as stripeRequest12 } from "@odla-ai/stripe";
14814
14973
  var reject7 = () => {
@@ -14896,6 +15055,7 @@ async function attachNamedSeatStripeRenewal(sk, seat) {
14896
15055
  // src/membership-seat-checkout.ts
14897
15056
  async function prepareNamedMembershipSeatPayment(db, input, secretKey, now) {
14898
15057
  let seat = await reserveNamedMembershipSeat(db, input, now);
15058
+ if (namedSeatIncluded(seat)) membershipFail("named_seat_included");
14899
15059
  const state = await loadMembershipAuthorityState(db, input);
14900
15060
  const member = await membershipLoad(db, "networkMembership", seat.primaryMembershipId);
14901
15061
  requireMembershipScope(member, input);
@@ -15102,7 +15262,7 @@ async function recordRecipient(db, input, now, approve) {
15102
15262
  requireMembershipScope(member, input);
15103
15263
  const entitlement = await membershipLoad(db, "networkEntitlement", String(seat.entitlementId));
15104
15264
  requireMembershipScope(entitlement, input);
15105
- 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");
15265
+ 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");
15106
15266
  if (seat.acceptedAt === void 0) {
15107
15267
  if (approve || normalizeChapterEmail(input.verifiedRecipientEmail) !== seat.recipientEmailNormalized) {
15108
15268
  membershipFail("named_seat_recipient_verification_required");
@@ -15289,6 +15449,15 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
15289
15449
  };
15290
15450
  if (action === "seat-accept") return acceptNamedSeatRecipient(db, recipientInput, now);
15291
15451
  if (action === "seat-approve") return approveNamedSeatRecipient(db, recipientInput, now);
15452
+ const checkout = {
15453
+ ...target,
15454
+ priceId: peer.seatPriceId,
15455
+ recipientName: String(input.recipientName),
15456
+ recipientEmail: String(input.recipientEmail),
15457
+ purchaserEmail: String(input.purchaserEmail),
15458
+ purchaserUserId: String(input.purchaserUserId)
15459
+ };
15460
+ if (action === "seat-checkouts" && namedSeatIncluded(await reserveNamedMembershipSeat(db, checkout, now))) return grantIncludedNamedSeat(db, checkout, now);
15292
15461
  const secret = await getVaultSecret(db, stripeSecretName);
15293
15462
  if (!secret) throw new Error("network Stripe provider is unavailable");
15294
15463
  if (action === "seat-decline") {
@@ -15304,14 +15473,7 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
15304
15473
  return { seatId: seat.id, status: "refunded", refundedCents: refunded.refundedCents, subscriptionCanceled: false };
15305
15474
  }
15306
15475
  if (action === "seat-operator-refund") return refundNamedSeatByOperator(db, { ...target, seatId: String(input.seatId) }, secret, now);
15307
- if (action === "seat-checkouts") return prepareNamedMembershipSeatPayment(db, {
15308
- ...target,
15309
- priceId: peer.seatPriceId,
15310
- recipientName: String(input.recipientName),
15311
- recipientEmail: String(input.recipientEmail),
15312
- purchaserEmail: String(input.purchaserEmail),
15313
- purchaserUserId: String(input.purchaserUserId)
15314
- }, secret, now);
15476
+ if (action === "seat-checkouts") return prepareNamedMembershipSeatPayment(db, checkout, secret, now);
15315
15477
  const evidence = { ...target, seatId: String(input.seatId), eventId: String(input.eventId) };
15316
15478
  if (action === "seat-payments") {
15317
15479
  const receipt = await confirmNamedMembershipSeatPayment(db, evidence, now);
@@ -16167,13 +16329,13 @@ async function requestNamedSeatDecline(options) {
16167
16329
  verifiedRecipientEmail: options.verifiedRecipientEmail
16168
16330
  });
16169
16331
  const seat = result.seat;
16170
- 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");
16332
+ 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");
16171
16333
  return seat;
16172
16334
  }
16173
16335
  async function requestNamedSeatOperatorRefund(options) {
16174
16336
  const result = await requestAuthority(options, "seat-operator-refund", { applicationId: options.applicationId, skuId: options.skuId, seatId: options.seatId });
16175
16337
  const seat = result.seat;
16176
- 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");
16338
+ 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");
16177
16339
  return seat;
16178
16340
  }
16179
16341
  async function requestNamedSeatReservation(options) {
@@ -16187,13 +16349,13 @@ async function requestNamedSeatReservation(options) {
16187
16349
  });
16188
16350
  const seat = result.seat;
16189
16351
  const expected = (await membershipOfferIdForRequest({ ...options, requestId: "named_seat" })).replace(/^offer_/, "seat_");
16190
- 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");
16352
+ 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");
16191
16353
  return { seatId: seat.seatId, amountCents: seat.amountCents, currency: seat.currency, termEndsAt: seat.termEndsAt };
16192
16354
  }
16193
16355
  async function requestNamedSeatOffer(options) {
16194
16356
  const result = await requestAuthority(options, "seat-offer", { applicationId: options.applicationId, skuId: options.skuId });
16195
16357
  const offer = result.seat;
16196
- 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");
16358
+ 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");
16197
16359
  return offer;
16198
16360
  }
16199
16361
  async function requestNamedSeatInvitation(options) {
@@ -16229,7 +16391,8 @@ async function requestNamedSeatCheckout(options) {
16229
16391
  });
16230
16392
  const seat = result.seat;
16231
16393
  const expected = (await membershipOfferIdForRequest({ ...options, requestId: "named_seat" })).replace(/^offer_/, "seat_");
16232
- 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)) {
16394
+ const included = seat?.amountCents === 0;
16395
+ 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))) {
16233
16396
  throw new Error("invalid named seat checkout receipt");
16234
16397
  }
16235
16398
  return seat;
@@ -16720,6 +16883,12 @@ export {
16720
16883
  deliverMembershipChapterEffect,
16721
16884
  deliverSignupControl,
16722
16885
  drainMembershipEffects,
16886
+ grantIncludedNamedSeat,
16887
+ liftSeatPurchaseWindow,
16888
+ lowerSeatPrice,
16889
+ namedSeatIncluded,
16890
+ namedSeatRecipientState,
16891
+ readNamedSeatOffer,
16723
16892
  refundNamedSeatByOperator,
16724
16893
  renderActiveSignupControlHtml,
16725
16894
  resolveNetworkTargetTransport,