@odla-ai/chapter 0.49.0 → 0.51.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.
package/dist/index.js CHANGED
@@ -1442,6 +1442,7 @@ var DEFAULT_ADMIN_COPY = {
1442
1442
  },
1443
1443
  records: {
1444
1444
  workflowMissing: "This record is not linked to an application workflow.",
1445
+ invitation: "Invitation",
1445
1446
  comms: "Comms",
1446
1447
  commsHistory: "Comms history",
1447
1448
  scheduling: "Scheduling",
@@ -1526,7 +1527,9 @@ var DEFAULT_JOIN_COPY = {
1526
1527
  processing: "Processing\u2026",
1527
1528
  payAndContinue: "Pay and continue",
1528
1529
  incomplete: "The payment did not complete. Please try again.",
1529
- consent: "I have read and agree to the pricing and refund policy above."
1530
+ consent: "I have read and agree to the pricing and refund policy above.",
1531
+ foundingMember: "Founding member rate",
1532
+ foundingDiscount: "Founding discount"
1530
1533
  },
1531
1534
  done: {
1532
1535
  label: "You're booked",
@@ -3083,7 +3086,7 @@ function findTier(tiers2, tierId) {
3083
3086
  if (!tierId) return tiers2.length === 1 ? tiers2[0] : null;
3084
3087
  return tiers2.find((tier) => tier.id === tierId) ?? null;
3085
3088
  }
3086
- function joinConfigTiers(tiers2, defaults = {}) {
3089
+ function joinConfigTiers(tiers2, defaults = {}, founding = /* @__PURE__ */ new Map()) {
3087
3090
  return tiers2.map((tier) => ({
3088
3091
  id: tier.id,
3089
3092
  name: tier.name,
@@ -3091,7 +3094,8 @@ function joinConfigTiers(tiers2, defaults = {}) {
3091
3094
  currency: tier.currency ?? defaults.currency ?? "usd",
3092
3095
  interval: tier.interval ?? defaults.interval ?? "year",
3093
3096
  blurb: tier.blurb ?? "",
3094
- free: tierIsFree(tier)
3097
+ free: tierIsFree(tier),
3098
+ ...founding.has(tier.id) ? { founding: founding.get(tier.id) } : {}
3095
3099
  }));
3096
3100
  }
3097
3101
 
@@ -3194,6 +3198,11 @@ function createChapterIntegration(chapter, options = {}) {
3194
3198
  }
3195
3199
 
3196
3200
  // src/membership-policy.ts
3201
+ function seatPurchaseDeadline(offering, member) {
3202
+ if (offering.seatPurchaseWindowCalendarMonths === void 0) return Number(member.termEndsAt);
3203
+ if (Number.isSafeInteger(member.seatPurchaseEndsAt)) return Number(member.seatPurchaseEndsAt);
3204
+ return addMembershipCalendarMonths(Number(member.initialTermStartedAt ?? member.termStartedAt), offering.seatPurchaseWindowCalendarMonths);
3205
+ }
3197
3206
  var fail2 = (field2) => {
3198
3207
  throw new TypeError(`invalid membership policy: ${field2}`);
3199
3208
  };
@@ -3250,7 +3259,7 @@ function membershipOfferingVersion(value) {
3250
3259
  autoRenew: true,
3251
3260
  paymentGraceDays: integer2(input.paymentGraceDays, 0, 60),
3252
3261
  seatPriceCents: integer2(input.seatPriceCents, 1, Number.MAX_SAFE_INTEGER),
3253
- seatPurchaseWindowCalendarMonths: integer2(input.seatPurchaseWindowCalendarMonths, 1, 120),
3262
+ ...input.seatPurchaseWindowCalendarMonths === void 0 || input.seatPurchaseWindowCalendarMonths === null ? {} : { seatPurchaseWindowCalendarMonths: integer2(input.seatPurchaseWindowCalendarMonths, 1, 120) },
3254
3263
  requiredDisclosures: ids(input.requiredDisclosures)
3255
3264
  };
3256
3265
  }
@@ -3377,24 +3386,63 @@ async function verifyIssuedMembershipOffer(value, expected) {
3377
3386
  if (!value || typeof value !== "object" || Array.isArray(value)) return reject("invalid_offer_snapshot");
3378
3387
  return restore({ ...value, approvedFoundingCount: 0 }, expected);
3379
3388
  }
3380
- async function issueMembershipCommercialOffer(db, input) {
3381
- validate(input);
3382
- const stateId = `${input.chapterId}:${input.environment}`;
3383
- const state = await load(db, "membershipAuthorityState", stateId);
3389
+ async function previewMembershipCommercialOffer(db, input) {
3390
+ for (const key3 of ["appId", "chapterId", "environment", "runtime", "skuId"]) {
3391
+ if (typeof input[key3] !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.:-]{1,159}$/.test(input[key3])) reject("invalid_offer_identity");
3392
+ }
3393
+ if (!Number.isSafeInteger(input.now) || input.now < 0) reject("invalid_offer_window");
3394
+ const state = await authoritativeState(db, input);
3395
+ const { offering, policy } = await currentPolicy(db, input, false, state);
3396
+ const approvedFoundingCount = policy ? state.cohortCounts[policy.cohortLabel] ?? 0 : 0;
3397
+ if (!Number.isSafeInteger(approvedFoundingCount) || Number(approvedFoundingCount) < 0) reject("invalid_cohort_count");
3398
+ const contract = createMembershipCommercialOffer({
3399
+ offering,
3400
+ discountPolicy: policy,
3401
+ approvedFoundingCount: Number(approvedFoundingCount),
3402
+ now: input.now,
3403
+ couponId: await couponId(input, policy?.id)
3404
+ });
3405
+ const discount = contract.billingTerms.discount;
3406
+ return {
3407
+ skuId: input.skuId,
3408
+ offeringVersionId: offering.id,
3409
+ basePriceCents: offering.basePriceCents,
3410
+ currency: offering.currency,
3411
+ interval: offering.interval,
3412
+ founding: contract.founding,
3413
+ ...contract.founding && contract.discountPolicy ? { cohortLabel: contract.discountPolicy.cohortLabel } : {},
3414
+ dueTodayCents: contract.dueTodayCents,
3415
+ renewalCents: contract.renewalCents,
3416
+ ...discount ? { discountDuration: discount.duration === "once" ? "first_term" : "while_active" } : {},
3417
+ ...contract.initialTermEndsAt !== void 0 ? { initialTermEndsAt: contract.initialTermEndsAt } : {}
3418
+ };
3419
+ }
3420
+ async function authoritativeState(db, input) {
3421
+ const state = await load(db, "membershipAuthorityState", `${input.chapterId}:${input.environment}`);
3384
3422
  if (!state || state.mode !== "authoritative" || state.chapterId !== input.chapterId || state.environment !== input.environment || !Number.isSafeInteger(state.revision) || Number(state.revision) < 1 || !state.cohortCounts || typeof state.cohortCounts !== "object" || Array.isArray(state.cohortCounts)) return reject("membership_authority_unavailable");
3385
- const existing = await load(db, "membershipCheckoutOffer", input.offerId);
3386
- if (existing) return restore(existing, input);
3423
+ return state;
3424
+ }
3425
+ async function currentPolicy(db, input, restartAware, state) {
3387
3426
  const rows3 = await db.query({
3388
3427
  membershipOfferingVersion: { $: { where: { chapterId: input.chapterId }, limit: 200 } },
3389
3428
  discountPolicyVersion: { $: { where: { chapterId: input.chapterId }, limit: 200 } }
3390
3429
  });
3391
3430
  if (!Array.isArray(rows3.membershipOfferingVersion) || !Array.isArray(rows3.discountPolicyVersion) || rows3.membershipOfferingVersion.length >= 200 || rows3.discountPolicyVersion.length >= 200) return reject("policy_history_incomplete");
3392
- const offerings = rows3.membershipOfferingVersion.map((row3) => membershipOfferingVersion(row3.snapshot)).filter((offer) => offer.chapterId === input.chapterId && offer.baseSkuId === input.skuId && offer.status === "published" && input.now >= offer.effectiveFrom && (offer.effectiveUntil === void 0 || input.now < offer.effectiveUntil));
3393
- const policies = rows3.discountPolicyVersion.map((row3) => membershipDiscountPolicyVersion(row3.snapshot)).filter((policy2) => policy2.chapterId === input.chapterId && policy2.status === "published" && policy2.cohortEligibleSkuIds.includes(input.skuId) && input.now >= policy2.qualifyingStartsAt && input.now < policy2.qualifyingEndsAt);
3431
+ const offerings = rows3.membershipOfferingVersion.map((row2) => membershipOfferingVersion(row2.snapshot)).filter((offer) => offer.chapterId === input.chapterId && offer.baseSkuId === input.skuId && offer.status === "published" && input.now >= offer.effectiveFrom && (offer.effectiveUntil === void 0 || input.now < offer.effectiveUntil));
3432
+ const policies = rows3.discountPolicyVersion.map((row2) => membershipDiscountPolicyVersion(row2.snapshot)).filter((policy2) => policy2.chapterId === input.chapterId && policy2.status === "published" && policy2.cohortEligibleSkuIds.includes(input.skuId) && input.now >= policy2.qualifyingStartsAt && input.now < policy2.qualifyingEndsAt);
3394
3433
  const offering = offerings.sort((a, b) => b.version - a.version)[0];
3395
- const restart = await load(db, "membershipRestart", (await membershipOfferIdForRequest({ ...input, requestId: "checkout_binding" })).replace(/^offer_/, "membership_"));
3434
+ const restart = restartAware && input.applicationId ? await load(db, "membershipRestart", (await membershipOfferIdForRequest({ ...input, applicationId: input.applicationId, requestId: "checkout_binding" })).replace(/^offer_/, "membership_")) : void 0;
3396
3435
  const policy = restart ? void 0 : policies.sort((a, b) => b.version - a.version)[0];
3397
3436
  if (!offering || offerings.filter((item) => item.version === offering.version).length !== 1 || policy && policies.filter((item) => item.version === policy.version).length !== 1) reject("ambiguous_membership_policy");
3437
+ return { offering, policy };
3438
+ }
3439
+ async function issueMembershipCommercialOffer(db, input) {
3440
+ validate(input);
3441
+ const stateId = `${input.chapterId}:${input.environment}`;
3442
+ const state = await authoritativeState(db, input);
3443
+ const existing = await load(db, "membershipCheckoutOffer", input.offerId);
3444
+ if (existing) return restore(existing, input);
3445
+ const { offering, policy } = await currentPolicy(db, input, true, state);
3398
3446
  const approvedFoundingCount = policy ? state.cohortCounts[policy.cohortLabel] ?? 0 : 0;
3399
3447
  if (!Number.isSafeInteger(approvedFoundingCount) || Number(approvedFoundingCount) < 0) reject("invalid_cohort_count");
3400
3448
  const contract = createMembershipCommercialOffer({
@@ -3605,7 +3653,7 @@ async function confirmMembershipPayment(db, input, now) {
3605
3653
  initialTermEndsAt: endsAt,
3606
3654
  termStartedAt: paidAt,
3607
3655
  termEndsAt: endsAt,
3608
- seatPurchaseEndsAt: addMembershipCalendarMonths(Number(paidAt), offering.seatPurchaseWindowCalendarMonths),
3656
+ seatPurchaseEndsAt: seatPurchaseDeadline(offering, { initialTermStartedAt: paidAt, termEndsAt: endsAt }),
3609
3657
  autoRenew: true,
3610
3658
  refundedCents: 0,
3611
3659
  revision: 1,
@@ -4033,7 +4081,11 @@ async function reconcileMembershipRenewal(db, input, now) {
4033
4081
  membershipUpdate("networkMembership", id10, {
4034
4082
  status: status2,
4035
4083
  graceEndsAt,
4036
- ...paid ? { termStartedAt: start, termEndsAt: end } : {},
4084
+ ...paid ? {
4085
+ termStartedAt: start,
4086
+ termEndsAt: end,
4087
+ ...membershipOfferingVersion(member.offeringSnapshot).seatPurchaseWindowCalendarMonths === void 0 ? { seatPurchaseEndsAt: end } : {}
4088
+ } : {},
4037
4089
  revision: Number(member.revision) + 1,
4038
4090
  updatedAt: now
4039
4091
  }),
@@ -4335,10 +4387,11 @@ async function reserveNamedMembershipSeat(db, input, now) {
4335
4387
  const id10 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
4336
4388
  let seat = await membershipLoad(db, "networkSeat", id10);
4337
4389
  if (!seat) {
4338
- 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 > Number(member.seatPurchaseEndsAt) || !Number.isSafeInteger(member.seatPurchaseEndsAt) || member.namedSeatId !== void 0) {
4390
+ const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
4391
+ const purchaseEndsAt = seatPurchaseDeadline(offering, member);
4392
+ 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) {
4339
4393
  membershipFail("named_seat_purchase_unavailable");
4340
4394
  }
4341
- const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
4342
4395
  const attrs = {
4343
4396
  id: id10,
4344
4397
  ...membershipScope(input),
@@ -4357,7 +4410,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
4357
4410
  status: "payment_pending",
4358
4411
  preparedAt: now,
4359
4412
  termEndsAt: member.termEndsAt,
4360
- seatPurchaseEndsAt: member.seatPurchaseEndsAt,
4413
+ seatPurchaseEndsAt: purchaseEndsAt,
4361
4414
  offeringSnapshot: offering,
4362
4415
  revision: 1,
4363
4416
  createdAt: now,
@@ -4574,14 +4627,14 @@ async function confirmNamedMembershipSeatPayment(db, input, now) {
4574
4627
  }
4575
4628
  if (seat.purchasedAt === void 0) {
4576
4629
  if (!["payment_processing", "payment_canceling"].includes(String(seat.status))) membershipFail("named_seat_payment_not_expected");
4577
- const eligible = seat.status !== "payment_canceling" && ["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) && Number(member.refundedCents) === 0 && paidAt <= Number(member.seatPurchaseEndsAt) && now < Number(member.termEndsAt);
4630
+ const eligible = seat.status !== "payment_canceling" && ["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) && Number(member.refundedCents) === 0 && paidAt <= seatPurchaseDeadline(membershipOfferingVersion(member.offeringSnapshot), member) && now < Number(member.termEndsAt);
4578
4631
  const status2 = eligible ? "renewal_pending" : "refund_pending", effectId = `${seat.id}:${eligible ? "attach-renewal" : "refund"}`;
4579
4632
  try {
4580
4633
  await db.transact([
4581
4634
  membershipUpdate("networkSeat", input.seatId, {
4582
4635
  status: status2,
4583
4636
  purchasedAt: paidAt,
4584
- acceptanceEndsAt: Math.min(paidAt + 30 * 864e5, Number(member.termEndsAt)),
4637
+ acceptanceEndsAt: Number(member.termEndsAt),
4585
4638
  termStartedAt: paidAt,
4586
4639
  termEndsAt: member.termEndsAt,
4587
4640
  paymentEventId: input.eventId,
@@ -4629,7 +4682,7 @@ async function completeNamedSeatRenewal(db, seatId, secretKey, now) {
4629
4682
  if (seat.status !== "renewal_pending" || !seat.purchasedAt) membershipFail("named_seat_renewal_not_ready");
4630
4683
  const member = await membershipLoad(db, "networkMembership", target.primaryMembershipId);
4631
4684
  requireMembershipScope(member, target);
4632
- if (now >= Number(seat.acceptanceEndsAt) || now >= Number(member.termEndsAt) || Number(member.refundedCents) !== 0 || !["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status))) membershipFail("named_seat_refund_required");
4685
+ if (now >= Number(member.termEndsAt) || Number(member.refundedCents) !== 0 || !["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status))) membershipFail("named_seat_refund_required");
4633
4686
  const itemId = await attachNamedSeatStripeRenewal(secretKey, target);
4634
4687
  const entitlementId = `${seatId}:entitlement`, effectId = `${seatId}:attach-renewal`;
4635
4688
  try {
@@ -4704,7 +4757,7 @@ async function recordRecipient(db, input, now, approve) {
4704
4757
  requireMembershipScope(entitlement, input);
4705
4758
  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");
4706
4759
  if (seat.acceptedAt === void 0) {
4707
- if (approve || now >= Number(seat.acceptanceEndsAt) || normalizeChapterEmail(input.verifiedRecipientEmail) !== seat.recipientEmailNormalized) {
4760
+ if (approve || normalizeChapterEmail(input.verifiedRecipientEmail) !== seat.recipientEmailNormalized) {
4708
4761
  membershipFail("named_seat_recipient_verification_required");
4709
4762
  }
4710
4763
  } else if (seat.recipientUserId !== input.recipientUserId || seat.recipientApplicationId !== input.recipientApplicationId) {
@@ -4817,7 +4870,7 @@ async function requestNamedSeatRefund(db, input, now) {
4817
4870
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
4818
4871
  requireMembershipScope(member, input);
4819
4872
  if (seat.status === "refunded" || seat.status === "refund_pending") return;
4820
- if (!seat.purchasedAt || !seat.providerPaymentIntentId || input.reason === "declined" && seat.journeyApprovedAt !== void 0 || input.reason === "expired" && (seat.acceptedAt !== void 0 || !Number.isSafeInteger(seat.acceptanceEndsAt) || now < Number(seat.acceptanceEndsAt)) || input.reason === "primary_closed" && !["refunded", "declined"].includes(String(member.status))) membershipFail("named_seat_refund_not_eligible");
4873
+ 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");
4821
4874
  const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
4822
4875
  await db.transact([
4823
4876
  membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
@@ -4889,6 +4942,19 @@ async function confirmNamedSeatProviderRefund(db, input, now) {
4889
4942
  await requestNamedSeatRefund(db, { ...input, reason: "provider_refund" }, now);
4890
4943
  }
4891
4944
 
4945
+ // src/membership-seat-operator-refund.ts
4946
+ async function refundNamedSeatByOperator(db, input, stripeSecretKey, now) {
4947
+ const seat = await membershipLoad(db, "networkSeat", input.seatId);
4948
+ requireMembershipScope(seat, input);
4949
+ await requestNamedSeatRefund(db, { ...input, reason: "operator" }, now);
4950
+ await completeNamedSeatRefund(db, input.seatId, stripeSecretKey, now);
4951
+ const refunded = await membershipLoad(db, "networkSeat", input.seatId);
4952
+ if (refunded?.status !== "refunded" || !Number.isSafeInteger(refunded.refundedCents) || Number(refunded.refundedCents) <= 0) {
4953
+ throw new Error("named seat refund unconfirmed");
4954
+ }
4955
+ return { seatId: String(seat.id), status: "refunded", refundedCents: Number(refunded.refundedCents), subscriptionCanceled: false };
4956
+ }
4957
+
4892
4958
  // src/membership-seat-offer.ts
4893
4959
  async function namedSeatOfferDigest(offer) {
4894
4960
  const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([
@@ -4901,7 +4967,7 @@ async function namedSeatOfferDigest(offer) {
4901
4967
  "named-only",
4902
4968
  "full-upfront",
4903
4969
  "auto-renew-coterm",
4904
- "30-day-unaccepted-full-refund"
4970
+ "no-automatic-refund"
4905
4971
  ])));
4906
4972
  return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
4907
4973
  }
@@ -4913,15 +4979,16 @@ async function readNamedSeatOffer(db, input, now) {
4913
4979
  const offering = membershipOfferingVersion(member.offeringSnapshot);
4914
4980
  const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
4915
4981
  if (seat) requireMembershipScope(seat, input);
4982
+ const purchaseEndsAt = seatPurchaseDeadline(offering, member);
4916
4983
  const offer = {
4917
4984
  membershipId: id10,
4918
4985
  amountCents: offering.seatPriceCents,
4919
4986
  currency: offering.currency,
4920
4987
  interval: offering.interval,
4921
4988
  termEndsAt: Number(member.termEndsAt),
4922
- purchaseEndsAt: Number(member.seatPurchaseEndsAt),
4989
+ purchaseEndsAt,
4923
4990
  autoRenew: member.autoRenew === true,
4924
- eligible: ["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) && member.refundedCents === 0 && now >= Number(member.initialTermStartedAt) && now < Number(member.termEndsAt) && now <= Number(member.seatPurchaseEndsAt) && (!seat || ["payment_pending", "payment_processing"].includes(String(seat.status))),
4991
+ eligible: ["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) && member.refundedCents === 0 && now >= Number(member.initialTermStartedAt) && now < Number(member.termEndsAt) && now <= purchaseEndsAt && (!seat || ["payment_pending", "payment_processing"].includes(String(seat.status))),
4925
4992
  ...seat ? { seat: {
4926
4993
  seatId: String(seat.id),
4927
4994
  status: String(seat.status),
@@ -4929,7 +4996,7 @@ async function readNamedSeatOffer(db, input, now) {
4929
4996
  recipientEmail: String(seat.recipientEmailNormalized)
4930
4997
  } } : {}
4931
4998
  };
4932
- return { ...offer, digest: await namedSeatOfferDigest(offer) };
4999
+ return { ...offer, digest: await namedSeatOfferDigest(offer), purchaseWindowed: offering.seatPurchaseWindowCalendarMonths !== void 0 };
4933
5000
  }
4934
5001
 
4935
5002
  // src/membership-seat-invitation.ts
@@ -4941,7 +5008,7 @@ async function readNamedSeatInvitation(db, input, now) {
4941
5008
  await loadMembershipAuthorityState(db, target);
4942
5009
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
4943
5010
  requireMembershipScope(member, target);
4944
- if (!["pending_acceptance", "pending_approval", "pending_primary", "active"].includes(String(seat.status)) || !["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || now >= Number(member.termEndsAt) || !seat.acceptedAt && now >= Number(seat.acceptanceEndsAt)) membershipFail("named_seat_invitation_unavailable");
5011
+ if (!["pending_acceptance", "pending_approval", "pending_primary", "active"].includes(String(seat.status)) || !["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || now >= Number(member.termEndsAt)) membershipFail("named_seat_invitation_unavailable");
4945
5012
  return {
4946
5013
  seatId: input.seatId,
4947
5014
  primaryApplicationId: target.applicationId,
@@ -4971,7 +5038,7 @@ async function readMembershipChapterEffect(db, input, now) {
4971
5038
  requireMembershipScope(member, input);
4972
5039
  if (!effect || effect.kind !== input.kind || effect.seatId !== seat.id || effect.membershipId !== member.id || ["chapterId", "environment", "runtime"].some((key3) => effect[key3] !== seat[key3]) || !["invite_named_recipient", "grant_named_recipient_access", "revoke_named_recipient_access"].includes(input.kind)) throw new Error("invalid chapter effect");
4973
5040
  let applicable;
4974
- if (input.kind === "invite_named_recipient") applicable = seat.status === "pending_acceptance" && seat.acceptedAt === void 0 && now < Number(seat.acceptanceEndsAt) && now < Number(member.termEndsAt) && ["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status));
5041
+ if (input.kind === "invite_named_recipient") applicable = seat.status === "pending_acceptance" && seat.acceptedAt === void 0 && now < Number(member.termEndsAt) && ["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status));
4975
5042
  else if (input.kind === "revoke_named_recipient_access") applicable = ["refund_pending", "refunded"].includes(String(seat.status));
4976
5043
  else applicable = (await readNamedSeatAccess(db, {
4977
5044
  ...input,
@@ -5000,6 +5067,7 @@ var MEMBERSHIP_SEAT_ACTION_FIELDS = {
5000
5067
  "seat-reservations": ["recipientName", "recipientEmail", "purchaserEmail", "purchaserUserId"],
5001
5068
  "seat-payments": ["seatId", "eventId"],
5002
5069
  "seat-refunds": ["seatId", "eventId"],
5070
+ "seat-operator-refund": ["seatId"],
5003
5071
  "seat-accept": recipient,
5004
5072
  "seat-approve": recipient,
5005
5073
  "seat-decline": recipient,
@@ -5068,6 +5136,7 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
5068
5136
  if (refunded?.status !== "refunded" || refunded.refundedCents !== seat.amountCents) throw new Error("named seat refund unconfirmed");
5069
5137
  return { seatId: seat.id, status: "refunded", refundedCents: refunded.refundedCents, subscriptionCanceled: false };
5070
5138
  }
5139
+ if (action === "seat-operator-refund") return refundNamedSeatByOperator(db, { ...target, seatId: String(input.seatId) }, secret, now);
5071
5140
  if (action === "seat-checkouts") return prepareNamedMembershipSeatPayment(db, {
5072
5141
  ...target,
5073
5142
  priceId: peer.seatPriceId,
@@ -5156,7 +5225,7 @@ function createMembershipAuthorityHandler(options) {
5156
5225
  if (req.method !== "POST") return respond({ error: "method_not_allowed" }, 405);
5157
5226
  const sender = req.headers.get(FEDERATION_HEADERS.sender);
5158
5227
  const action = url.pathname.split("/").at(-1);
5159
- if (!["offers", "checkouts", "payments", "approvals", "refunds", "renewals", "access", "restarts", "cancel-renewal", ...Object.keys(MEMBERSHIP_SEAT_ACTION_FIELDS)].includes(action ?? "")) return respond({ error: "unauthorized" }, 401);
5228
+ if (!["offers", "offer-preview", "checkouts", "payments", "approvals", "refunds", "renewals", "access", "restarts", "cancel-renewal", ...Object.keys(MEMBERSHIP_SEAT_ACTION_FIELDS)].includes(action ?? "")) return respond({ error: "unauthorized" }, 401);
5160
5229
  const peer = peers.find((candidate) => candidate.appId === sender && url.pathname === `${MEMBERSHIP_AUTHORITY_PATH}/${candidate.runtime}/${action}` && !url.search);
5161
5230
  if (!peer) return respond({ error: "unauthorized" }, 401);
5162
5231
  let body;
@@ -5178,13 +5247,23 @@ function createMembershipAuthorityHandler(options) {
5178
5247
  let input;
5179
5248
  try {
5180
5249
  input = JSON.parse(verified.body);
5181
- const fields = action === "seat-invitation" ? MEMBERSHIP_SEAT_ACTION_FIELDS[action] : ["applicationId", "skuId", ...action === "offers" ? ["requestId"] : action === "checkouts" ? ["offerId", "offerDigest", "quoteDigest", "priceId"] : action === "payments" ? ["eventId", "providerSubscriptionId", "providerCustomerId"] : action === "renewals" ? ["eventId"] : action === "restarts" ? ["previousSubscriptionId"] : MEMBERSHIP_SEAT_ACTION_FIELDS[action] ?? []];
5250
+ const fields = action === "seat-invitation" ? MEMBERSHIP_SEAT_ACTION_FIELDS[action] : action === "offer-preview" ? ["skuId"] : ["applicationId", "skuId", ...action === "offers" ? ["requestId"] : action === "checkouts" ? ["offerId", "offerDigest", "quoteDigest", "priceId"] : action === "payments" ? ["eventId", "providerSubscriptionId", "providerCustomerId"] : action === "renewals" ? ["eventId"] : action === "restarts" ? ["previousSubscriptionId"] : MEMBERSHIP_SEAT_ACTION_FIELDS[action] ?? []];
5182
5251
  if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key3) => !fields.includes(key3)) || !fields.every((key3) => typeof input[key3] === "string" && (["recipientName", "recipientEmail", "purchaserEmail", "verifiedRecipientEmail"].includes(key3) ? input[key3].length > 0 && input[key3].length <= 254 && !/[\u0000-\u001f\u007f]/.test(input[key3]) : SAFE.test(input[key3])))) {
5183
5252
  throw new TypeError("invalid request");
5184
5253
  }
5185
5254
  } catch {
5186
5255
  return respond({ error: "invalid_offer_request" }, 400);
5187
5256
  }
5257
+ if (action === "offer-preview") {
5258
+ return respond({ ok: true, preview: await previewMembershipCommercialOffer(db, {
5259
+ appId: peer.appId,
5260
+ chapterId: peer.chapterId,
5261
+ environment: peer.environment,
5262
+ runtime: peer.runtime,
5263
+ skuId: String(input.skuId),
5264
+ now: options.now?.() ?? Date.now()
5265
+ }) });
5266
+ }
5188
5267
  if (Object.hasOwn(MEMBERSHIP_SEAT_ACTION_FIELDS, action)) {
5189
5268
  return respond({ ok: true, seat: await handleMembershipSeatAction(
5190
5269
  action,
@@ -5335,6 +5414,12 @@ async function requestMembershipCommercialOffer(options) {
5335
5414
  lifetimeMs: 6e4
5336
5415
  });
5337
5416
  }
5417
+ async function requestMembershipOfferPreview(options) {
5418
+ const result = await requestAuthority(options, "offer-preview", { skuId: options.skuId });
5419
+ const preview = result.preview;
5420
+ if (!preview || preview.skuId !== options.skuId || typeof preview.founding !== "boolean" || !Number.isSafeInteger(preview.basePriceCents) || !Number.isSafeInteger(preview.dueTodayCents) || preview.dueTodayCents < 0 || preview.dueTodayCents > preview.basePriceCents || !Number.isSafeInteger(preview.renewalCents) || !/^[a-z]{3}$/.test(preview.currency) || !["month", "year"].includes(preview.interval) || preview.cohortLabel !== void 0 && typeof preview.cohortLabel !== "string" || preview.discountDuration !== void 0 && !["first_term", "while_active"].includes(preview.discountDuration)) throw new Error("invalid offer preview");
5421
+ return preview;
5422
+ }
5338
5423
  async function requestMembershipCheckoutBinding(options) {
5339
5424
  const result = await requestAuthority(options, "checkouts", {
5340
5425
  applicationId: options.applicationId,
@@ -5373,6 +5458,12 @@ async function requestNamedSeatDecline(options) {
5373
5458
  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");
5374
5459
  return seat;
5375
5460
  }
5461
+ async function requestNamedSeatOperatorRefund(options) {
5462
+ const result = await requestAuthority(options, "seat-operator-refund", { applicationId: options.applicationId, skuId: options.skuId, seatId: options.seatId });
5463
+ const seat = result.seat;
5464
+ 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");
5465
+ return seat;
5466
+ }
5376
5467
  async function requestNamedSeatReservation(options) {
5377
5468
  const result = await requestAuthority(options, "seat-reservations", {
5378
5469
  applicationId: options.applicationId,
@@ -5390,7 +5481,7 @@ async function requestNamedSeatReservation(options) {
5390
5481
  async function requestNamedSeatOffer(options) {
5391
5482
  const result = await requestAuthority(options, "seat-offer", { applicationId: options.applicationId, skuId: options.skuId });
5392
5483
  const offer = result.seat;
5393
- 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.digest !== await namedSeatOfferDigest(offer)) throw new Error("invalid named seat offer");
5484
+ 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");
5394
5485
  return offer;
5395
5486
  }
5396
5487
  async function requestNamedSeatInvitation(options) {
@@ -5617,7 +5708,7 @@ async function drainMembershipEffects(db, options, now) {
5617
5708
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !options.stripeSecretKey) throw new TypeError("invalid membership recovery configuration");
5618
5709
  await loadMembershipAuthorityState(db, scope2);
5619
5710
  const matches2 = (row2) => Object.entries(scope2).every(([key3, value]) => row2[key3] === value);
5620
- let expired = 0, pending = 0, complete = 0, more = false;
5711
+ let pending = 0, complete = 0, more = false;
5621
5712
  for (const status2 of ["payment_pending", "payment_processing", "payment_canceling"]) {
5622
5713
  const seats = (await db.query({ networkSeat: { $: { where: { ...scope2, status: status2 }, order: { updatedAt: "asc" }, limit: limit + 1 } } })).networkSeat ?? [];
5623
5714
  more ||= seats.length > limit;
@@ -5635,26 +5726,6 @@ async function drainMembershipEffects(db, options, now) {
5635
5726
  }
5636
5727
  }
5637
5728
  }
5638
- for (const status2 of ["renewal_pending", "pending_acceptance"]) {
5639
- const seats = (await db.query({ networkSeat: { $: { where: { ...scope2, status: status2 }, order: { acceptanceEndsAt: "asc" }, limit: limit + 1 } } })).networkSeat ?? [];
5640
- more ||= seats.length > limit;
5641
- for (const seat of seats.slice(0, limit)) {
5642
- if (!matches2(seat)) throw new Error("membership recovery scope mismatch");
5643
- if (seat.acceptedAt !== void 0 || now < Number(seat.acceptanceEndsAt)) continue;
5644
- try {
5645
- await requestNamedSeatRefund(db, {
5646
- ...scope2,
5647
- applicationId: String(seat.applicationId),
5648
- skuId: String(seat.skuId),
5649
- seatId: String(seat.id),
5650
- reason: "expired"
5651
- }, now);
5652
- expired++;
5653
- } catch {
5654
- pending++;
5655
- }
5656
- }
5657
- }
5658
5729
  const effects = (await db.query({ membershipEffect: { $: { where: {
5659
5730
  chapterId: scope2.chapterId,
5660
5731
  environment: scope2.environment,
@@ -5716,7 +5787,7 @@ async function drainMembershipEffects(db, options, now) {
5716
5787
  pending++;
5717
5788
  }
5718
5789
  }
5719
- return { complete, pending, expired, more };
5790
+ return { complete, pending, more };
5720
5791
  }
5721
5792
 
5722
5793
  // src/schema-membership-offers.ts
@@ -9500,6 +9571,7 @@ export {
9500
9571
  planMemberSystemEventReplay,
9501
9572
  planMemberSystemMigration,
9502
9573
  planPrivateRoleMigration,
9574
+ previewMembershipCommercialOffer,
9503
9575
  projectApplicant,
9504
9576
  projectBuilderLibraryDelivery,
9505
9577
  projectMemberContent,
@@ -9532,6 +9604,7 @@ export {
9532
9604
  requestMembershipChapterEffect,
9533
9605
  requestMembershipCheckoutBinding,
9534
9606
  requestMembershipCommercialOffer,
9607
+ requestMembershipOfferPreview,
9535
9608
  requestMembershipPayment,
9536
9609
  requestMembershipRefund,
9537
9610
  requestMembershipRenewal,
@@ -9543,6 +9616,7 @@ export {
9543
9616
  requestNamedSeatDecline,
9544
9617
  requestNamedSeatInvitation,
9545
9618
  requestNamedSeatOffer,
9619
+ requestNamedSeatOperatorRefund,
9546
9620
  requestNamedSeatPayment,
9547
9621
  requestNamedSeatRefund2 as requestNamedSeatRefund,
9548
9622
  requestNamedSeatReservation,
@@ -9560,6 +9634,7 @@ export {
9560
9634
  resolveSignupControl,
9561
9635
  resolveTiers,
9562
9636
  roleFromClaim,
9637
+ seatPurchaseDeadline,
9563
9638
  sendTemplated,
9564
9639
  sharedPersonInput,
9565
9640
  sharedRecordFromCrm,