@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.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
 
@@ -3377,24 +3381,63 @@ async function verifyIssuedMembershipOffer(value, expected) {
3377
3381
  if (!value || typeof value !== "object" || Array.isArray(value)) return reject("invalid_offer_snapshot");
3378
3382
  return restore({ ...value, approvedFoundingCount: 0 }, expected);
3379
3383
  }
3380
- async function issueMembershipCommercialOffer(db, input) {
3381
- validate(input);
3382
- const stateId = `${input.chapterId}:${input.environment}`;
3383
- const state = await load(db, "membershipAuthorityState", stateId);
3384
+ async function previewMembershipCommercialOffer(db, input) {
3385
+ for (const key3 of ["appId", "chapterId", "environment", "runtime", "skuId"]) {
3386
+ if (typeof input[key3] !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.:-]{1,159}$/.test(input[key3])) reject("invalid_offer_identity");
3387
+ }
3388
+ if (!Number.isSafeInteger(input.now) || input.now < 0) reject("invalid_offer_window");
3389
+ const state = await authoritativeState(db, input);
3390
+ const { offering, policy } = await currentPolicy(db, input, false, state);
3391
+ const approvedFoundingCount = policy ? state.cohortCounts[policy.cohortLabel] ?? 0 : 0;
3392
+ if (!Number.isSafeInteger(approvedFoundingCount) || Number(approvedFoundingCount) < 0) reject("invalid_cohort_count");
3393
+ const contract = createMembershipCommercialOffer({
3394
+ offering,
3395
+ discountPolicy: policy,
3396
+ approvedFoundingCount: Number(approvedFoundingCount),
3397
+ now: input.now,
3398
+ couponId: await couponId(input, policy?.id)
3399
+ });
3400
+ const discount = contract.billingTerms.discount;
3401
+ return {
3402
+ skuId: input.skuId,
3403
+ offeringVersionId: offering.id,
3404
+ basePriceCents: offering.basePriceCents,
3405
+ currency: offering.currency,
3406
+ interval: offering.interval,
3407
+ founding: contract.founding,
3408
+ ...contract.founding && contract.discountPolicy ? { cohortLabel: contract.discountPolicy.cohortLabel } : {},
3409
+ dueTodayCents: contract.dueTodayCents,
3410
+ renewalCents: contract.renewalCents,
3411
+ ...discount ? { discountDuration: discount.duration === "once" ? "first_term" : "while_active" } : {},
3412
+ ...contract.initialTermEndsAt !== void 0 ? { initialTermEndsAt: contract.initialTermEndsAt } : {}
3413
+ };
3414
+ }
3415
+ async function authoritativeState(db, input) {
3416
+ const state = await load(db, "membershipAuthorityState", `${input.chapterId}:${input.environment}`);
3384
3417
  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);
3418
+ return state;
3419
+ }
3420
+ async function currentPolicy(db, input, restartAware, state) {
3387
3421
  const rows3 = await db.query({
3388
3422
  membershipOfferingVersion: { $: { where: { chapterId: input.chapterId }, limit: 200 } },
3389
3423
  discountPolicyVersion: { $: { where: { chapterId: input.chapterId }, limit: 200 } }
3390
3424
  });
3391
3425
  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);
3426
+ 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));
3427
+ 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
3428
  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_"));
3429
+ const restart = restartAware && input.applicationId ? await load(db, "membershipRestart", (await membershipOfferIdForRequest({ ...input, applicationId: input.applicationId, requestId: "checkout_binding" })).replace(/^offer_/, "membership_")) : void 0;
3396
3430
  const policy = restart ? void 0 : policies.sort((a, b) => b.version - a.version)[0];
3397
3431
  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");
