@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.
@@ -329,6 +329,7 @@ function createWorkerContext(options) {
329
329
  crmBase,
330
330
  requirePaymentQuote: options.requirePaymentQuote === true,
331
331
  membershipAuthority: options.membershipAuthority,
332
+ membershipAuthorityOperator: options.membershipAuthorityOperator,
332
333
  getPublicConfig,
333
334
  verifyUser,
334
335
  makeDb,
@@ -1842,24 +1843,63 @@ async function verifyIssuedMembershipOffer(value, expected) {
1842
1843
  if (!value || typeof value !== "object" || Array.isArray(value)) return reject("invalid_offer_snapshot");
1843
1844
  return restore({ ...value, approvedFoundingCount: 0 }, expected);
1844
1845
  }
1845
- async function issueMembershipCommercialOffer(db, input) {
1846
- validate(input);
1847
- const stateId = `${input.chapterId}:${input.environment}`;
1848
- const state = await load(db, "membershipAuthorityState", stateId);
1846
+ async function previewMembershipCommercialOffer(db, input) {
1847
+ for (const key3 of ["appId", "chapterId", "environment", "runtime", "skuId"]) {
1848
+ if (typeof input[key3] !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.:-]{1,159}$/.test(input[key3])) reject("invalid_offer_identity");
1849
+ }
1850
+ if (!Number.isSafeInteger(input.now) || input.now < 0) reject("invalid_offer_window");
1851
+ const state = await authoritativeState(db, input);
1852
+ const { offering, policy } = await currentPolicy(db, input, false, state);
1853
+ const approvedFoundingCount = policy ? state.cohortCounts[policy.cohortLabel] ?? 0 : 0;
1854
+ if (!Number.isSafeInteger(approvedFoundingCount) || Number(approvedFoundingCount) < 0) reject("invalid_cohort_count");
1855
+ const contract = createMembershipCommercialOffer({
1856
+ offering,
1857
+ discountPolicy: policy,
1858
+ approvedFoundingCount: Number(approvedFoundingCount),
1859
+ now: input.now,
1860
+ couponId: await couponId(input, policy?.id)
1861
+ });
1862
+ const discount = contract.billingTerms.discount;
1863
+ return {
1864
+ skuId: input.skuId,
1865
+ offeringVersionId: offering.id,
1866
+ basePriceCents: offering.basePriceCents,
1867
+ currency: offering.currency,
1868
+ interval: offering.interval,
1869
+ founding: contract.founding,
1870
+ ...contract.founding && contract.discountPolicy ? { cohortLabel: contract.discountPolicy.cohortLabel } : {},
1871
+ dueTodayCents: contract.dueTodayCents,
1872
+ renewalCents: contract.renewalCents,
1873
+ ...discount ? { discountDuration: discount.duration === "once" ? "first_term" : "while_active" } : {},
1874
+ ...contract.initialTermEndsAt !== void 0 ? { initialTermEndsAt: contract.initialTermEndsAt } : {}
1875
+ };
1876
+ }
1877
+ async function authoritativeState(db, input) {
1878
+ const state = await load(db, "membershipAuthorityState", `${input.chapterId}:${input.environment}`);
1849
1879
  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");
1850
- const existing = await load(db, "membershipCheckoutOffer", input.offerId);
1851
- if (existing) return restore(existing, input);
1880
+ return state;
1881
+ }
1882
+ async function currentPolicy(db, input, restartAware, state) {
1852
1883
  const rows5 = await db.query({
1853
1884
  membershipOfferingVersion: { $: { where: { chapterId: input.chapterId }, limit: 200 } },
1854
1885
  discountPolicyVersion: { $: { where: { chapterId: input.chapterId }, limit: 200 } }
1855
1886
  });
1856
1887
  if (!Array.isArray(rows5.membershipOfferingVersion) || !Array.isArray(rows5.discountPolicyVersion) || rows5.membershipOfferingVersion.length >= 200 || rows5.discountPolicyVersion.length >= 200) return reject("policy_history_incomplete");
1857
- const offerings = rows5.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));
1858
- const policies = rows5.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);
1888
+ const offerings = rows5.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));
1889
+ const policies = rows5.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);
1859
1890
  const offering = offerings.sort((a, b) => b.version - a.version)[0];
1860
- const restart = await load(db, "membershipRestart", (await membershipOfferIdForRequest({ ...input, requestId: "checkout_binding" })).replace(/^offer_/, "membership_"));
1891
+ const restart = restartAware && input.applicationId ? await load(db, "membershipRestart", (await membershipOfferIdForRequest({ ...input, applicationId: input.applicationId, requestId: "checkout_binding" })).replace(/^offer_/, "membership_")) : void 0;
1861
1892
  const policy = restart ? void 0 : policies.sort((a, b) => b.version - a.version)[0];
1862
1893
  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");
