@odla-ai/chapter 0.49.0 → 0.50.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.cjs CHANGED
@@ -176,6 +176,7 @@ __export(src_exports, {
176
176
  planMemberSystemEventReplay: () => planMemberSystemEventReplay,
177
177
  planMemberSystemMigration: () => planMemberSystemMigration,
178
178
  planPrivateRoleMigration: () => planPrivateRoleMigration,
179
+ previewMembershipCommercialOffer: () => previewMembershipCommercialOffer,
179
180
  projectApplicant: () => projectApplicant,
180
181
  projectBuilderLibraryDelivery: () => projectBuilderLibraryDelivery,
181
182
  projectMemberContent: () => projectMemberContent,
@@ -208,6 +209,7 @@ __export(src_exports, {
208
209
  requestMembershipChapterEffect: () => requestMembershipChapterEffect,
209
210
  requestMembershipCheckoutBinding: () => requestMembershipCheckoutBinding,
210
211
  requestMembershipCommercialOffer: () => requestMembershipCommercialOffer,
212
+ requestMembershipOfferPreview: () => requestMembershipOfferPreview,
211
213
  requestMembershipPayment: () => requestMembershipPayment,
212
214
  requestMembershipRefund: () => requestMembershipRefund,
213
215
  requestMembershipRenewal: () => requestMembershipRenewal,
@@ -219,6 +221,7 @@ __export(src_exports, {
219
221
  requestNamedSeatDecline: () => requestNamedSeatDecline,
220
222
  requestNamedSeatInvitation: () => requestNamedSeatInvitation,
221
223
  requestNamedSeatOffer: () => requestNamedSeatOffer,
224
+ requestNamedSeatOperatorRefund: () => requestNamedSeatOperatorRefund,
222
225
  requestNamedSeatPayment: () => requestNamedSeatPayment,
223
226
  requestNamedSeatRefund: () => requestNamedSeatRefund2,
224
227
  requestNamedSeatReservation: () => requestNamedSeatReservation,
@@ -1713,6 +1716,7 @@ var DEFAULT_ADMIN_COPY = {
1713
1716
  },
1714
1717
  records: {
1715
1718
  workflowMissing: "This record is not linked to an application workflow.",
1719
+ invitation: "Invitation",
1716
1720
  comms: "Comms",
1717
1721
  commsHistory: "Comms history",
1718
1722
  scheduling: "Scheduling",
@@ -1797,7 +1801,9 @@ var DEFAULT_JOIN_COPY = {
1797
1801
  processing: "Processing\u2026",
1798
1802
  payAndContinue: "Pay and continue",
1799
1803
  incomplete: "The payment did not complete. Please try again.",
1800
- consent: "I have read and agree to the pricing and refund policy above."
1804
+ consent: "I have read and agree to the pricing and refund policy above.",
1805
+ foundingMember: "Founding member rate",
1806
+ foundingDiscount: "Founding discount"
1801
1807
  },
1802
1808
  done: {
1803
1809
  label: "You're booked",
@@ -3354,7 +3360,7 @@ function findTier(tiers2, tierId) {
3354
3360
  if (!tierId) return tiers2.length === 1 ? tiers2[0] : null;
3355
3361
  return tiers2.find((tier) => tier.id === tierId) ?? null;
3356
3362
  }
3357
- function joinConfigTiers(tiers2, defaults = {}) {
3363
+ function joinConfigTiers(tiers2, defaults = {}, founding = /* @__PURE__ */ new Map()) {
3358
3364
  return tiers2.map((tier) => ({
3359
3365
  id: tier.id,
3360
3366
  name: tier.name,
@@ -3362,7 +3368,8 @@ function joinConfigTiers(tiers2, defaults = {}) {
3362
3368
  currency: tier.currency ?? defaults.currency ?? "usd",
3363
3369
  interval: tier.interval ?? defaults.interval ?? "year",
3364
3370
  blurb: tier.blurb ?? "",
3365
- free: tierIsFree(tier)
3371
+ free: tierIsFree(tier),
3372
+ ...founding.has(tier.id) ? { founding: founding.get(tier.id) } : {}
3366
3373
  }));
3367
3374
  }
3368
3375
 
@@ -3648,24 +3655,63 @@ async function verifyIssuedMembershipOffer(value, expected) {
3648
3655
  if (!value || typeof value !== "object" || Array.isArray(value)) return reject("invalid_offer_snapshot");
3649
3656
  return restore({ ...value, approvedFoundingCount: 0 }, expected);
3650
3657
  }
3651
- async function issueMembershipCommercialOffer(db, input) {
3652
- validate(input);
3653
- const stateId = `${input.chapterId}:${input.environment}`;
3654
- const state = await load(db, "membershipAuthorityState", stateId);
3658
+ async function previewMembershipCommercialOffer(db, input) {
3659
+ for (const key3 of ["appId", "chapterId", "environment", "runtime", "skuId"]) {
3660
+ if (typeof input[key3] !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.:-]{1,159}$/.test(input[key3])) reject("invalid_offer_identity");
3661
+ }
3662
+ if (!Number.isSafeInteger(input.now) || input.now < 0) reject("invalid_offer_window");
3663
+ const state = await authoritativeState(db, input);
3664
+ const { offering, policy } = await currentPolicy(db, input, false, state);
3665
+ const approvedFoundingCount = policy ? state.cohortCounts[policy.cohortLabel] ?? 0 : 0;
3666
+ if (!Number.isSafeInteger(approvedFoundingCount) || Number(approvedFoundingCount) < 0) reject("invalid_cohort_count");
3667
+ const contract = createMembershipCommercialOffer({
3668
+ offering,
3669
+ discountPolicy: policy,
3670
+ approvedFoundingCount: Number(approvedFoundingCount),
3671
+ now: input.now,
3672
+ couponId: await couponId(input, policy?.id)
3673
+ });
3674
+ const discount = contract.billingTerms.discount;
3675
+ return {
3676
+ skuId: input.skuId,
3677
+ offeringVersionId: offering.id,
3678
+ basePriceCents: offering.basePriceCents,
3679
+ currency: offering.currency,
3680
+ interval: offering.interval,
3681
+ founding: contract.founding,
3682
+ ...contract.founding && contract.discountPolicy ? { cohortLabel: contract.discountPolicy.cohortLabel } : {},
3683
+ dueTodayCents: contract.dueTodayCents,
3684
+ renewalCents: contract.renewalCents,
3685
+ ...discount ? { discountDuration: discount.duration === "once" ? "first_term" : "while_active" } : {},
3686
+ ...contract.initialTermEndsAt !== void 0 ? { initialTermEndsAt: contract.initialTermEndsAt } : {}
3687
+ };
3688
+ }
3689
+ async function authoritativeState(db, input) {
3690
+ const state = await load(db, "membershipAuthorityState", `${input.chapterId}:${input.environment}`);
3655
3691
  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");
3656
- const existing = await load(db, "membershipCheckoutOffer", input.offerId);
3657
- if (existing) return restore(existing, input);
3692
+ return state;
3693
+ }
3694
+ async function currentPolicy(db, input, restartAware, state) {
3658
3695
  const rows3 = await db.query({
3659
3696
  membershipOfferingVersion: { $: { where: { chapterId: input.chapterId }, limit: 200 } },
3660
3697
  discountPolicyVersion: { $: { where: { chapterId: input.chapterId }, limit: 200 } }
3661
3698
  });
3662
3699
  if (!Array.isArray(rows3.membershipOfferingVersion) || !Array.isArray(rows3.discountPolicyVersion) || rows3.membershipOfferingVersion.length >= 200 || rows3.discountPolicyVersion.length >= 200) return reject("policy_history_incomplete");
3663
- 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));
3664
- 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);
3700
+ 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));
3701
+ 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);
3665
3702
  const offering = offerings.sort((a, b) => b.version - a.version)[0];
3666
- const restart = await load(db, "membershipRestart", (await membershipOfferIdForRequest({ ...input, requestId: "checkout_binding" })).replace(/^offer_/, "membership_"));
3703
+ const restart = restartAware && input.applicationId ? await load(db, "membershipRestart", (await membershipOfferIdForRequest({ ...input, applicationId: input.applicationId, requestId: "checkout_binding" })).replace(/^offer_/, "membership_")) : void 0;
3667
3704
  const policy = restart ? void 0 : policies.sort((a, b) => b.version - a.version)[0];
3668
3705
  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");
3706
+ return { offering, policy };
3707
+ }
3708
+ async function issueMembershipCommercialOffer(db, input) {
3709
+ validate(input);
3710
+ const stateId = `${input.chapterId}:${input.environment}`;
3711
+ const state = await authoritativeState(db, input);
3712
+ const existing = await load(db, "membershipCheckoutOffer", input.offerId);
3713
+ if (existing) return restore(existing, input);
3714
+ const { offering, policy } = await currentPolicy(db, input, true, state);
3669
3715
  const approvedFoundingCount = policy ? state.cohortCounts[policy.cohortLabel] ?? 0 : 0;
3670
3716
  if (!Number.isSafeInteger(approvedFoundingCount) || Number(approvedFoundingCount) < 0) reject("invalid_cohort_count");
3671
3717
  const contract = createMembershipCommercialOffer({
@@ -4852,7 +4898,7 @@ async function confirmNamedMembershipSeatPayment(db, input, now) {
4852
4898
  membershipUpdate("networkSeat", input.seatId, {
4853
4899
  status: status2,
4854
4900
  purchasedAt: paidAt,
4855
- acceptanceEndsAt: Math.min(paidAt + 30 * 864e5, Number(member.termEndsAt)),
4901
+ acceptanceEndsAt: Number(member.termEndsAt),
4856
4902
  termStartedAt: paidAt,
4857
4903
  termEndsAt: member.termEndsAt,
4858
4904
  paymentEventId: input.eventId,
@@ -4900,7 +4946,7 @@ async function completeNamedSeatRenewal(db, seatId, secretKey, now) {
4900
4946
  if (seat.status !== "renewal_pending" || !seat.purchasedAt) membershipFail("named_seat_renewal_not_ready");
4901
4947
  const member = await membershipLoad(db, "networkMembership", target.primaryMembershipId);
4902
4948
  requireMembershipScope(member, target);
4903
- 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");
4949
+ 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");
4904
4950
  const itemId = await attachNamedSeatStripeRenewal(secretKey, target);
4905
4951
  const entitlementId = `${seatId}:entitlement`, effectId = `${seatId}:attach-renewal`;
4906
4952
  try {
@@ -4975,7 +5021,7 @@ async function recordRecipient(db, input, now, approve) {
4975
5021
  requireMembershipScope(entitlement, input);
4976
5022
  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");
4977
5023
  if (seat.acceptedAt === void 0) {
4978
- if (approve || now >= Number(seat.acceptanceEndsAt) || normalizeChapterEmail(input.verifiedRecipientEmail) !== seat.recipientEmailNormalized) {
5024
+ if (approve || normalizeChapterEmail(input.verifiedRecipientEmail) !== seat.recipientEmailNormalized) {
4979
5025
  membershipFail("named_seat_recipient_verification_required");
4980
5026
  }
4981
5027
  } else if (seat.recipientUserId !== input.recipientUserId || seat.recipientApplicationId !== input.recipientApplicationId) {
@@ -5088,7 +5134,7 @@ async function requestNamedSeatRefund(db, input, now) {
5088
5134
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
5089
5135
  requireMembershipScope(member, input);
5090
5136
  if (seat.status === "refunded" || seat.status === "refund_pending") return;
5091
- 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");
5137
+ 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");
5092
5138
  const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
5093
5139
  await db.transact([
5094
5140
  membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
@@ -5160,6 +5206,19 @@ async function confirmNamedSeatProviderRefund(db, input, now) {
5160
5206
  await requestNamedSeatRefund(db, { ...input, reason: "provider_refund" }, now);
5161
5207
  }
5162
5208
 
5209
+ // src/membership-seat-operator-refund.ts
5210
+ async function refundNamedSeatByOperator(db, input, stripeSecretKey, now) {
5211
+ const seat = await membershipLoad(db, "networkSeat", input.seatId);
5212
+ requireMembershipScope(seat, input);
5213
+ await requestNamedSeatRefund(db, { ...input, reason: "operator" }, now);
5214
+ await completeNamedSeatRefund(db, input.seatId, stripeSecretKey, now);
5215
+ const refunded = await membershipLoad(db, "networkSeat", input.seatId);
5216
+ if (refunded?.status !== "refunded" || !Number.isSafeInteger(refunded.refundedCents) || Number(refunded.refundedCents) <= 0) {
5217
+ throw new Error("named seat refund unconfirmed");
5218
+ }
5219
+ return { seatId: String(seat.id), status: "refunded", refundedCents: Number(refunded.refundedCents), subscriptionCanceled: false };
5220
+ }
5221
+
5163
5222
  // src/membership-seat-offer.ts
5164
5223
  async function namedSeatOfferDigest(offer) {
5165
5224
  const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([
@@ -5172,7 +5231,7 @@ async function namedSeatOfferDigest(offer) {
5172
5231
  "named-only",
5173
5232
  "full-upfront",
5174
5233
  "auto-renew-coterm",
5175
- "30-day-unaccepted-full-refund"
5234
+ "no-automatic-refund"
5176
5235
  ])));
5177
5236
  return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
5178
5237
  }
@@ -5212,7 +5271,7 @@ async function readNamedSeatInvitation(db, input, now) {
5212
5271
  await loadMembershipAuthorityState(db, target);
5213
5272
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
5214
5273
  requireMembershipScope(member, target);
5215
- 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");
5274
+ 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");
5216
5275
  return {
5217
5276
  seatId: input.seatId,
5218
5277
  primaryApplicationId: target.applicationId,
@@ -5242,7 +5301,7 @@ async function readMembershipChapterEffect(db, input, now) {
5242
5301
  requireMembershipScope(member, input);
5243
5302
  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");
5244
5303
  let applicable;
5245
- 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));
5304
+ 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));
5246
5305
  else if (input.kind === "revoke_named_recipient_access") applicable = ["refund_pending", "refunded"].includes(String(seat.status));
5247
5306
  else applicable = (await readNamedSeatAccess(db, {
5248
5307
  ...input,
@@ -5271,6 +5330,7 @@ var MEMBERSHIP_SEAT_ACTION_FIELDS = {
5271
5330
  "seat-reservations": ["recipientName", "recipientEmail", "purchaserEmail", "purchaserUserId"],
5272
5331
  "seat-payments": ["seatId", "eventId"],
5273
5332
  "seat-refunds": ["seatId", "eventId"],
5333
+ "seat-operator-refund": ["seatId"],
5274
5334
  "seat-accept": recipient,
5275
5335
  "seat-approve": recipient,
5276
5336
  "seat-decline": recipient,
@@ -5339,6 +5399,7 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
5339
5399
  if (refunded?.status !== "refunded" || refunded.refundedCents !== seat.amountCents) throw new Error("named seat refund unconfirmed");
5340
5400
  return { seatId: seat.id, status: "refunded", refundedCents: refunded.refundedCents, subscriptionCanceled: false };
5341
5401
  }
5402
+ if (action === "seat-operator-refund") return refundNamedSeatByOperator(db, { ...target, seatId: String(input.seatId) }, secret, now);
5342
5403
  if (action === "seat-checkouts") return prepareNamedMembershipSeatPayment(db, {
5343
5404
  ...target,
5344
5405
  priceId: peer.seatPriceId,
@@ -5427,7 +5488,7 @@ function createMembershipAuthorityHandler(options) {
5427
5488
  if (req.method !== "POST") return respond({ error: "method_not_allowed" }, 405);
5428
5489
  const sender = req.headers.get(import_db.FEDERATION_HEADERS.sender);
5429
5490
  const action = url.pathname.split("/").at(-1);
5430
- if (!["offers", "checkouts", "payments", "approvals", "refunds", "renewals", "access", "restarts", "cancel-renewal", ...Object.keys(MEMBERSHIP_SEAT_ACTION_FIELDS)].includes(action ?? "")) return respond({ error: "unauthorized" }, 401);
5491
+ 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);
5431
5492
  const peer = peers.find((candidate) => candidate.appId === sender && url.pathname === `${MEMBERSHIP_AUTHORITY_PATH}/${candidate.runtime}/${action}` && !url.search);
5432
5493
  if (!peer) return respond({ error: "unauthorized" }, 401);
5433
5494
  let body;
@@ -5449,13 +5510,23 @@ function createMembershipAuthorityHandler(options) {
5449
5510
  let input;
5450
5511
  try {
5451
5512
  input = JSON.parse(verified.body);
5452
- 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] ?? []];
5513
+ 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] ?? []];
5453
5514
  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])))) {
5454
5515
  throw new TypeError("invalid request");
5455
5516
  }
5456
5517
  } catch {
5457
5518
  return respond({ error: "invalid_offer_request" }, 400);
5458
5519
  }
5520
+ if (action === "offer-preview") {
5521
+ return respond({ ok: true, preview: await previewMembershipCommercialOffer(db, {
5522
+ appId: peer.appId,
5523
+ chapterId: peer.chapterId,
5524
+ environment: peer.environment,
5525
+ runtime: peer.runtime,
5526
+ skuId: String(input.skuId),
5527
+ now: options.now?.() ?? Date.now()
5528
+ }) });
5529
+ }
5459
5530
  if (Object.hasOwn(MEMBERSHIP_SEAT_ACTION_FIELDS, action)) {
5460
5531
  return respond({ ok: true, seat: await handleMembershipSeatAction(
5461
5532
  action,
@@ -5606,6 +5677,12 @@ async function requestMembershipCommercialOffer(options) {
5606
5677
  lifetimeMs: 6e4
5607
5678
  });
5608
5679
  }
5680
+ async function requestMembershipOfferPreview(options) {
5681
+ const result = await requestAuthority(options, "offer-preview", { skuId: options.skuId });
5682
+ const preview = result.preview;
5683
+ 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");
5684
+ return preview;
5685
+ }
5609
5686
  async function requestMembershipCheckoutBinding(options) {
5610
5687
  const result = await requestAuthority(options, "checkouts", {
5611
5688
  applicationId: options.applicationId,
@@ -5644,6 +5721,12 @@ async function requestNamedSeatDecline(options) {
5644
5721
  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");
5645
5722
  return seat;
5646
5723
  }
5724
+ async function requestNamedSeatOperatorRefund(options) {
5725
+ const result = await requestAuthority(options, "seat-operator-refund", { applicationId: options.applicationId, skuId: options.skuId, seatId: options.seatId });
5726
+ const seat = result.seat;
5727
+ 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");
5728
+ return seat;
5729
+ }
5647
5730
  async function requestNamedSeatReservation(options) {
5648
5731
  const result = await requestAuthority(options, "seat-reservations", {
5649
5732
  applicationId: options.applicationId,
@@ -5888,7 +5971,7 @@ async function drainMembershipEffects(db, options, now) {
5888
5971
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !options.stripeSecretKey) throw new TypeError("invalid membership recovery configuration");
5889
5972
  await loadMembershipAuthorityState(db, scope2);
5890
5973
  const matches2 = (row2) => Object.entries(scope2).every(([key3, value]) => row2[key3] === value);
5891
- let expired = 0, pending = 0, complete = 0, more = false;
5974
+ let pending = 0, complete = 0, more = false;
5892
5975
  for (const status2 of ["payment_pending", "payment_processing", "payment_canceling"]) {
5893
5976
  const seats = (await db.query({ networkSeat: { $: { where: { ...scope2, status: status2 }, order: { updatedAt: "asc" }, limit: limit + 1 } } })).networkSeat ?? [];
5894
5977
  more ||= seats.length > limit;
@@ -5906,26 +5989,6 @@ async function drainMembershipEffects(db, options, now) {
5906
5989
  }
5907
5990
  }
5908
5991
  }
5909
- for (const status2 of ["renewal_pending", "pending_acceptance"]) {
5910
- const seats = (await db.query({ networkSeat: { $: { where: { ...scope2, status: status2 }, order: { acceptanceEndsAt: "asc" }, limit: limit + 1 } } })).networkSeat ?? [];
5911
- more ||= seats.length > limit;
5912
- for (const seat of seats.slice(0, limit)) {
5913
- if (!matches2(seat)) throw new Error("membership recovery scope mismatch");
5914
- if (seat.acceptedAt !== void 0 || now < Number(seat.acceptanceEndsAt)) continue;
5915
- try {
5916
- await requestNamedSeatRefund(db, {
5917
- ...scope2,
5918
- applicationId: String(seat.applicationId),
5919
- skuId: String(seat.skuId),
5920
- seatId: String(seat.id),
5921
- reason: "expired"
5922
- }, now);
5923
- expired++;
5924
- } catch {
5925
- pending++;
5926
- }
5927
- }
5928
- }
5929
5992
  const effects = (await db.query({ membershipEffect: { $: { where: {
5930
5993
  chapterId: scope2.chapterId,
5931
5994
  environment: scope2.environment,
@@ -5987,7 +6050,7 @@ async function drainMembershipEffects(db, options, now) {
5987
6050
  pending++;
5988
6051
  }
5989
6052
  }
5990
- return { complete, pending, expired, more };
6053
+ return { complete, pending, more };
5991
6054
  }
5992
6055
 
5993
6056
  // src/schema-membership-offers.ts