3432
+ return { offering, policy };
3433
+ }
3434
+ async function issueMembershipCommercialOffer(db, input) {
3435
+ validate(input);
3436
+ const stateId = `${input.chapterId}:${input.environment}`;
3437
+ const state = await authoritativeState(db, input);
3438
+ const existing = await load(db, "membershipCheckoutOffer", input.offerId);
3439
+ if (existing) return restore(existing, input);
3440
+ const { offering, policy } = await currentPolicy(db, input, true, state);
3398
3441
  const approvedFoundingCount = policy ? state.cohortCounts[policy.cohortLabel] ?? 0 : 0;
3399
3442
  if (!Number.isSafeInteger(approvedFoundingCount) || Number(approvedFoundingCount) < 0) reject("invalid_cohort_count");
3400
3443
  const contract = createMembershipCommercialOffer({
@@ -4581,7 +4624,7 @@ async function confirmNamedMembershipSeatPayment(db, input, now) {
4581
4624
  membershipUpdate("networkSeat", input.seatId, {
4582
4625
  status: status2,
4583
4626
  purchasedAt: paidAt,
4584
- acceptanceEndsAt: Math.min(paidAt + 30 * 864e5, Number(member.termEndsAt)),
4627
+ acceptanceEndsAt: Number(member.termEndsAt),
4585
4628
  termStartedAt: paidAt,
4586
4629
  termEndsAt: member.termEndsAt,
4587
4630
  paymentEventId: input.eventId,
@@ -4629,7 +4672,7 @@ async function completeNamedSeatRenewal(db, seatId, secretKey, now) {
4629
4672
  if (seat.status !== "renewal_pending" || !seat.purchasedAt) membershipFail("named_seat_renewal_not_ready");
4630
4673
  const member = await membershipLoad(db, "networkMembership", target.primaryMembershipId);
4631
4674
  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");
4675
+ 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
4676
  const itemId = await attachNamedSeatStripeRenewal(secretKey, target);
4634
4677
  const entitlementId = `${seatId}:entitlement`, effectId = `${seatId}:attach-renewal`;
4635
4678
  try {
@@ -4704,7 +4747,7 @@ async function recordRecipient(db, input, now, approve) {
4704
4747
  requireMembershipScope(entitlement, input);
4705
4748
  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
4749
  if (seat.acceptedAt === void 0) {
4707
- if (approve || now >= Number(seat.acceptanceEndsAt) || normalizeChapterEmail(input.verifiedRecipientEmail) !== seat.recipientEmailNormalized) {
4750
+ if (approve || normalizeChapterEmail(input.verifiedRecipientEmail) !== seat.recipientEmailNormalized) {
4708
4751
  membershipFail("named_seat_recipient_verification_required");
4709
4752
  }
4710
4753
  } else if (seat.recipientUserId !== input.recipientUserId || seat.recipientApplicationId !== input.recipientApplicationId) {
@@ -4817,7 +4860,7 @@ async function requestNamedSeatRefund(db, input, now) {
4817
4860
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
4818
4861
  requireMembershipScope(member, input);
4819
4862
  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");
4863
+ 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
4864
  const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
4822
4865
  await db.transact([
4823
4866
  membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
@@ -4889,6 +4932,19 @@ async function confirmNamedSeatProviderRefund(db, input, now) {
4889
4932
  await requestNamedSeatRefund(db, { ...input, reason: "provider_refund" }, now);
4890
4933
  }
4891
4934
 
4935
+ // src/membership-seat-operator-refund.ts
4936
+ async function refundNamedSeatByOperator(db, input, stripeSecretKey, now) {
4937
+ const seat = await membershipLoad(db, "networkSeat", input.seatId);
4938
+ requireMembershipScope(seat, input);
4939
+ await requestNamedSeatRefund(db, { ...input, reason: "operator" }, now);
4940
+ await completeNamedSeatRefund(db, input.seatId, stripeSecretKey, now);
4941
+ const refunded = await membershipLoad(db, "networkSeat", input.seatId);
4942
+ if (refunded?.status !== "refunded" || !Number.isSafeInteger(refunded.refundedCents) || Number(refunded.refundedCents) <= 0) {
4943
+ throw new Error("named seat refund unconfirmed");
4944
+ }
4945
+ return { seatId: String(seat.id), status: "refunded", refundedCents: Number(refunded.refundedCents), subscriptionCanceled: false };
4946
+ }
4947
+
4892
4948
  // src/membership-seat-offer.ts
4893
4949
  async function namedSeatOfferDigest(offer) {
4894
4950
  const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([
@@ -4901,7 +4957,7 @@ async function namedSeatOfferDigest(offer) {
4901
4957
  "named-only",
4902
4958
  "full-upfront",
4903
4959
  "auto-renew-coterm",
4904
- "30-day-unaccepted-full-refund"
4960
+ "no-automatic-refund"
4905
4961
  ])));
4906
4962
  return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
4907
4963
  }
@@ -4941,7 +4997,7 @@ async function readNamedSeatInvitation(db, input, now) {
4941
4997
  await loadMembershipAuthorityState(db, target);
4942
4998
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
4943
4999
  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");
5000
+ 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
5001
  return {
4946
5002
  seatId: input.seatId,
4947
5003
  primaryApplicationId: target.applicationId,
@@ -4971,7 +5027,7 @@ async function readMembershipChapterEffect(db, input, now) {
4971
5027
  requireMembershipScope(member, input);
4972
5028
  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
5029
  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));
5030
+ 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
5031
  else if (input.kind === "revoke_named_recipient_access") applicable = ["refund_pending", "refunded"].includes(String(seat.status));
4976
5032
  else applicable = (await readNamedSeatAccess(db, {
4977
5033
  ...input,
@@ -5000,6 +5056,7 @@ var MEMBERSHIP_SEAT_ACTION_FIELDS = {
5000
5056
  "seat-reservations": ["recipientName", "recipientEmail", "purchaserEmail", "purchaserUserId"],
5001
5057
  "seat-payments": ["seatId", "eventId"],
5002
5058
  "seat-refunds": ["seatId", "eventId"],
5059
+ "seat-operator-refund": ["seatId"],
5003
5060
  "seat-accept": recipient,
5004
5061
  "seat-approve": recipient,
5005
5062
  "seat-decline": recipient,
@@ -5068,6 +5125,7 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
5068
5125
  if (refunded?.status !== "refunded" || refunded.refundedCents !== seat.amountCents) throw new Error("named seat refund unconfirmed");
5069
5126
  return { seatId: seat.id, status: "refunded", refundedCents: refunded.refundedCents, subscriptionCanceled: false };
5070
5127
  }
5128
+ if (action === "seat-operator-refund") return refundNamedSeatByOperator(db, { ...target, seatId: String(input.seatId) }, secret, now);
5071
5129
  if (action === "seat-checkouts") return prepareNamedMembershipSeatPayment(db, {
5072
5130
  ...target,
5073
5131
  priceId: peer.seatPriceId,
@@ -5156,7 +5214,7 @@ function createMembershipAuthorityHandler(options) {
5156
5214
  if (req.method !== "POST") return respond({ error: "method_not_allowed" }, 405);
5157
5215
  const sender = req.headers.get(FEDERATION_HEADERS.sender);
5158
5216
  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);
5217
+ 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
5218
  const peer = peers.find((candidate) => candidate.appId === sender && url.pathname === `${MEMBERSHIP_AUTHORITY_PATH}/${candidate.runtime}/${action}` && !url.search);
5161
5219
  if (!peer) return respond({ error: "unauthorized" }, 401);
5162
5220
  let body;
@@ -5178,13 +5236,23 @@ function createMembershipAuthorityHandler(options) {
5178
5236
  let input;
5179
5237
  try {
5180
5238
  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] ?? []];
5239
+ 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
5240
  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
5241
  throw new TypeError("invalid request");
5184
5242
  }
5185
5243
  } catch {
5186
5244
  return respond({ error: "invalid_offer_request" }, 400);
5187
5245
  }
5246
+ if (action === "offer-preview") {
5247
+ return respond({ ok: true, preview: await previewMembershipCommercialOffer(db, {
5248
+ appId: peer.appId,
5249
+ chapterId: peer.chapterId,
5250
+ environment: peer.environment,
5251
+ runtime: peer.runtime,
5252
+ skuId: String(input.skuId),
5253
+ now: options.now?.() ?? Date.now()
5254
+ }) });
5255
+ }
5188
5256
  if (Object.hasOwn(MEMBERSHIP_SEAT_ACTION_FIELDS, action)) {
5189
5257
  return respond({ ok: true, seat: await handleMembershipSeatAction(
5190
5258
  action,
@@ -5335,6 +5403,12 @@ async function requestMembershipCommercialOffer(options) {
5335
5403
  lifetimeMs: 6e4
5336
5404
  });
5337
5405
  }
5406
+ async function requestMembershipOfferPreview(options) {
5407
+ const result = await requestAuthority(options, "offer-preview", { skuId: options.skuId });
5408
+ const preview = result.preview;
5409
+ 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");
5410
+ return preview;
5411
+ }
5338
5412
  async function requestMembershipCheckoutBinding(options) {
5339
5413
  const result = await requestAuthority(options, "checkouts", {
5340
5414
  applicationId: options.applicationId,
@@ -5373,6 +5447,12 @@ async function requestNamedSeatDecline(options) {
5373
5447
  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
5448
  return seat;
5375
5449
  }
5450
+ async function requestNamedSeatOperatorRefund(options) {
5451
+ const result = await requestAuthority(options, "seat-operator-refund", { applicationId: options.applicationId, skuId: options.skuId, seatId: options.seatId });
5452
+ const seat = result.seat;
5453
+ 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");
5454
+ return seat;
5455
+ }
5376
5456
  async function requestNamedSeatReservation(options) {
5377
5457
  const result = await requestAuthority(options, "seat-reservations", {
5378
5458
  applicationId: options.applicationId,
@@ -5617,7 +5697,7 @@ async function drainMembershipEffects(db, options, now) {
5617
5697
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !options.stripeSecretKey) throw new TypeError("invalid membership recovery configuration");
5618
5698
  await loadMembershipAuthorityState(db, scope2);
5619
5699
  const matches2 = (row2) => Object.entries(scope2).every(([key3, value]) => row2[key3] === value);
5620
- let expired = 0, pending = 0, complete = 0, more = false;
5700
+ let pending = 0, complete = 0, more = false;
5621
5701
  for (const status2 of ["payment_pending", "payment_processing", "payment_canceling"]) {
5622
5702
  const seats = (await db.query({ networkSeat: { $: { where: { ...scope2, status: status2 }, order: { updatedAt: "asc" }, limit: limit + 1 } } })).networkSeat ?? [];
5623
5703
  more ||= seats.length > limit;
@@ -5635,26 +5715,6 @@ async function drainMembershipEffects(db, options, now) {
5635
5715
  }
5636
5716
  }
5637
5717
  }
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
5718
  const effects = (await db.query({ membershipEffect: { $: { where: {
5659
5719
  chapterId: scope2.chapterId,
5660
5720
  environment: scope2.environment,
@@ -5716,7 +5776,7 @@ async function drainMembershipEffects(db, options, now) {
5716
5776
  pending++;
5717
5777
  }
5718
5778
  }
5719
- return { complete, pending, expired, more };
5779
+ return { complete, pending, more };
5720
5780
  }
5721
5781
 
5722
5782
  // src/schema-membership-offers.ts
@@ -9500,6 +9560,7 @@ export {
9500
9560
  planMemberSystemEventReplay,
9501
9561
  planMemberSystemMigration,
9502
9562
  planPrivateRoleMigration,
9563
+ previewMembershipCommercialOffer,
9503
9564
  projectApplicant,
9504
9565
  projectBuilderLibraryDelivery,
9505
9566
  projectMemberContent,
@@ -9532,6 +9593,7 @@ export {
9532
9593
  requestMembershipChapterEffect,
9533
9594
  requestMembershipCheckoutBinding,
9534
9595
  requestMembershipCommercialOffer,
9596
+ requestMembershipOfferPreview,
9535
9597
  requestMembershipPayment,
9536
9598
  requestMembershipRefund,
9537
9599
  requestMembershipRenewal,
@@ -9543,6 +9605,7 @@ export {
9543
9605
  requestNamedSeatDecline,
9544
9606
  requestNamedSeatInvitation,
9545
9607
  requestNamedSeatOffer,
9608
+ requestNamedSeatOperatorRefund,
9546
9609
  requestNamedSeatPayment,
9547
9610
  requestNamedSeatRefund2 as requestNamedSeatRefund,
9548
9611
  requestNamedSeatReservation,