1894
+ return { offering, policy };
1895
+ }
1896
+ async function issueMembershipCommercialOffer(db, input) {
1897
+ validate(input);
1898
+ const stateId = `${input.chapterId}:${input.environment}`;
1899
+ const state = await authoritativeState(db, input);
1900
+ const existing = await load(db, "membershipCheckoutOffer", input.offerId);
1901
+ if (existing) return restore(existing, input);
1902
+ const { offering, policy } = await currentPolicy(db, input, true, state);
1863
1903
  const approvedFoundingCount = policy ? state.cohortCounts[policy.cohortLabel] ?? 0 : 0;
1864
1904
  if (!Number.isSafeInteger(approvedFoundingCount) || Number(approvedFoundingCount) < 0) reject("invalid_cohort_count");
1865
1905
  const contract = createMembershipCommercialOffer({
@@ -2167,7 +2207,7 @@ function findTier(tiers, tierId) {
2167
2207
  if (!tierId) return tiers.length === 1 ? tiers[0] : null;
2168
2208
  return tiers.find((tier) => tier.id === tierId) ?? null;
2169
2209
  }
2170
- function joinConfigTiers(tiers, defaults = {}) {
2210
+ function joinConfigTiers(tiers, defaults = {}, founding = /* @__PURE__ */ new Map()) {
2171
2211
  return tiers.map((tier) => ({
2172
2212
  id: tier.id,
2173
2213
  name: tier.name,
@@ -2175,7 +2215,8 @@ function joinConfigTiers(tiers, defaults = {}) {
2175
2215
  currency: tier.currency ?? defaults.currency ?? "usd",
2176
2216
  interval: tier.interval ?? defaults.interval ?? "year",
2177
2217
  blurb: tier.blurb ?? "",
2178
- free: tierIsFree(tier)
2218
+ free: tierIsFree(tier),
2219
+ ...founding.has(tier.id) ? { founding: founding.get(tier.id) } : {}
2179
2220
  }));
2180
2221
  }
2181
2222
 
@@ -3410,6 +3451,267 @@ var handlePaymentRecovery = async (req, url, env, ctx) => {
3410
3451
  }
3411
3452
  };
3412
3453
 
3454
+ // src/worker-routes-admin-named-seat.ts
3455
+ var PATH = /^\/api\/admin\/applications\/([0-9a-f-]+)\/named-seat(\/refund)?$/;
3456
+ var REFUNDABLE = /* @__PURE__ */ new Set(["renewal_pending", "pending_acceptance", "pending_approval", "pending_primary", "active"]);
3457
+ var handleAdminNamedSeat = async (req, url, env, ctx) => {
3458
+ const match = url.pathname.match(PATH);
3459
+ if (!match) return null;
3460
+ const seats = ctx.membershipAuthority?.namedSeats;
3461
+ if (ctx.chapter.mode !== "chapter" || !seats) return json({ error: "named seats are not configured" }, 404);
3462
+ const refund = Boolean(match[2]);
3463
+ if (req.method !== (refund ? "POST" : "GET")) return json({ error: "method not allowed" }, 405);
3464
+ const raw2 = ctx.makeDb(env), user = await ctx.verifyUser(req, env);
3465
+ if (!user) return json({ error: "unauthorized" }, 401);
3466
+ if (!await ctx.isSuperAdminEmail(raw2, user.email, user.userId)) return json({ error: "super-admin required", code: "super_admin_required" }, 403);
3467
+ const db = raw2, app = await membershipLoad(db, "applications", match[1]);
3468
+ if (!app || !chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME) || app.namedSeatId) return json({ error: "not found" }, 404);
3469
+ const intent = await membershipLoad(db, "subscriptionCheckoutIntents", String(app.id));
3470
+ if (!intent) return json({ error: "paid membership required" }, 409);
3471
+ try {
3472
+ const target = await membershipTargetForIntent(intent, app, env, ctx);
3473
+ const offer = await seats.offer(target, env);
3474
+ const seat = offer.seat ?? null, refundable = Boolean(seat && REFUNDABLE.has(seat.status));
3475
+ if (!refund) {
3476
+ return json({
3477
+ seat,
3478
+ amountCents: offer.amountCents,
3479
+ currency: offer.currency,
3480
+ termEndsAt: offer.termEndsAt,
3481
+ purchaseEndsAt: offer.purchaseEndsAt,
3482
+ refundable
3483
+ });
3484
+ }
3485
+ if (!seat) return json({ error: "no purchased seat", code: "seat_not_found" }, 409);
3486
+ if (seat.status === "refunded") {
3487
+ await closeRecipientApplication(db, env, ctx, seat.seatId);
3488
+ return json({ ok: true, seatId: seat.seatId, status: "refunded", replayed: true });
3489
+ }
3490
+ if (!refundable) return json({ error: `seat is ${seat.status.replaceAll("_", " ")}`, code: "seat_not_refundable" }, 409);
3491
+ const receipt = await seats.operatorRefund({ ...target, seatId: seat.seatId }, env);
3492
+ await closeRecipientApplication(db, env, ctx, seat.seatId);
3493
+ return json({ ok: true, ...receipt });
3494
+ } catch {
3495
+ return json({ error: "named seat service unavailable; retry to confirm" }, 503);
3496
+ }
3497
+ };
3498
+ async function closeRecipientApplication(db, env, ctx, seatId) {
3499
+ const recipient2 = (await db.query({ applications: { $: { where: { namedSeatId: seatId }, limit: 1 } } })).applications?.[0];
3500
+ if (!recipient2 || !chapterBillingApplication(recipient2, ctx.chapter.id, env.ODLA_RUNTIME) || recipient2.status === "refunded") return;
3501
+ const attrs = { status: "refunded", renewalAt: 0, canceled: true };
3502
+ await db.transact(
3503
+ [{ t: "update", ns: "applications", id: String(recipient2.id), attrs }],
3504
+ { mutationId: `verified-refund:${String(recipient2.id)}:${seatId}` }
3505
+ );
3506
+ await syncApplicationToCrm(crmDeps(db, ctx), { app: { ...recipient2, ...attrs }, stage: "refunded" }).catch(() => void 0);
3507
+ }
3508
+
3509
+ // src/membership-seat-refund-stripe.ts
3510
+ import { stripeRequest as stripeRequest4 } from "@odla-ai/stripe";
3511
+ var fail3 = (code = "named_seat_refund_mismatch") => {
3512
+ throw Object.assign(new Error(code), { code });
3513
+ };
3514
+ async function refundNamedSeatStripePayment(secretKey, seat) {
3515
+ if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail3();
3516
+ const call = async (method, path, params, idempotencyKey) => {
3517
+ const result = await stripeRequest4({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
3518
+ if (!result.ok || !result.body) return fail3("named_seat_refund_provider_unavailable");
3519
+ return result.body;
3520
+ };
3521
+ const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
3522
+ const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`);
3523
+ if (payment.id !== seat.providerPaymentIntentId || payment.customer !== seat.providerCustomerId || payment.amount !== seat.amountCents || payment.amount_received !== seat.amountCents || payment.currency !== seat.currency || payment.livemode !== (seat.environment === "prod") || payment.status !== "succeeded" || !Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === value)) fail3();
3524
+ const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
3525
+ if (sub.id !== seat.providerSubscriptionId || sub.customer !== seat.providerCustomerId || sub.livemode !== (seat.environment === "prod") || !Object.entries({ ...stripePartitionMetadata(seat), applicationId: seat.applicationId }).every(([key3, value]) => sub.metadata?.[key3] === value) || sub.items?.has_more !== false || !Array.isArray(sub.items?.data)) fail3();
3526
+ const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
3527
+ if (items.length > 1) fail3();
3528
+ if (items.length && sub.status !== "canceled") {
3529
+ const item = items[0];
3530
+ if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail3();
3531
+ const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
3532
+ if (removed.id !== item.id || removed.deleted !== true) fail3("named_seat_renewal_removal_unconfirmed");
3533
+ }
3534
+ const list = await call("GET", "/v1/refunds", { payment_intent: seat.providerPaymentIntentId, limit: 100 });
3535
+ if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail3("named_seat_refund_history_incomplete");
3536
+ let refundedCents = 0, pendingCents = 0;
3537
+ const refundIds = [];
3538
+ for (const refund of list.data) {
3539
+ if (!/^re_[A-Za-z0-9_]+$/.test(refund.id) || refund.payment_intent !== seat.providerPaymentIntentId || refund.currency !== seat.currency || !Number.isSafeInteger(refund.amount) || refund.amount <= 0 || !["succeeded", "pending", "requires_action", "failed", "canceled"].includes(refund.status)) fail3();
3540
+ if (refund.status === "succeeded") {
3541
+ refundedCents += refund.amount;
3542
+ refundIds.push(refund.id);
3543
+ } else if (refund.status === "pending" || refund.status === "requires_action") pendingCents += refund.amount;
3544
+ }
3545
+ if (refundedCents + pendingCents > seat.amountCents) fail3();
3546
+ if (pendingCents) fail3("named_seat_refund_pending");
3547
+ if (refundedCents < seat.amountCents) {
3548
+ const remaining = seat.amountCents - refundedCents;
3549
+ const refund = await call("POST", "/v1/refunds", {
3550
+ payment_intent: seat.providerPaymentIntentId,
3551
+ amount: remaining,
3552
+ reason: "requested_by_customer",
3553
+ metadata
3554
+ }, `named-seat-refund:${seat.id}:${remaining}`);
3555
+ if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail3();
3556
+ if (refund.status !== "succeeded") fail3("named_seat_refund_pending");
3557
+ refundedCents += remaining;
3558
+ refundIds.push(refund.id);
3559
+ }
3560
+ return { refundedCents, refundIds };
3561
+ }
3562
+
3563
+ // src/membership-seat-refund.ts
3564
+ async function requestNamedSeatRefund(db, input, now) {
3565
+ membershipClock(now);
3566
+ const state = await loadMembershipAuthorityState(db, input);
3567
+ const seat = await membershipLoad(db, "networkSeat", input.seatId);
3568
+ requireMembershipScope(seat, input);
3569
+ const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
3570
+ requireMembershipScope(member, input);
3571
+ if (seat.status === "refunded" || seat.status === "refund_pending") return;
3572
+ 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");
3573
+ const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
3574
+ await db.transact([
3575
+ membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
3576
+ ...entitlement ? [membershipUpdate("networkEntitlement", String(entitlement.id), { status: "closed", revision: Number(entitlement.revision) + 1, updatedAt: now })] : [],
3577
+ membershipUpdate("membershipEffect", `${seat.id}:refund`, {
3578
+ id: `${seat.id}:refund`,
3579
+ chapterId: input.chapterId,
3580
+ environment: input.environment,
3581
+ runtime: input.runtime,
3582
+ membershipId: member.id,
3583
+ seatId: seat.id,
3584
+ kind: "refund_seat",
3585
+ status: "pending",
3586
+ attempts: 0,
3587
+ createdAt: now,
3588
+ updatedAt: now
3589
+ }),
3590
+ ...seat.recipientUserId ? [membershipUpdate("membershipEffect", `${seat.id}:revoke`, {
3591
+ id: `${seat.id}:revoke`,
3592
+ chapterId: input.chapterId,
3593
+ environment: input.environment,
3594
+ runtime: input.runtime,
3595
+ membershipId: member.id,
3596
+ seatId: seat.id,
3597
+ kind: "revoke_named_recipient_access",
3598
+ status: "pending",
3599
+ attempts: 0,
3600
+ createdAt: now,
3601
+ updatedAt: now
3602
+ })] : []
3603
+ ], { mutationId: `named-seat-refund-request:${seat.id}`, guards: [
3604
+ { ns: "networkSeat", id: input.seatId, exists: true, equals: { revision: seat.revision } },
3605
+ { ns: "networkMembership", id: String(member.id), exists: true, equals: { revision: member.revision } },
3606
+ { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
3607
+ ...entitlement ? [{ ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision } }] : []
3608
+ ] });
3609
+ }
3610
+ async function completeNamedSeatRefund(db, seatId, secretKey, now) {
3611
+ membershipClock(now);
3612
+ const seat = await membershipLoad(db, "networkSeat", seatId);
3613
+ if (!seat) return membershipFail("named_seat_not_found");
3614
+ await loadMembershipAuthorityState(db, seat);
3615
+ if (seat.status === "refunded") return;
3616
+ if (seat.status !== "refund_pending") membershipFail("named_seat_refund_not_ready");
3617
+ const result = await refundNamedSeatStripePayment(secretKey, seat);
3618
+ await db.transact([
3619
+ membershipUpdate("networkSeat", seatId, {
3620
+ status: "refunded",
3621
+ refundedCents: result.refundedCents,
3622
+ providerRefundIds: result.refundIds,
3623
+ revision: Number(seat.revision) + 1,
3624
+ updatedAt: now
3625
+ }),
3626
+ membershipUpdate("membershipEffect", `${seatId}:refund`, { status: "complete", updatedAt: now })
3627
+ ], { mutationId: `named-seat-refunded:${seatId}`, guards: [
3628
+ { ns: "networkSeat", id: seatId, exists: true, equals: { status: "refund_pending", revision: seat.revision } }
3629
+ ] });
3630
+ }
3631
+
3632
+ // src/membership-seat-operator-refund.ts
3633
+ async function refundNamedSeatByOperator(db, input, stripeSecretKey, now) {
3634
+ const seat = await membershipLoad(db, "networkSeat", input.seatId);
3635
+ requireMembershipScope(seat, input);
3636
+ await requestNamedSeatRefund(db, { ...input, reason: "operator" }, now);
3637
+ await completeNamedSeatRefund(db, input.seatId, stripeSecretKey, now);
3638
+ const refunded = await membershipLoad(db, "networkSeat", input.seatId);
3639
+ if (refunded?.status !== "refunded" || !Number.isSafeInteger(refunded.refundedCents) || Number(refunded.refundedCents) <= 0) {
3640
+ throw new Error("named seat refund unconfirmed");
3641
+ }
3642
+ return { seatId: String(seat.id), status: "refunded", refundedCents: Number(refunded.refundedCents), subscriptionCanceled: false };
3643
+ }
3644
+
3645
+ // src/worker-routes-admin-membership-seats.ts
3646
+ var LIST = "/api/admin/membership-seats";
3647
+ var ONE = /^\/api\/admin\/membership-seats\/([A-Za-z0-9][A-Za-z0-9_.:-]{2,159})\/refund$/;
3648
+ var PUBLIC = [
3649
+ "id",
3650
+ "appId",
3651
+ "chapterId",
3652
+ "environment",
3653
+ "runtime",
3654
+ "applicationId",
3655
+ "skuId",
3656
+ "status",
3657
+ "amountCents",
3658
+ "currency",
3659
+ "recipientName",
3660
+ "recipientEmailNormalized",
3661
+ "purchaserUserId",
3662
+ "purchasedAt",
3663
+ "acceptedAt",
3664
+ "journeyApprovedAt",
3665
+ "refundedCents",
3666
+ "refundReason",
3667
+ "termEndsAt",
3668
+ "updatedAt"
3669
+ ];
3670
+ var project = (row2) => Object.fromEntries(PUBLIC.filter((key3) => row2[key3] !== void 0).map((key3) => [key3, row2[key3]]));
3671
+ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
3672
+ const one = url.pathname.match(ONE);
3673
+ if (url.pathname !== LIST && !one) return null;
3674
+ const operator = ctx.membershipAuthorityOperator;
3675
+ if (!operator) return json({ error: "not found" }, 404);
3676
+ if (req.method !== (one ? "POST" : "GET")) return json({ error: "method not allowed" }, 405);
3677
+ const raw2 = ctx.makeDb(env), user = await ctx.verifyUser(req, env);
3678
+ if (!user) return json({ error: "unauthorized" }, 401);
3679
+ if (!await ctx.isSuperAdminEmail(raw2, user.email, user.userId)) return json({ error: "super-admin required", code: "super_admin_required" }, 403);
3680
+ let db;
3681
+ try {
3682
+ db = operator.makeDb(env);
3683
+ } catch {
3684
+ return json({ error: "the membership authority is not hosted by this deployment" }, 404);
3685
+ }
3686
+ if (!one) {
3687
+ const rows5 = (await db.query({ networkSeat: { $: { where: { environment: env.ODLA_ENV }, order: { updatedAt: "desc" }, limit: 201 } } })).networkSeat ?? [];
3688
+ return json({ seats: rows5.slice(0, 200).map(project), truncated: rows5.length > 200 });
3689
+ }
3690
+ const seat = await membershipLoad(db, "networkSeat", one[1]);
3691
+ if (!seat || seat.environment !== env.ODLA_ENV) return json({ error: "not found" }, 404);
3692
+ if (seat.status === "refunded") {
3693
+ return json({ ok: true, seatId: seat.id, status: "refunded", refundedCents: seat.refundedCents, subscriptionCanceled: false, replayed: true });
3694
+ }
3695
+ if (!seat.purchasedAt) return json({ error: "seat has no completed payment to refund", code: "seat_not_refundable" }, 409);
3696
+ const secret = await getVaultSecret(db, operator.stripeSecretName ?? "stripe_secret_key");
3697
+ if (!secret) return json({ error: "network payment provider unavailable" }, 503);
3698
+ try {
3699
+ const receipt = await refundNamedSeatByOperator(db, {
3700
+ appId: String(seat.appId),
3701
+ chapterId: String(seat.chapterId),
3702
+ environment: String(seat.environment),
3703
+ runtime: String(seat.runtime),
3704
+ applicationId: String(seat.applicationId),
3705
+ skuId: String(seat.skuId),
3706
+ seatId: String(seat.id)
3707
+ }, secret, Date.now());
3708
+ return json({ ok: true, ...receipt });
3709
+ } catch (error) {
3710
+ const code = error.code;
3711
+ return json({ error: "seat refund is pending; retry to confirm", ...code ? { code } : {} }, code ? 409 : 503);
3712
+ }
3713
+ };
3714
+
3413
3715
  // src/worker-routes-membership-cancel.ts
3414
3716
  var handleMembershipCancellation = async (req, url, env, ctx) => {
3415
3717
  const match = url.pathname.match(/^\/api\/admin\/applications\/([0-9a-f-]+)\/cancel-renewal$/);
@@ -4006,6 +4308,29 @@ async function authorizeCrmDiscussionReference(req, env, fetcher = fetch) {
4006
4308
  }
4007
4309
  }
4008
4310
 
4311
+ // src/join-founding-preview.ts
4312
+ async function foundingPreviews(tiers, env, ctx) {
4313
+ const result = /* @__PURE__ */ new Map();
4314
+ const preview = ctx.membershipAuthority?.previewOffer;
4315
+ if (!preview) return result;
4316
+ const scope2 = { appId: env.ODLA_APP_ID, chapterId: ctx.chapter.id, environment: env.ODLA_ENV, runtime: stripeRuntimeIdentity(env.ODLA_RUNTIME) };
4317
+ await Promise.all(tiers.filter((tier) => !tierIsFree(tier) && tier.stripePriceId).map(async (tier) => {
4318
+ try {
4319
+ const offer = await preview({ ...scope2, skuId: tier.id }, env);
4320
+ if (!offer.founding || !offer.cohortLabel || offer.basePriceCents !== tier.priceCents || offer.dueTodayCents >= offer.basePriceCents) return;
4321
+ result.set(tier.id, {
4322
+ cohortLabel: offer.cohortLabel,
4323
+ dueTodayCents: offer.dueTodayCents,
4324
+ discountCents: offer.basePriceCents - offer.dueTodayCents,
4325
+ renewalCents: offer.renewalCents,
4326
+ ...offer.discountDuration ? { discountDuration: offer.discountDuration } : {}
4327
+ });
4328
+ } catch {
4329
+ }
4330
+ }));
4331
+ return result;
4332
+ }
4333
+
4009
4334
  // src/admission-grants-contract.ts
4010
4335
  var ADMISSION_GRANTS_SCHEMA_VERSION = 1;
4011
4336
  var ADMISSION_GRANTS_PATH = "/api/admission-grants";
@@ -4532,10 +4857,24 @@ async function materializeSignupControl(db, envelope, previous, receivedAt) {
4532
4857
  }
4533
4858
 
4534
4859
  // src/payment-quote.ts
4860
+ function tierPresentationFor(document, tierId) {
4861
+ const compareAt = document?.membershipPage?.tiers?.find((tier) => tier.tierId === tierId)?.compareAtPriceCents;
4862
+ return Number.isSafeInteger(compareAt) && Number(compareAt) > 0 ? { compareAtPriceCents: Number(compareAt) } : void 0;
4863
+ }
4864
+ function foundingOf(offer) {
4865
+ return offer?.contract?.founding === true && offer.contract.discountPolicy ? { cohortLabel: offer.contract.discountPolicy.cohortLabel } : void 0;
4866
+ }
4535
4867
  async function createPaymentQuote(input) {
4536
4868
  const { group, tier, policy } = input;
4537
4869
  const terms2 = input.billingTerms ? subscriptionBillingTerms(input.billingTerms) : void 0;
4870
+ const compareAt = input.presentation?.compareAtPriceCents;
4871
+ const presentation = Number.isSafeInteger(compareAt) && Number(compareAt) > tier.priceCents ? { compareAtPriceCents: Number(compareAt) } : void 0;
4538
4872
  const dueToday = terms2 ? subscriptionAmountDue({ priceCents: tier.priceCents, billingTerms: terms2 }) : void 0;
4873
+ const lines = dueToday === void 0 ? paymentLineItems(group, tier) : {
4874
+ standardCents: tier.priceCents,
4875
+ discountCents: tier.priceCents - dueToday,
4876
+ dueTodayCents: dueToday
4877
+ };
4539
4878
  const quote = {
4540
4879
  applicationId: input.applicationId,
4541
4880
  chapterId: input.chapterId,
@@ -4543,11 +4882,12 @@ async function createPaymentQuote(input) {
4543
4882
  tierName: tier.name,
4544
4883
  currency: tier.currency ?? String(group.currency ?? "usd"),
4545
4884
  interval: tier.interval ?? (group.interval === "month" ? "month" : "year"),
4546
- lineItems: dueToday === void 0 ? paymentLineItems(group, tier) : {
4547
- standardCents: tier.priceCents,
4548
- discountCents: tier.priceCents - dueToday,
4549
- dueTodayCents: dueToday
4550
- },
4885
+ lineItems: presentation ? {
4886
+ standardCents: presentation.compareAtPriceCents,
4887
+ discountCents: presentation.compareAtPriceCents - lines.dueTodayCents,
4888
+ dueTodayCents: lines.dueTodayCents
4889
+ } : lines,
4890
+ ...presentation ? { presentation } : {},
4551
4891
  refundPolicyText: policy?.refundPolicyText ?? String(group.refundPolicyText ?? ""),
4552
4892
  merchantDisclosureText: String(group.merchantDisclosureText ?? ""),
4553
4893
  trustCopy: policy ? policy.trustCopy ?? "" : String(group.trustCopy ?? ""),
@@ -4572,37 +4912,39 @@ async function createPaymentQuote(input) {
4572
4912
  });
4573
4913
  const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(contract));
4574
4914
  const digest = [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
4575
- return { ...quote, digest };
4915
+ return { ...quote, digest, ...input.founding ? { founding: input.founding } : {} };
4576
4916
  }
4577
4917
 
4578
4918
  // src/membership-quote-projection.ts
4579
4919
  var MEMBERSHIP_QUOTE_NAMESPACE = "membershipQuoteProjections";
4580
4920
  var keys2 = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId"];
4581
- var fail3 = (code) => {
4921
+ var fail4 = (code) => {
4582
4922
  throw Object.assign(new Error(code), { code });
4583
4923
  };
4584
4924
  var load3 = async (db, ns, id6) => (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
4585
4925
  async function restore2(row2, target, now, allowExpired = false) {
4586
- if (!row2 || keys2.some((key3) => row2[key3] !== target[key3]) || !Number.isSafeInteger(row2.revision) || Number(row2.revision) < 1 || typeof row2.requestId !== "string") return fail3("membership_quote_unavailable");
4587
- if (row2.status !== "ready") return fail3("membership_quote_pending");
4926
+ if (!row2 || keys2.some((key3) => row2[key3] !== target[key3]) || !Number.isSafeInteger(row2.revision) || Number(row2.revision) < 1 || typeof row2.requestId !== "string") return fail4("membership_quote_unavailable");
4927
+ if (row2.status !== "ready") return fail4("membership_quote_pending");
4588
4928
  const offer = await verifyIssuedMembershipOffer(row2.offer, { ...target, offerId: await membershipOfferIdForRequest({
4589
4929
  ...target,
4590
4930
  requestId: row2.requestId
4591
4931
  }), now, lifetimeMs: 6e4 });
4592
- if (offer.issuedAt > now + 3e4 || !allowExpired && offer.expiresAt <= now) return fail3("membership_quote_expired");
4932
+ if (offer.issuedAt > now + 3e4 || !allowExpired && offer.expiresAt <= now) return fail4("membership_quote_expired");
4593
4933
  const tier = row2.tier, quote = row2.quote;
4594
4934
  const offering = offer.contract.offering;
4595
4935
  if (!tier || tier.id !== target.skuId || tier.groupId !== target.chapterId || !tier.stripePriceId || tier.priceCents !== offering.basePriceCents || tier.currency !== offering.currency || tier.interval !== offering.interval || !quote) {
4596
- return fail3("membership_catalog_mismatch");
4936
+ return fail4("membership_catalog_mismatch");
4597
4937
  }
4598
4938
  const expected = await createPaymentQuote({
4599
4939
  ...target,
4600
4940
  tier,
4601
4941
  group: { merchantDisclosureText: quote.merchantDisclosureText },
4602
4942
  policy: { refundPolicyText: quote.refundPolicyText, trustCopy: quote.trustCopy },
4603
- billingTerms: offer.contract.billingTerms
4943
+ billingTerms: offer.contract.billingTerms,
4944
+ presentation: quote.presentation,
4945
+ founding: foundingOf(offer)
4604
4946
  });
4605
- if (quote.digest !== expected.digest) return fail3("membership_quote_conflict");
4947
+ if (quote.digest !== expected.digest) return fail4("membership_quote_conflict");
4606
4948
  return { tier, quote: expected, billingTerms: offer.contract.billingTerms, membershipOffer: offer, projectionRevision: Number(row2.revision) };
4607
4949
  }
4608
4950
  async function readMembershipQuote(db, target, now) {
@@ -4610,9 +4952,9 @@ async function readMembershipQuote(db, target, now) {
4610
4952
  }
4611
4953
  async function prepareMembershipQuote(db, input, issue) {
4612
4954
  const { applicationId: id6, now, app } = input;
4613
- if (app.id !== id6 || app.groupId !== input.chapterId || app.tierId !== input.skuId || app.stripeRuntime !== input.runtime || app.status !== "submitted" || app.canceled === true || app.giftOfferId || app.giftClaimId || app.namedSeatId || app.namedSeatClaimPending) return fail3("membership_quote_unavailable");
4955
+ if (app.id !== id6 || app.groupId !== input.chapterId || app.tierId !== input.skuId || app.stripeRuntime !== input.runtime || app.status !== "submitted" || app.canceled === true || app.giftOfferId || app.giftClaimId || app.namedSeatId || app.namedSeatClaimPending) return fail4("membership_quote_unavailable");
4614
4956
  let row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
4615
- if (row2 && keys2.some((key3) => row2[key3] !== input[key3])) return fail3("membership_quote_conflict");
4957
+ if (row2 && keys2.some((key3) => row2[key3] !== input[key3])) return fail4("membership_quote_conflict");
4616
4958
  if (row2?.status === "ready") {
4617
4959
  try {
4618
4960
  return await restore2(row2, input, now);
@@ -4657,7 +4999,7 @@ async function prepareMembershipQuote(db, input, issue) {
4657
4999
  }
4658
5000
  if (row2?.status === "ready") return restore2(row2, input, now);
4659
5001
  if (!row2 || row2.status !== "pending" || keys2.some((key3) => row2[key3] !== input[key3]) || typeof row2.requestId !== "string") {
4660
- return fail3("membership_quote_pending");
5002
+ return fail4("membership_quote_pending");
4661
5003
  }
4662
5004
  const target = Object.fromEntries(keys2.map((key3) => [key3, input[key3]]));
4663
5005
  const offer = await verifyIssuedMembershipOffer(await issue({ ...target, requestId: row2.requestId }), {
@@ -4671,7 +5013,15 @@ async function prepareMembershipQuote(db, input, issue) {
4671
5013
  currency: input.tier.currency ?? String(input.group.currency ?? "usd"),
4672
5014
  interval: input.tier.interval ?? (input.group.interval === "month" ? "month" : "year")
4673
5015
  };
4674
- const quote = await createPaymentQuote({ ...target, tier, group: input.group, policy: input.policy, billingTerms: offer.contract.billingTerms });
5016
+ const quote = await createPaymentQuote({
5017
+ ...target,
5018
+ tier,
5019
+ group: input.group,
5020
+ policy: input.policy,
5021
+ billingTerms: offer.contract.billingTerms,
5022
+ presentation: input.presentation,
5023
+ founding: foundingOf(offer)
5024
+ });
4675
5025
  const ready = { ...row2, offer, tier, quote, status: "ready", updatedAt: now };
4676
5026
  await restore2(ready, target, now, true);
4677
5027
  try {
@@ -4726,7 +5076,9 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
4726
5076
  group: { merchantDisclosureText: quote2.merchantDisclosureText },
4727
5077
  tier: tier2,
4728
5078
  policy: { refundPolicyText: quote2.refundPolicyText, trustCopy: quote2.trustCopy },
4729
- billingTerms
5079
+ billingTerms,
5080
+ presentation: quote2.presentation,
5081
+ founding: foundingOf(intent.membershipOffer)
4730
5082
  });
4731
5083
  if (expected.digest !== quote2.digest) throw new Error("invalid commercial snapshot");
4732
5084
  return { tier: tier2, quote: expected, billingTerms, membershipOffer: intent.membershipOffer };
@@ -4753,9 +5105,10 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
4753
5105
  if (!tier || tierIsFree(tier) || !tier.stripePriceId) throw Object.assign(new Error("tier unavailable"), { code: "tier_unresolved" });
4754
5106
  const control3 = await loadActiveSignupControl(db, ctx.chapter.id, env.ODLA_ENV);
4755
5107
  const policy = control3?.document.policy?.tierPolicies?.[tier.id];
5108
+ const presentation = tierPresentationFor(control3?.document, tier.id);
4756
5109
  if (ctx.membershipAuthority) return prepareMembershipQuote(
4757
5110
  db,
4758
- { ...target, app, group, tier, policy, now: Date.now() },
5111
+ { ...target, app, group, tier, policy, presentation, now: Date.now() },
4759
5112
  (input) => ctx.membershipAuthority.issueOffer(input, env)
4760
5113
  );
4761
5114
  const quote = await createPaymentQuote({
@@ -4766,7 +5119,8 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
4766
5119
  runtime: stripeRuntimeIdentity(env.ODLA_RUNTIME),
4767
5120
  group,
4768
5121
  tier,
4769
- policy
5122
+ policy,
5123
+ presentation
4770
5124
  });
4771
5125
  return { tier, quote, billingTerms: void 0 };
4772
5126
  }
@@ -5420,10 +5774,11 @@ var handleMember = async (req, url, env, ctx) => {
5420
5774
  group,
5421
5775
  Boolean(stripeKey)
5422
5776
  );
5777
+ const founding = await foundingPreviews(offered, env, ctx);
5423
5778
  const activeControl = chapter.signupControl ? await loadActiveSignupControl(db, chapter.id, env.ODLA_ENV) : null;
5424
5779
  const application = controlledApplication(chapter.application, activeControl?.document);
5425
5780
  return json({
5426
- ...joinConfig(group, paymentsReady, joinConfigTiers(offered, { currency: typeof group.currency === "string" ? group.currency : void 0, interval: group.interval === "month" ? "month" : "year" }), application.conditions),
5781
+ ...joinConfig(group, paymentsReady, joinConfigTiers(offered, { currency: typeof group.currency === "string" ? group.currency : void 0, interval: group.interval === "month" ? "month" : "year" }, founding), application.conditions),
5427
5782
  fieldContract: chapterFlowFieldContract(chapter, "join", application),
5428
5783
  copy: { ...chapter.copy.join, ...activeControl?.document.application?.copy ?? {} },
5429
5784
  ...activeControl ? { signupControl: {
@@ -7116,7 +7471,7 @@ async function deliverAdmissionGrants(options) {
7116
7471
  }
7117
7472
 
7118
7473
  // src/worker-routes-network-admission.ts
7119
- var PATH = "/api/admin/network/admissions";
7474
+ var PATH2 = "/api/admin/network/admissions";
7120
7475
  var RETRY_PATH = "/api/admin/network/admissions/retry";
7121
7476
  var REVOKE_PATH = "/api/admin/network/admissions/revoke";
7122
7477
  var MUTATION_ID2 = /^[A-Za-z0-9_-]{16,128}$/;
@@ -7196,11 +7551,11 @@ async function parseBody(req) {
7196
7551
  }
7197
7552
  }
7198
7553
  var handleAdminNetworkAdmissions = async (req, url, env, ctx) => {
7199
- if (![PATH, RETRY_PATH, REVOKE_PATH].includes(url.pathname)) return null;
7554
+ if (![PATH2, RETRY_PATH, REVOKE_PATH].includes(url.pathname)) return null;
7200
7555
  const auth = await gate2(req, env, ctx);
7201
7556
  if ("response" in auth) return auth.response;
7202
7557
  const db = auth.db;
7203
- if (url.pathname === PATH && req.method === "GET") {
7558
+ if (url.pathname === PATH2 && req.method === "GET") {
7204
7559
  const targetId2 = url.searchParams.get("targetId") ?? "";
7205
7560
  const target2 = targetFor(ctx, targetId2);
7206
7561
  if (!target2) return reject6(404, "unknown_target", "admission target is not configured");
@@ -7444,28 +7799,28 @@ var handleSchedule = async (req, url, env, ctx) => {
7444
7799
  };
7445
7800
 
7446
7801
  // src/payments-stripe.ts
7447
- import { stripeRequest as stripeRequest7, verifyStripeSignature as verifyStripeSignature2 } from "@odla-ai/stripe";
7802
+ import { stripeRequest as stripeRequest8, verifyStripeSignature as verifyStripeSignature2 } from "@odla-ai/stripe";
7448
7803
 
7449
7804
  // src/payments-refund.ts
7450
- import { stripeRequest as stripeRequest4 } from "@odla-ai/stripe";
7805
+ import { stripeRequest as stripeRequest5 } from "@odla-ai/stripe";
7451
7806
  var row = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
7452
7807
  var id2 = (value) => typeof value === "string" ? value : String(row(value).id ?? "");
7453
7808
  var providerId2 = (value, prefix) => new RegExp(`^${prefix}_[A-Za-z0-9]+$`).test(value);
7454
- function fail4(code = "refund_target_mismatch") {
7809
+ function fail5(code = "refund_target_mismatch") {
7455
7810
  throw Object.assign(new Error(code), { code });
7456
7811
  }
7457
7812
  async function refundSubscriptionPayment(secretKey, input) {
7458
- if (!providerId2(input.customerId, "cus") || !providerId2(input.subscriptionId, "sub")) fail4();
7813
+ if (!providerId2(input.customerId, "cus") || !providerId2(input.subscriptionId, "sub")) fail5();
7459
7814
  const call = async (method, path, params, key3) => {
7460
- const result = await stripeRequest4({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
7461
- if (!result.ok) fail4("stripe_error");
7462
- if (!result.body || typeof result.body !== "object" || Array.isArray(result.body)) fail4("stripe_invalid_response");
7815
+ const result = await stripeRequest5({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
7816
+ if (!result.ok) fail5("stripe_error");
7817
+ if (!result.body || typeof result.body !== "object" || Array.isArray(result.body)) fail5("stripe_invalid_response");
7463
7818
  return result.body;
7464
7819
  };
7465
7820
  const sub = await call("GET", `/v1/subscriptions/${input.subscriptionId}`);
7466
- if (id2(sub) !== input.subscriptionId || id2(sub.customer) !== input.customerId) fail4();
7821
+ if (id2(sub) !== input.subscriptionId || id2(sub.customer) !== input.customerId) fail5();
7467
7822
  for (const [key3, value] of Object.entries(input.metadata ?? {})) {
7468
- if (row(sub.metadata)[key3] !== value) fail4();
7823
+ if (row(sub.metadata)[key3] !== value) fail5();
7469
7824
  }
7470
7825
  let cursor;
7471
7826
  let earliest;
@@ -7478,69 +7833,69 @@ async function refundSubscriptionPayment(secretKey, input) {
7478
7833
  limit: 100,
7479
7834
  ...cursor ? { starting_after: cursor } : {}
7480
7835
  });
7481
- if (!Array.isArray(list.data) || typeof list.has_more !== "boolean") fail4("stripe_invalid_response");
7836
+ if (!Array.isArray(list.data) || typeof list.has_more !== "boolean") fail5("stripe_invalid_response");
7482
7837
  for (const value of list.data) {
7483
7838
  const invoice2 = row(value);
7484
7839
  const subscription = invoice2.subscription ?? row(row(invoice2.parent).subscription_details).subscription;
7485
- if (!providerId2(id2(invoice2), "in") || seen.has(id2(invoice2)) || id2(subscription) !== input.subscriptionId || id2(invoice2.customer) !== input.customerId || invoice2.status !== "paid" || !Number.isSafeInteger(invoice2.created) || !Number.isSafeInteger(invoice2.amount_paid) || Number(invoice2.amount_paid) < 0) fail4();
7840
+ if (!providerId2(id2(invoice2), "in") || seen.has(id2(invoice2)) || id2(subscription) !== input.subscriptionId || id2(invoice2.customer) !== input.customerId || invoice2.status !== "paid" || !Number.isSafeInteger(invoice2.created) || !Number.isSafeInteger(invoice2.amount_paid) || Number(invoice2.amount_paid) < 0) fail5();
7486
7841
  seen.add(id2(invoice2));
7487
7842
  if (Number(invoice2.amount_paid) > 0 && (!earliest || Number(invoice2.created) < Number(earliest.created))) earliest = invoice2;
7488
7843
  }
7489
7844
  if (!list.has_more) break;
7490
- if (!list.data.length || page === 9) fail4("refund_history_incomplete");
7845
+ if (!list.data.length || page === 9) fail5("refund_history_incomplete");
7491
7846
  cursor = id2(list.data[list.data.length - 1]);
7492
7847
  }
7493
- if (!earliest) fail4("no_charge");
7848
+ if (!earliest) fail5("no_charge");
7494
7849
  const invoice = earliest;
7495
- if (input.expectedInvoiceId !== void 0 && id2(invoice) !== input.expectedInvoiceId || input.expectedAmountCents !== void 0 && invoice.amount_paid !== input.expectedAmountCents) fail4();
7850
+ if (input.expectedInvoiceId !== void 0 && id2(invoice) !== input.expectedInvoiceId || input.expectedAmountCents !== void 0 && invoice.amount_paid !== input.expectedAmountCents) fail5();
7496
7851
  let chargeId = id2(invoice.charge);
7497
7852
  let paymentIntentId = id2(invoice.payment_intent);
7498
7853
  if (!chargeId && !paymentIntentId) {
7499
7854
  const payments = await call("GET", "/v1/invoice_payments", { invoice: id2(invoice), status: "paid", limit: 100 });
7500
- if (payments.has_more !== false || !Array.isArray(payments.data) || payments.data.length !== 1) fail4("refund_payment_ambiguous");
7855
+ if (payments.has_more !== false || !Array.isArray(payments.data) || payments.data.length !== 1) fail5("refund_payment_ambiguous");
7501
7856
  const payment = row(payments.data[0]);
7502
- if (id2(payment.invoice) !== id2(invoice) || payment.status !== "paid" || row(payment.payment).type !== "payment_intent") fail4();
7857
+ if (id2(payment.invoice) !== id2(invoice) || payment.status !== "paid" || row(payment.payment).type !== "payment_intent") fail5();
7503
7858
  paymentIntentId = id2(row(payment.payment).payment_intent);
7504
7859
  }
7505
7860
  if (!chargeId) {
7506
- if (!providerId2(paymentIntentId, "pi")) fail4();
7861
+ if (!providerId2(paymentIntentId, "pi")) fail5();
7507
7862
  const payment = await call("GET", `/v1/payment_intents/${paymentIntentId}`);
7508
- if (id2(payment) !== paymentIntentId || id2(payment.customer) !== input.customerId || payment.status !== "succeeded") fail4();
7863
+ if (id2(payment) !== paymentIntentId || id2(payment.customer) !== input.customerId || payment.status !== "succeeded") fail5();
7509
7864
  chargeId = id2(payment.latest_charge);
7510
7865
  }
7511
- if (!providerId2(chargeId, "ch")) fail4();
7866
+ if (!providerId2(chargeId, "ch")) fail5();
7512
7867
  const charge = await call("GET", `/v1/charges/${chargeId}`);
7513
- if (id2(charge) !== chargeId || id2(charge.customer) !== input.customerId || charge.status !== "succeeded" || charge.invoice != null && id2(charge.invoice) !== id2(invoice) || paymentIntentId && id2(charge.payment_intent) !== paymentIntentId || !Number.isSafeInteger(charge.amount) || Number(charge.amount) <= 0 || !Number.isSafeInteger(charge.amount_refunded) || Number(charge.amount_refunded) < 0 || Number(charge.amount_refunded) > Number(charge.amount) || charge.currency !== invoice.currency || Number(charge.amount) !== Number(invoice.amount_paid)) fail4();
7514
- if (input.alreadyRefundedChargeId !== void 0 && (chargeId !== input.alreadyRefundedChargeId || charge.amount_refunded !== charge.amount || charge.refunded !== true)) fail4();
7868
+ if (id2(charge) !== chargeId || id2(charge.customer) !== input.customerId || charge.status !== "succeeded" || charge.invoice != null && id2(charge.invoice) !== id2(invoice) || paymentIntentId && id2(charge.payment_intent) !== paymentIntentId || !Number.isSafeInteger(charge.amount) || Number(charge.amount) <= 0 || !Number.isSafeInteger(charge.amount_refunded) || Number(charge.amount_refunded) < 0 || Number(charge.amount_refunded) > Number(charge.amount) || charge.currency !== invoice.currency || Number(charge.amount) !== Number(invoice.amount_paid)) fail5();
7869
+ if (input.alreadyRefundedChargeId !== void 0 && (chargeId !== input.alreadyRefundedChargeId || charge.amount_refunded !== charge.amount || charge.refunded !== true)) fail5();
7515
7870
  if (charge.amount_refunded !== charge.amount) {
7516
7871
  const refund = await call("POST", "/v1/refunds", {
7517
7872
  charge: chargeId,
7518
7873
  ...input.metadata ? { metadata: input.metadata } : {}
7519
7874
  }, `chapter-initial-refund:${input.subscriptionId}:${chargeId}`);
7520
- if (id2(refund.charge) !== chargeId || !["succeeded", "pending"].includes(String(refund.status)) || !Number.isSafeInteger(refund.amount) || Number(refund.amount) !== Number(charge.amount) - Number(charge.amount_refunded)) fail4("refund_not_confirmed");
7875
+ if (id2(refund.charge) !== chargeId || !["succeeded", "pending"].includes(String(refund.status)) || !Number.isSafeInteger(refund.amount) || Number(refund.amount) !== Number(charge.amount) - Number(charge.amount_refunded)) fail5("refund_not_confirmed");
7521
7876
  }
7522
7877
  let subscriptionCanceled = sub.status === "canceled";
7523
7878
  if (input.cancelSubscription !== false && !subscriptionCanceled) {
7524
7879
  const canceled = await call("DELETE", `/v1/subscriptions/${input.subscriptionId}`);
7525
- if (id2(canceled) !== input.subscriptionId || canceled.status !== "canceled") fail4("refund_cancel_incomplete");
7880
+ if (id2(canceled) !== input.subscriptionId || canceled.status !== "canceled") fail5("refund_cancel_incomplete");
7526
7881
  subscriptionCanceled = true;
7527
7882
  }
7528
7883
  const confirmed = await call("GET", `/v1/charges/${chargeId}`);
7529
- if (id2(confirmed) !== chargeId || id2(confirmed.customer) !== input.customerId || confirmed.amount !== charge.amount || confirmed.currency !== charge.currency || confirmed.amount_refunded !== charge.amount || confirmed.refunded !== true) fail4("refund_not_confirmed");
7884
+ if (id2(confirmed) !== chargeId || id2(confirmed.customer) !== input.customerId || confirmed.amount !== charge.amount || confirmed.currency !== charge.currency || confirmed.amount_refunded !== charge.amount || confirmed.refunded !== true) fail5("refund_not_confirmed");
7530
7885
  return { refundedCents: Number(charge.amount), subscriptionCanceled };
7531
7886
  }
7532
7887
 
7533
7888
  // src/subscription-payment-identity.ts
7534
- import { stripeRequest as stripeRequest6 } from "@odla-ai/stripe";
7889
+ import { stripeRequest as stripeRequest7 } from "@odla-ai/stripe";
7535
7890
 
7536
7891
  // src/subscription-provider-contract.ts
7537
- import { stripeRequest as stripeRequest5 } from "@odla-ai/stripe";
7892
+ import { stripeRequest as stripeRequest6 } from "@odla-ai/stripe";
7538
7893
  function subscriptionContractMismatch() {
7539
7894
  throw Object.assign(new Error("Stripe subscription does not match the checkout contract"), { code: "stripe_contract_mismatch" });
7540
7895
  }
7541
7896
  async function verifySubscriptionPrice(secretKey, input, resume = false) {
7542
7897
  if (!/^price_[A-Za-z0-9]+$/.test(input.priceId) || !Number.isSafeInteger(input.priceCents) || input.priceCents <= 0 || !/^[a-z]{3}$/.test(input.currency) || !["month", "year"].includes(input.interval)) subscriptionContractMismatch();
7543
- const result = await stripeRequest5({ secretKey }, "GET", `/v1/prices/${encodeURIComponent(input.priceId)}`);
7898
+ const result = await stripeRequest6({ secretKey }, "GET", `/v1/prices/${encodeURIComponent(input.priceId)}`);
7544
7899
  const price = result.body;
7545
7900
  if (!result.ok || !price || price.id !== input.priceId || !resume && price.active !== true || price.unit_amount !== input.priceCents || price.currency !== input.currency || price.type !== "recurring" || price.billing_scheme !== "per_unit" || price.custom_unit_amount || price.transform_quantity || price.recurring?.interval !== input.interval || price.recurring?.interval_count !== 1 || price.recurring?.usage_type !== "licensed" || price.livemode !== (input.environment === "prod")) subscriptionContractMismatch();
7546
7901
  return typeof price.product === "string" ? price.product : void 0;
@@ -7559,7 +7914,7 @@ async function tagSubscriptionPayment(secretKey, invoice, customerId, metadata,
7559
7914
  let paymentId = id3(invoice.payment_intent);
7560
7915
  if (!paymentId) {
7561
7916
  if (!/^in_[A-Za-z0-9]+$/.test(id3(invoice))) subscriptionContractMismatch();
7562
- const result2 = await stripeRequest6({ secretKey }, "GET", "/v1/invoice_payments", { invoice: id3(invoice), limit: 100 });
7917
+ const result2 = await stripeRequest7({ secretKey }, "GET", "/v1/invoice_payments", { invoice: id3(invoice), limit: 100 });
7563
7918
  const list = result2.body;
7564
7919
  if (!result2.ok || list?.has_more !== false || !Array.isArray(list.data) || list.data.length !== 1) subscriptionContractMismatch();
7565
7920
  const link = list.data[0];
@@ -7568,11 +7923,11 @@ async function tagSubscriptionPayment(secretKey, invoice, customerId, metadata,
7568
7923
  }
7569
7924
  if (!/^pi_[A-Za-z0-9]+$/.test(paymentId)) subscriptionContractMismatch();
7570
7925
  const path = `/v1/payment_intents/${paymentId}`;
7571
- const result = await stripeRequest6({ secretKey }, "GET", path);
7926
+ const result = await stripeRequest7({ secretKey }, "GET", path);
7572
7927
  const payment = result.body;
7573
7928
  if (!result.ok || payment?.id !== paymentId || id3(payment.customer) !== customerId || payment.amount !== invoice.total || payment.currency !== invoice.currency || payment.client_secret !== clientSecret2 || payment.livemode !== (metadata.odlaEnvironment === "prod") || !Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === void 0 || payment.metadata[key3] === value)) subscriptionContractMismatch();
7574
7929
  if (Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === value)) return;
7575
- const tagged = await stripeRequest6(
7930
+ const tagged = await stripeRequest7(
7576
7931
  { secretKey },
7577
7932
  "POST",
7578
7933
  path,
@@ -7584,7 +7939,7 @@ async function tagSubscriptionPayment(secretKey, invoice, customerId, metadata,
7584
7939
 
7585
7940
  // src/payments-stripe.ts
7586
7941
  async function stripeCall(sk, method, path, params, idempotencyKey) {
7587
- return stripeRequest7(
7942
+ return stripeRequest8(
7588
7943
  { secretKey: sk },
7589
7944
  method,
7590
7945
  path,
@@ -7592,7 +7947,7 @@ async function stripeCall(sk, method, path, params, idempotencyKey) {
7592
7947
  idempotencyKey ? { idempotencyKey } : {}
7593
7948
  );
7594
7949
  }
7595
- function fail5(op, r) {
7950
+ function fail6(op, r) {
7596
7951
  const err = new Error(`stripe ${op} failed: ${r.status}`);
7597
7952
  err.code = "stripe_error";
7598
7953
  throw err;
@@ -7642,7 +7997,7 @@ function createStripeProvider(config) {
7642
7997
  { email: input.email, name: input.name, metadata: meta },
7643
7998
  `cus:${input.applicationId}`
7644
7999
  );
7645
- if (!cust.ok) fail5("customer create", cust);
8000
+ if (!cust.ok) fail6("customer create", cust);
7646
8001
  if (typeof cust.body.id !== "string" || !/^cus_[A-Za-z0-9]+$/.test(cust.body.id)) subscriptionContractMismatch();
7647
8002
  customerId = cust.body.id;
7648
8003
  }
@@ -7674,7 +8029,7 @@ function createStripeProvider(config) {
7674
8029
  // retry between create and the db write can't orphan a second one.
7675
8030
  `sub:${input.applicationId}${input.purchaseAttemptId ? `:${input.purchaseAttemptId}` : ""}`
7676
8031
  );
7677
- if (!sub.ok) fail5("subscription create", sub);
8032
+ if (!sub.ok) fail6("subscription create", sub);
7678
8033
  if (input.billingTerms?.discount?.duration === "once" && Array.isArray(sub.body.discounts) && sub.body.discounts.length === 0) {
7679
8034
  const subscriptionId = sub.body.id;
7680
8035
  if (typeof subscriptionId !== "string" || !/^sub_[A-Za-z0-9]+$/.test(subscriptionId)) subscriptionContractMismatch();
@@ -7684,14 +8039,14 @@ function createStripeProvider(config) {
7684
8039
  "expand[2]": "discounts",
7685
8040
  "expand[3]": "latest_invoice.discounts"
7686
8041
  });
7687
- if (!expanded.ok) fail5("subscription invoice evidence", expanded);
8042
+ if (!expanded.ok) fail6("subscription invoice evidence", expanded);
7688
8043
  if (expanded.body.id !== subscriptionId) subscriptionContractMismatch();
7689
8044
  sub = expanded;
7690
8045
  }
7691
8046
  verifySubscriptionResult(sub.body, input, customerId, meta);
7692
8047
  const invoice = sub.body.latest_invoice;
7693
8048
  const clientSecret2 = clientSecretOf(sub.body);
7694
- if (!clientSecret2) fail5("subscription confirmation-secret missing", sub);
8049
+ if (!clientSecret2) fail6("subscription confirmation-secret missing", sub);
7695
8050
  await tagSubscriptionPayment(
7696
8051
  sk,
7697
8052
  invoice,
@@ -8363,7 +8718,7 @@ async function namedSeatOfferDigest(offer) {
8363
8718
  "named-only",
8364
8719
  "full-upfront",
8365
8720
  "auto-renew-coterm",
8366
- "30-day-unaccepted-full-refund"
8721
+ "no-automatic-refund"
8367
8722
  ])));
8368
8723
  return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
8369
8724
  }
@@ -8768,6 +9123,23 @@ async function updateChapterPerson(opts) {
8768
9123
  });
8769
9124
  }
8770
9125
 
9126
+ // src/application-actions.ts
9127
+ function isApplicationStage(ctx, stage) {
9128
+ return ctx.chapter.pipeline.stages.includes(stage);
9129
+ }
9130
+ function applicationActionStages(app, env, ctx) {
9131
+ if (!chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME)) return [];
9132
+ const from = String(app.status ?? "");
9133
+ if (app.canceled === true || ["refunded", "canceled"].includes(from)) return [];
9134
+ return ctx.chapter.pipeline.stages.filter((to) => {
9135
+ if (to === from) return false;
9136
+ if (to === "approved") return canApprove(from, ctx.chapter.pipeline) && from !== "declined";
9137
+ if (to === "refunded") return Boolean(app.namedSeatId || app.stripeCustomerId && app.stripeSubscriptionId) && (!ctx.chapter.operations.refund.allowedFrom || ctx.chapter.operations.refund.allowedFrom.includes(from));
9138
+ if (ctx.membershipAuthority && ["submitted", "paid_pending_vetting", "declined", "canceled"].includes(to)) return false;
9139
+ return canTransition(from, to, ctx.chapter.pipeline);
9140
+ });
9141
+ }
9142
+
8771
9143
  // src/worker-routes-people-profile.ts
8772
9144
  var handlePeopleProfile = async (req, url, env, ctx) => {
8773
9145
  if (ctx.chapter.mode !== "chapter") return null;
@@ -8789,7 +9161,16 @@ var handlePeopleProfile = async (req, url, env, ctx) => {
8789
9161
  const record4 = await getRecord3({ crm: ctx.chapter.crm, db }, id6);
8790
9162
  if (!record4) return json({ error: "not found" }, 404);
8791
9163
  if (record4.type !== "person") return dedicated ? json({ error: "person required" }, 422) : null;
8792
- if (match?.[2]) return json({ error: "use the application lifecycle workflow", code: "application_workflow_required" }, 409);
9164
+ if (match?.[2]) {
9165
+ const linked = typeof record4.fields.applicationId === "string" && record4.fields.applicationId !== "";
9166
+ let to = "";
9167
+ try {
9168
+ to = String((await req.clone().json()).to ?? "");
9169
+ } catch {
9170
+ }
9171
+ if (!linked && to && !isApplicationStage(ctx, to)) return null;
9172
+ return json({ error: "use the application lifecycle workflow", code: "application_workflow_required" }, 409);
9173
+ }
8793
9174
  if (req.method === "GET" && dedicated) {
8794
9175
  const excluded = profileReadOnlyFields(record4, ctx);
8795
9176
  const fields = Object.entries(ctx.chapter.crm.type(record4.type).fields).filter(([id7]) => !excluded.includes(id7)).map(([id7, field]) => ({
@@ -9480,20 +9861,6 @@ var handleAdminBilling = async (req, url, env, ctx) => {
9480
9861
  // src/worker-routes-admin-lifecycle.ts
9481
9862
  import { computeBookableSlots as computeBookableSlots2, initCalendar as initCalendar3 } from "@odla-ai/calendar";
9482
9863
 
9483
- // src/application-actions.ts
9484
- function applicationActionStages(app, env, ctx) {
9485
- if (!chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME)) return [];
9486
- const from = String(app.status ?? "");
9487
- if (app.canceled === true || ["refunded", "canceled"].includes(from)) return [];
9488
- return ctx.chapter.pipeline.stages.filter((to) => {
9489
- if (to === from) return false;
9490
- if (to === "approved") return canApprove(from, ctx.chapter.pipeline) && from !== "declined";
9491
- if (to === "refunded") return Boolean(app.namedSeatId || app.stripeCustomerId && app.stripeSubscriptionId) && (!ctx.chapter.operations.refund.allowedFrom || ctx.chapter.operations.refund.allowedFrom.includes(from));
9492
- if (ctx.membershipAuthority && ["submitted", "paid_pending_vetting", "declined", "canceled"].includes(to)) return false;
9493
- return canTransition(from, to, ctx.chapter.pipeline);
9494
- });
9495
- }
9496
-
9497
9864
  // src/refund-application.ts
9498
9865
  async function refundApplication(db, env, ctx, app) {
9499
9866
  if (!chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME)) return json({ error: "not found" }, 404);
@@ -9684,6 +10051,12 @@ var handleAdminRefund = async (req, url, env, ctx) => {
9684
10051
  const app = await loadApp(db, m[1]);
9685
10052
  if (!app) return json({ error: "not found" }, 404);
9686
10053
  if (!chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME)) return json({ error: "not found" }, 404);
10054
+ if (app.namedSeatId) {
10055
+ const actor = await ctx.verifyUser(req, env);
10056
+ if (!actor || !await ctx.isSuperAdminEmail(db, actor.email, actor.userId)) {
10057
+ return json({ error: "super-admin required", code: "super_admin_required" }, 403);
10058
+ }
10059
+ }
9687
10060
  return refundApplication(db, env, ctx, app);
9688
10061
  };
9689
10062
  var handleAdminApplicationPatch = async (req, url, env, ctx) => {
@@ -10298,7 +10671,7 @@ async function operationalReaderRecord(record4, reader, db, env, ctx) {
10298
10671
  const appId = record4.fields.applicationId;
10299
10672
  const app = typeof appId === "string" ? (await db.query({ applications: { $: { where: { id: appId }, limit: 1 } } })).applications?.[0] : void 0;
10300
10673
  if (reader.stageTransitions.includes(record4.type)) {
10301
- result.edit.allowedStages = app ? applicationActionStages(app, env, ctx).map((id6) => ({ id: id6, label: id6.replaceAll("_", " ") })) : [];
10674
+ result.edit.allowedStages = app ? applicationActionStages(app, env, ctx).map((id6) => ({ id: id6, label: id6.replaceAll("_", " ") })) : result.edit.allowedStages.filter((stage) => !isApplicationStage(ctx, stage.id));
10302
10675
  const lock = (await db.query({ crm_activity: { $: { where: { id: `network-action-lock:${record4.id}` }, limit: 1 } } })).crm_activity?.[0];
10303
10676
  const meta = lock?.meta;
10304
10677
  if (meta?.state === "pending" && typeof meta.activityId === "string") {
@@ -10516,7 +10889,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
10516
10889
  }
10517
10890
  };
10518
10891
  try {
10519
- if (hasStage && ctx.chapter.mode === "chapter" && record4.type === "person") {
10892
+ if (hasStage && ctx.chapter.mode === "chapter" && record4.type === "person" && (record4.fields.applicationId || isApplicationStage(ctx, input.stage))) {
10520
10893
  const updated2 = await networkApplicationAction({
10521
10894
  db,
10522
10895
  env,
@@ -11876,7 +12249,7 @@ async function revealNetworkPersonRegistryRecord(db, input, mutationId, actor, n
11876
12249
  }
11877
12250
 
11878
12251
  // src/worker-routes-network-person-registry.ts
11879
- var PATH2 = "/api/admin/network-person-registry";
12252
+ var PATH3 = "/api/admin/network-person-registry";
11880
12253
  var MAX_BODY2 = 8 * 1024 * 1024;
11881
12254
  var MUTATION_ID4 = /^[A-Za-z0-9_-]{16,128}$/;
11882
12255
  var positiveRevision = (value) => {
@@ -11901,7 +12274,7 @@ function registryError(error) {
11901
12274
  return null;
11902
12275
  }
11903
12276
  var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
11904
- if (url.pathname !== PATH2) return null;
12277
+ if (url.pathname !== PATH3) return null;
11905
12278
  if (!ctx.chapter.networkPersonRegistry) return json({ error: "not found" }, 404);
11906
12279
  if (req.method !== "GET" && req.method !== "POST") return json({ error: "method not allowed" }, 405);
11907
12280
  if (req.method === "POST" && Number(req.headers.get("content-length") ?? 0) > MAX_BODY2) {
@@ -13097,7 +13470,7 @@ var handleAdminBuilderLibrary = async (req, url, env, ctx) => {
13097
13470
  };
13098
13471
 
13099
13472
  // src/membership-cancel-authority.ts
13100
- import { stripeRequest as stripeRequest8 } from "@odla-ai/stripe";
13473
+ import { stripeRequest as stripeRequest9 } from "@odla-ai/stripe";
13101
13474
  async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
13102
13475
  membershipClock(now);
13103
13476
  const id6 = await networkMembershipId(target);
@@ -13108,14 +13481,14 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
13108
13481
  membershipFail("membership_cancellation_unavailable");
13109
13482
  }
13110
13483
  const path = `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`;
13111
- const read2 = await stripeRequest8({ secretKey: stripeSecret }, "GET", path);
13484
+ const read2 = await stripeRequest9({ secretKey: stripeSecret }, "GET", path);
13112
13485
  const sub = read2.body;
13113
13486
  if (!read2.ok || sub.id !== member.providerSubscriptionId || sub.customer !== member.providerCustomerId || sub.livemode !== (target.environment === "prod") || !Object.entries({
13114
13487
  ...stripePartitionMetadata(target),
13115
13488
  applicationId: target.applicationId
13116
13489
  }).every(([key3, value]) => sub.metadata?.[key3] === value)) membershipFail("membership_cancellation_provider_mismatch");
13117
13490
  if (sub.status !== "canceled" && sub.cancel_at_period_end !== true) {
13118
- const write = await stripeRequest8(
13491
+ const write = await stripeRequest9(
13119
13492
  { secretKey: stripeSecret },
13120
13493
  "POST",
13121
13494
  path,
@@ -13337,7 +13710,7 @@ async function refundNetworkMembership(db, input, secretKey, now) {
13337
13710
  }
13338
13711
 
13339
13712
  // src/membership-provider-lifecycle.ts
13340
- import { stripeRequest as stripeRequest9 } from "@odla-ai/stripe";
13713
+ import { stripeRequest as stripeRequest10 } from "@odla-ai/stripe";
13341
13714
 
13342
13715
  // src/membership-renewal-authority.ts
13343
13716
  async function reconcileMembershipRenewal(db, input, now) {
@@ -13495,7 +13868,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
13495
13868
  }
13496
13869
  if (fact.subscriptionId !== member.providerSubscriptionId || fact.objectKind !== "subscription") membershipFail("membership_billing_scope_mismatch");
13497
13870
  if (["refund_pending", "refunded", "declined", "canceled", "restarting"].includes(String(member.status))) return receipt();
13498
- const result = await stripeRequest9({ secretKey: sk }, "GET", `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`);
13871
+ const result = await stripeRequest10({ secretKey: sk }, "GET", `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`);
13499
13872
  const sub = result.body;
13500
13873
  if (!result.ok || sub.id !== member.providerSubscriptionId || sub.customer !== member.providerCustomerId || sub.livemode !== (input.environment === "prod") || typeof sub.cancel_at_period_end !== "boolean" || !["active", "trialing", "past_due", "unpaid", "canceled"].includes(sub.status) || !Object.entries({ ...stripePartitionMetadata(input), applicationId: input.applicationId }).every(([key3, value]) => sub.metadata?.[key3] === value)) {
13501
13874
  membershipFail("membership_billing_provider_mismatch");
@@ -13610,7 +13983,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
13610
13983
  }
13611
13984
 
13612
13985
  // src/membership-seat-stripe.ts
13613
- import { stripeRequest as stripeRequest10 } from "@odla-ai/stripe";
13986
+ import { stripeRequest as stripeRequest11 } from "@odla-ai/stripe";
13614
13987
  var reject7 = () => {
13615
13988
  throw Object.assign(new Error("named seat provider contract mismatch"), { code: "named_seat_provider_mismatch" });
13616
13989
  };
@@ -13622,7 +13995,7 @@ var metadataFor = (seat) => ({
13622
13995
  });
13623
13996
  var matches = (value, expected) => value && Object.entries(expected).every(([key3, item]) => value[key3] === item);
13624
13997
  async function read(sk, path, params) {
13625
- const response = await stripeRequest10({ secretKey: sk }, "GET", path, params);
13998
+ const response = await stripeRequest11({ secretKey: sk }, "GET", path, params);
13626
13999
  if (!response.ok) return reject7();
13627
14000
  return response.body;
13628
14001
  }
@@ -13659,11 +14032,11 @@ async function createNamedSeatStripePayment(sk, seat, now) {
13659
14032
  }
13660
14033
  await subscriptionForSeat(sk, seat);
13661
14034
  const metadata = metadataFor(seat);
13662
- const result = seat.providerPaymentIntentId ? await stripeRequest10(
14035
+ const result = seat.providerPaymentIntentId ? await stripeRequest11(
13663
14036
  { secretKey: sk },
13664
14037
  "GET",
13665
14038
  `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`
13666
- ) : await stripeRequest10({ secretKey: sk }, "POST", "/v1/payment_intents", {
14039
+ ) : await stripeRequest11({ secretKey: sk }, "POST", "/v1/payment_intents", {
13667
14040
  amount: seat.amountCents,
13668
14041
  currency: seat.currency,
13669
14042
  customer: seat.providerCustomerId,
@@ -13681,7 +14054,7 @@ async function attachNamedSeatStripeRenewal(sk, seat) {
13681
14054
  const sub = await subscriptionForSeat(sk, seat);
13682
14055
  const existing = sub.items.data.find((item2) => item2.metadata?.seatId === seat.id);
13683
14056
  if (existing) return String(existing.id);
13684
- const result = await stripeRequest10({ secretKey: sk }, "POST", "/v1/subscription_items", {
14057
+ const result = await stripeRequest11({ secretKey: sk }, "POST", "/v1/subscription_items", {
13685
14058
  subscription: seat.providerSubscriptionId,
13686
14059
  price: seat.priceId,
13687
14060
  quantity: 1,
@@ -13781,7 +14154,7 @@ async function confirmNamedMembershipSeatPayment(db, input, now) {
13781
14154
  membershipUpdate("networkSeat", input.seatId, {
13782
14155
  status: status2,
13783
14156
  purchasedAt: paidAt,
13784
- acceptanceEndsAt: Math.min(paidAt + 30 * 864e5, Number(member.termEndsAt)),
14157
+ acceptanceEndsAt: Number(member.termEndsAt),
13785
14158
  termStartedAt: paidAt,
13786
14159
  termEndsAt: member.termEndsAt,
13787
14160
  paymentEventId: input.eventId,
@@ -13829,7 +14202,7 @@ async function completeNamedSeatRenewal(db, seatId, secretKey, now) {
13829
14202
  if (seat.status !== "renewal_pending" || !seat.purchasedAt) membershipFail("named_seat_renewal_not_ready");
13830
14203
  const member = await membershipLoad(db, "networkMembership", target.primaryMembershipId);
13831
14204
  requireMembershipScope(member, target);
13832
- 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");
14205
+ 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");
13833
14206
  const itemId = await attachNamedSeatStripeRenewal(secretKey, target);
13834
14207
  const entitlementId = `${seatId}:entitlement`, effectId = `${seatId}:attach-renewal`;
13835
14208
  try {
@@ -13904,7 +14277,7 @@ async function recordRecipient(db, input, now, approve) {
13904
14277
  requireMembershipScope(entitlement, input);
13905
14278
  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");
13906
14279
  if (seat.acceptedAt === void 0) {
13907
- if (approve || now >= Number(seat.acceptanceEndsAt) || normalizeChapterEmail(input.verifiedRecipientEmail) !== seat.recipientEmailNormalized) {
14280
+ if (approve || normalizeChapterEmail(input.verifiedRecipientEmail) !== seat.recipientEmailNormalized) {
13908
14281
  membershipFail("named_seat_recipient_verification_required");
13909
14282
  }
13910
14283
  } else if (seat.recipientUserId !== input.recipientUserId || seat.recipientApplicationId !== input.recipientApplicationId) {
@@ -13954,129 +14327,6 @@ async function recordRecipient(db, input, now, approve) {
13954
14327
  return membershipFail("named_seat_recipient_retry_required");
13955
14328
  }
13956
14329
 
13957
- // src/membership-seat-refund-stripe.ts
13958
- import { stripeRequest as stripeRequest11 } from "@odla-ai/stripe";
13959
- var fail6 = (code = "named_seat_refund_mismatch") => {
13960
- throw Object.assign(new Error(code), { code });
13961
- };
13962
- async function refundNamedSeatStripePayment(secretKey, seat) {
13963
- if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail6();
13964
- const call = async (method, path, params, idempotencyKey) => {
13965
- const result = await stripeRequest11({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
13966
- if (!result.ok || !result.body) return fail6("named_seat_refund_provider_unavailable");
13967
- return result.body;
13968
- };
13969
- const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
13970
- const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`);
13971
- if (payment.id !== seat.providerPaymentIntentId || payment.customer !== seat.providerCustomerId || payment.amount !== seat.amountCents || payment.amount_received !== seat.amountCents || payment.currency !== seat.currency || payment.livemode !== (seat.environment === "prod") || payment.status !== "succeeded" || !Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === value)) fail6();
13972
- const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
13973
- if (sub.id !== seat.providerSubscriptionId || sub.customer !== seat.providerCustomerId || sub.livemode !== (seat.environment === "prod") || !Object.entries({ ...stripePartitionMetadata(seat), applicationId: seat.applicationId }).every(([key3, value]) => sub.metadata?.[key3] === value) || sub.items?.has_more !== false || !Array.isArray(sub.items?.data)) fail6();
13974
- const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
13975
- if (items.length > 1) fail6();
13976
- if (items.length && sub.status !== "canceled") {
13977
- const item = items[0];
13978
- if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail6();
13979
- const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
13980
- if (removed.id !== item.id || removed.deleted !== true) fail6("named_seat_renewal_removal_unconfirmed");
13981
- }
13982
- const list = await call("GET", "/v1/refunds", { payment_intent: seat.providerPaymentIntentId, limit: 100 });
13983
- if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail6("named_seat_refund_history_incomplete");
13984
- let refundedCents = 0, pendingCents = 0;
13985
- const refundIds = [];
13986
- for (const refund of list.data) {
13987
- if (!/^re_[A-Za-z0-9_]+$/.test(refund.id) || refund.payment_intent !== seat.providerPaymentIntentId || refund.currency !== seat.currency || !Number.isSafeInteger(refund.amount) || refund.amount <= 0 || !["succeeded", "pending", "requires_action", "failed", "canceled"].includes(refund.status)) fail6();
13988
- if (refund.status === "succeeded") {
13989
- refundedCents += refund.amount;
13990
- refundIds.push(refund.id);
13991
- } else if (refund.status === "pending" || refund.status === "requires_action") pendingCents += refund.amount;
13992
- }
13993
- if (refundedCents + pendingCents > seat.amountCents) fail6();
13994
- if (pendingCents) fail6("named_seat_refund_pending");
13995
- if (refundedCents < seat.amountCents) {
13996
- const remaining = seat.amountCents - refundedCents;
13997
- const refund = await call("POST", "/v1/refunds", {
13998
- payment_intent: seat.providerPaymentIntentId,
13999
- amount: remaining,
14000
- reason: "requested_by_customer",
14001
- metadata
14002
- }, `named-seat-refund:${seat.id}:${remaining}`);
14003
- if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail6();
14004
- if (refund.status !== "succeeded") fail6("named_seat_refund_pending");
14005
- refundedCents += remaining;
14006
- refundIds.push(refund.id);
14007
- }
14008
- return { refundedCents, refundIds };
14009
- }
14010
-
14011
- // src/membership-seat-refund.ts
14012
- async function requestNamedSeatRefund(db, input, now) {
14013
- membershipClock(now);
14014
- const state = await loadMembershipAuthorityState(db, input);
14015
- const seat = await membershipLoad(db, "networkSeat", input.seatId);
14016
- requireMembershipScope(seat, input);
14017
- const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
14018
- requireMembershipScope(member, input);
14019
- if (seat.status === "refunded" || seat.status === "refund_pending") return;
14020
- 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");
14021
- const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
14022
- await db.transact([
14023
- membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
14024
- ...entitlement ? [membershipUpdate("networkEntitlement", String(entitlement.id), { status: "closed", revision: Number(entitlement.revision) + 1, updatedAt: now })] : [],
14025
- membershipUpdate("membershipEffect", `${seat.id}:refund`, {
14026
- id: `${seat.id}:refund`,
14027
- chapterId: input.chapterId,
14028
- environment: input.environment,
14029
- runtime: input.runtime,
14030
- membershipId: member.id,
14031
- seatId: seat.id,
14032
- kind: "refund_seat",
14033
- status: "pending",
14034
- attempts: 0,
14035
- createdAt: now,
14036
- updatedAt: now
14037
- }),
14038
- ...seat.recipientUserId ? [membershipUpdate("membershipEffect", `${seat.id}:revoke`, {
14039
- id: `${seat.id}:revoke`,
14040
- chapterId: input.chapterId,
14041
- environment: input.environment,
14042
- runtime: input.runtime,
14043
- membershipId: member.id,
14044
- seatId: seat.id,
14045
- kind: "revoke_named_recipient_access",
14046
- status: "pending",
14047
- attempts: 0,
14048
- createdAt: now,
14049
- updatedAt: now
14050
- })] : []
14051
- ], { mutationId: `named-seat-refund-request:${seat.id}`, guards: [
14052
- { ns: "networkSeat", id: input.seatId, exists: true, equals: { revision: seat.revision } },
14053
- { ns: "networkMembership", id: String(member.id), exists: true, equals: { revision: member.revision } },
14054
- { ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
14055
- ...entitlement ? [{ ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision } }] : []
14056
- ] });
14057
- }
14058
- async function completeNamedSeatRefund(db, seatId, secretKey, now) {
14059
- membershipClock(now);
14060
- const seat = await membershipLoad(db, "networkSeat", seatId);
14061
- if (!seat) return membershipFail("named_seat_not_found");
14062
- await loadMembershipAuthorityState(db, seat);
14063
- if (seat.status === "refunded") return;
14064
- if (seat.status !== "refund_pending") membershipFail("named_seat_refund_not_ready");
14065
- const result = await refundNamedSeatStripePayment(secretKey, seat);
14066
- await db.transact([
14067
- membershipUpdate("networkSeat", seatId, {
14068
- status: "refunded",
14069
- refundedCents: result.refundedCents,
14070
- providerRefundIds: result.refundIds,
14071
- revision: Number(seat.revision) + 1,
14072
- updatedAt: now
14073
- }),
14074
- membershipUpdate("membershipEffect", `${seatId}:refund`, { status: "complete", updatedAt: now })
14075
- ], { mutationId: `named-seat-refunded:${seatId}`, guards: [
14076
- { ns: "networkSeat", id: seatId, exists: true, equals: { status: "refund_pending", revision: seat.revision } }
14077
- ] });
14078
- }
14079
-
14080
14330
  // src/membership-seat-provider-refund.ts
14081
14331
  async function confirmNamedSeatProviderRefund(db, input, now) {
14082
14332
  const seat = await membershipLoad(db, "networkSeat", input.seatId);
@@ -14098,7 +14348,7 @@ async function readNamedSeatInvitation(db, input, now) {
14098
14348
  await loadMembershipAuthorityState(db, target);
14099
14349
  const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
14100
14350
  requireMembershipScope(member, target);
14101
- 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");
14351
+ 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");
14102
14352
  return {
14103
14353
  seatId: input.seatId,
14104
14354
  primaryApplicationId: target.applicationId,
@@ -14128,7 +14378,7 @@ async function readMembershipChapterEffect(db, input, now) {
14128
14378
  requireMembershipScope(member, input);
14129
14379
  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");
14130
14380
  let applicable;
14131
- 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));
14381
+ 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));
14132
14382
  else if (input.kind === "revoke_named_recipient_access") applicable = ["refund_pending", "refunded"].includes(String(seat.status));
14133
14383
  else applicable = (await readNamedSeatAccess(db, {
14134
14384
  ...input,
@@ -14157,6 +14407,7 @@ var MEMBERSHIP_SEAT_ACTION_FIELDS = {
14157
14407
  "seat-reservations": ["recipientName", "recipientEmail", "purchaserEmail", "purchaserUserId"],
14158
14408
  "seat-payments": ["seatId", "eventId"],
14159
14409
  "seat-refunds": ["seatId", "eventId"],
14410
+ "seat-operator-refund": ["seatId"],
14160
14411
  "seat-accept": recipient,
14161
14412
  "seat-approve": recipient,
14162
14413
  "seat-decline": recipient,
@@ -14225,6 +14476,7 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
14225
14476
  if (refunded?.status !== "refunded" || refunded.refundedCents !== seat.amountCents) throw new Error("named seat refund unconfirmed");
14226
14477
  return { seatId: seat.id, status: "refunded", refundedCents: refunded.refundedCents, subscriptionCanceled: false };
14227
14478
  }
14479
+ if (action === "seat-operator-refund") return refundNamedSeatByOperator(db, { ...target, seatId: String(input.seatId) }, secret, now);
14228
14480
  if (action === "seat-checkouts") return prepareNamedMembershipSeatPayment(db, {
14229
14481
  ...target,
14230
14482
  priceId: peer.seatPriceId,
@@ -14313,7 +14565,7 @@ function createMembershipAuthorityHandler(options) {
14313
14565
  if (req.method !== "POST") return respond({ error: "method_not_allowed" }, 405);
14314
14566
  const sender = req.headers.get(FEDERATION_HEADERS2.sender);
14315
14567
  const action = url.pathname.split("/").at(-1);
14316
- if (!["offers", "checkouts", "payments", "approvals", "refunds", "renewals", "access", "restarts", "cancel-renewal", ...Object.keys(MEMBERSHIP_SEAT_ACTION_FIELDS)].includes(action ?? "")) return respond({ error: "unauthorized" }, 401);
14568
+ 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);
14317
14569
  const peer = peers.find((candidate) => candidate.appId === sender && url.pathname === `${MEMBERSHIP_AUTHORITY_PATH}/${candidate.runtime}/${action}` && !url.search);
14318
14570
  if (!peer) return respond({ error: "unauthorized" }, 401);
14319
14571
  let body;
@@ -14335,13 +14587,23 @@ function createMembershipAuthorityHandler(options) {
14335
14587
  let input;
14336
14588
  try {
14337
14589
  input = JSON.parse(verified.body);
14338
- 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] ?? []];
14590
+ 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] ?? []];
14339
14591
  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])))) {
14340
14592
  throw new TypeError("invalid request");
14341
14593
  }
14342
14594
  } catch {
14343
14595
  return respond({ error: "invalid_offer_request" }, 400);
14344
14596
  }
14597
+ if (action === "offer-preview") {
14598
+ return respond({ ok: true, preview: await previewMembershipCommercialOffer(db, {
14599
+ appId: peer.appId,
14600
+ chapterId: peer.chapterId,
14601
+ environment: peer.environment,
14602
+ runtime: peer.runtime,
14603
+ skuId: String(input.skuId),
14604
+ now: options.now?.() ?? Date.now()
14605
+ }) });
14606
+ }
14345
14607
  if (Object.hasOwn(MEMBERSHIP_SEAT_ACTION_FIELDS, action)) {
14346
14608
  return respond({ ok: true, seat: await handleMembershipSeatAction(
14347
14609
  action,
@@ -14735,7 +14997,7 @@ async function drainMembershipEffects(db, options, now) {
14735
14997
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !options.stripeSecretKey) throw new TypeError("invalid membership recovery configuration");
14736
14998
  await loadMembershipAuthorityState(db, scope2);
14737
14999
  const matches2 = (row2) => Object.entries(scope2).every(([key3, value]) => row2[key3] === value);
14738
- let expired = 0, pending = 0, complete = 0, more = false;
15000
+ let pending = 0, complete = 0, more = false;
14739
15001
  for (const status2 of ["payment_pending", "payment_processing", "payment_canceling"]) {
14740
15002
  const seats = (await db.query({ networkSeat: { $: { where: { ...scope2, status: status2 }, order: { updatedAt: "asc" }, limit: limit + 1 } } })).networkSeat ?? [];
14741
15003
  more ||= seats.length > limit;
@@ -14753,26 +15015,6 @@ async function drainMembershipEffects(db, options, now) {
14753
15015
  }
14754
15016
  }
14755
15017
  }
14756
- for (const status2 of ["renewal_pending", "pending_acceptance"]) {
14757
- const seats = (await db.query({ networkSeat: { $: { where: { ...scope2, status: status2 }, order: { acceptanceEndsAt: "asc" }, limit: limit + 1 } } })).networkSeat ?? [];
14758
- more ||= seats.length > limit;
14759
- for (const seat of seats.slice(0, limit)) {
14760
- if (!matches2(seat)) throw new Error("membership recovery scope mismatch");
14761
- if (seat.acceptedAt !== void 0 || now < Number(seat.acceptanceEndsAt)) continue;
14762
- try {
14763
- await requestNamedSeatRefund(db, {
14764
- ...scope2,
14765
- applicationId: String(seat.applicationId),
14766
- skuId: String(seat.skuId),
14767
- seatId: String(seat.id),
14768
- reason: "expired"
14769
- }, now);
14770
- expired++;
14771
- } catch {
14772
- pending++;
14773
- }
14774
- }
14775
- }
14776
15018
  const effects = (await db.query({ membershipEffect: { $: { where: {
14777
15019
  chapterId: scope2.chapterId,
14778
15020
  environment: scope2.environment,
@@ -14834,7 +15076,7 @@ async function drainMembershipEffects(db, options, now) {
14834
15076
  pending++;
14835
15077
  }
14836
15078
  }
14837
- return { complete, pending, expired, more };
15079
+ return { complete, pending, more };
14838
15080
  }
14839
15081
 
14840
15082
  // src/approval-effects-retry.ts
@@ -14984,7 +15226,7 @@ function createMembershipChapterEffectRoute(options) {
14984
15226
  config.emailTemplates = { ...config.emailTemplates, namedSeatInvitation: config.emailTemplates?.namedSeatInvitation ?? {
14985
15227
  enabled: true,
14986
15228
  subject: "{{purchaserName}} invited you to {{chapterName}}",
14987
- text: "Hello {{firstName}},\n\n{{purchaserName}} has purchased a membership for you at {{chapterName}}. Your seat is already paid for.\n\nAccept your invitation by {{acceptBy}}:\n{{joinUrl}}\n\nYou will complete your own application and membership requirements. You will not be asked to pay for this seat."
15229
+ text: "Hello {{firstName}},\n\n{{purchaserName}} has purchased a membership for you at {{chapterName}}. Your seat is already paid for.\n\nAccept your invitation here:\n{{joinUrl}}\n\nYou will complete your own application and membership requirements. You will not be asked to pay for this seat."
14988
15230
  } };
14989
15231
  const template = config.emailTemplates.namedSeatInvitation;
14990
15232
  if (!template.text.includes("{{purchaserName}}")) config.emailTemplates.namedSeatInvitation = {
@@ -15057,6 +15299,12 @@ async function requestMembershipCommercialOffer(options) {
15057
15299
  lifetimeMs: 6e4
15058
15300
  });
15059
15301
  }
15302
+ async function requestMembershipOfferPreview(options) {
15303
+ const result = await requestAuthority(options, "offer-preview", { skuId: options.skuId });
15304
+ const preview = result.preview;
15305
+ 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");
15306
+ return preview;
15307
+ }
15060
15308
  async function requestMembershipCheckoutBinding(options) {
15061
15309
  const result = await requestAuthority(options, "checkouts", {
15062
15310
  applicationId: options.applicationId,
@@ -15095,6 +15343,12 @@ async function requestNamedSeatDecline(options) {
15095
15343
  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");
15096
15344
  return seat;
15097
15345
  }
15346
+ async function requestNamedSeatOperatorRefund(options) {
15347
+ const result = await requestAuthority(options, "seat-operator-refund", { applicationId: options.applicationId, skuId: options.skuId, seatId: options.seatId });
15348
+ const seat = result.seat;
15349
+ 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");
15350
+ return seat;
15351
+ }
15098
15352
  async function requestNamedSeatReservation(options) {
15099
15353
  const result = await requestAuthority(options, "seat-reservations", {
15100
15354
  applicationId: options.applicationId,
@@ -15280,6 +15534,7 @@ function createMembershipWorkerAdapter(options) {
15280
15534
  return {
15281
15535
  authority: {
15282
15536
  issueOffer: async (input, env) => requestMembershipCommercialOffer({ ...input, ...await transport(input, env) }),
15537
+ previewOffer: async (input, env) => requestMembershipOfferPreview({ ...input, ...await transport(input, env) }),
15283
15538
  bindCheckout: async (input, env) => requestMembershipCheckoutBinding({ ...input, ...await transport(input, env) }),
15284
15539
  confirmPayment: async (input, env) => requestMembershipPayment({ ...input, ...await transport(input, env) }),
15285
15540
  approveMembership: async (input, env) => requestMembershipApproval({ ...input, ...await transport(input, env) }),
@@ -15298,7 +15553,8 @@ function createMembershipWorkerAdapter(options) {
15298
15553
  confirmRefund: async (input, env) => requestNamedSeatRefund2({ ...input, ...await transport(input, env) }),
15299
15554
  accept: async (input, env) => requestNamedSeatAcceptance({ ...input, ...await transport(input, env) }),
15300
15555
  approve: async (input, env) => requestNamedSeatApproval({ ...input, ...await transport(input, env) }),
15301
- decline: async (input, env) => requestNamedSeatDecline({ ...input, ...await transport(input, env) })
15556
+ decline: async (input, env) => requestNamedSeatDecline({ ...input, ...await transport(input, env) }),
15557
+ operatorRefund: async (input, env) => requestNamedSeatOperatorRefund({ ...input, ...await transport(input, env) })
15302
15558
  } } : {}
15303
15559
  },
15304
15560
  readEffect: async (input, env) => requestMembershipChapterEffect({ ...input, ...await transport(input, env) })
@@ -15553,6 +15809,8 @@ var BUILTIN_ROUTES = [
15553
15809
  handleMembershipRestart,
15554
15810
  handleMembershipCancellation,
15555
15811
  handlePaymentRecovery,
15812
+ handleAdminNamedSeat,
15813
+ handleAdminMembershipSeats,
15556
15814
  handleAdminMeetings,
15557
15815
  handleAdminScheduling,
15558
15816
  // Roster + identity
@@ -15633,6 +15891,7 @@ export {
15633
15891
  deliverMembershipChapterEffect,
15634
15892
  deliverSignupControl,
15635
15893
  drainMembershipEffects,
15894
+ refundNamedSeatByOperator,
15636
15895
  renderActiveSignupControlHtml,
15637
15896
  resolveNetworkTargetTransport,
15638
15897
  retryChapterApprovalEffects,