@odla-ai/chapter 0.49.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -2
- package/dist/{chunk-KXIGZWG5.js → chunk-4EA5V4JE.js} +855 -614
- package/dist/chunk-4EA5V4JE.js.map +1 -0
- package/dist/{chunk-N2JIKINY.js → chunk-JXX4BFDP.js} +5 -2
- package/dist/chunk-JXX4BFDP.js.map +1 -0
- package/dist/{chunk-KHDO2QFA.js → chunk-OS46ZE46.js} +40 -24
- package/dist/chunk-OS46ZE46.js.map +1 -0
- package/dist/{copy-context-By4Nq7yB.d.ts → copy-context-Ddat__H5.d.ts} +10 -2
- package/dist/index.cjs +106 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -6
- package/dist/index.d.ts +52 -6
- package/dist/index.js +106 -43
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +54 -6
- package/dist/ui/admin/index.js +10 -2
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +11 -3
- package/dist/ui/member/index.d.ts +10 -2
- package/dist/ui/member/index.js +2 -2
- package/dist/worker/index.cjs +520 -261
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +81 -5
- package/dist/worker/index.d.ts +81 -5
- package/dist/worker/index.js +514 -255
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-KHDO2QFA.js.map +0 -1
- package/dist/chunk-KXIGZWG5.js.map +0 -1
- package/dist/chunk-N2JIKINY.js.map +0 -1
package/dist/worker/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __export(worker_exports, {
|
|
|
36
36
|
deliverMembershipChapterEffect: () => deliverMembershipChapterEffect,
|
|
37
37
|
deliverSignupControl: () => deliverSignupControl,
|
|
38
38
|
drainMembershipEffects: () => drainMembershipEffects,
|
|
39
|
+
refundNamedSeatByOperator: () => refundNamedSeatByOperator,
|
|
39
40
|
renderActiveSignupControlHtml: () => renderActiveSignupControlHtml,
|
|
40
41
|
resolveNetworkTargetTransport: () => resolveNetworkTargetTransport,
|
|
41
42
|
retryChapterApprovalEffects: () => retryChapterApprovalEffects,
|
|
@@ -375,6 +376,7 @@ function createWorkerContext(options) {
|
|
|
375
376
|
crmBase,
|
|
376
377
|
requirePaymentQuote: options.requirePaymentQuote === true,
|
|
377
378
|
membershipAuthority: options.membershipAuthority,
|
|
379
|
+
membershipAuthorityOperator: options.membershipAuthorityOperator,
|
|
378
380
|
getPublicConfig,
|
|
379
381
|
verifyUser,
|
|
380
382
|
makeDb,
|
|
@@ -1883,24 +1885,63 @@ async function verifyIssuedMembershipOffer(value, expected) {
|
|
|
1883
1885
|
if (!value || typeof value !== "object" || Array.isArray(value)) return reject("invalid_offer_snapshot");
|
|
1884
1886
|
return restore({ ...value, approvedFoundingCount: 0 }, expected);
|
|
1885
1887
|
}
|
|
1886
|
-
async function
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1888
|
+
async function previewMembershipCommercialOffer(db, input) {
|
|
1889
|
+
for (const key3 of ["appId", "chapterId", "environment", "runtime", "skuId"]) {
|
|
1890
|
+
if (typeof input[key3] !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.:-]{1,159}$/.test(input[key3])) reject("invalid_offer_identity");
|
|
1891
|
+
}
|
|
1892
|
+
if (!Number.isSafeInteger(input.now) || input.now < 0) reject("invalid_offer_window");
|
|
1893
|
+
const state = await authoritativeState(db, input);
|
|
1894
|
+
const { offering, policy } = await currentPolicy(db, input, false, state);
|
|
1895
|
+
const approvedFoundingCount = policy ? state.cohortCounts[policy.cohortLabel] ?? 0 : 0;
|
|
1896
|
+
if (!Number.isSafeInteger(approvedFoundingCount) || Number(approvedFoundingCount) < 0) reject("invalid_cohort_count");
|
|
1897
|
+
const contract = createMembershipCommercialOffer({
|
|
1898
|
+
offering,
|
|
1899
|
+
discountPolicy: policy,
|
|
1900
|
+
approvedFoundingCount: Number(approvedFoundingCount),
|
|
1901
|
+
now: input.now,
|
|
1902
|
+
couponId: await couponId(input, policy?.id)
|
|
1903
|
+
});
|
|
1904
|
+
const discount = contract.billingTerms.discount;
|
|
1905
|
+
return {
|
|
1906
|
+
skuId: input.skuId,
|
|
1907
|
+
offeringVersionId: offering.id,
|
|
1908
|
+
basePriceCents: offering.basePriceCents,
|
|
1909
|
+
currency: offering.currency,
|
|
1910
|
+
interval: offering.interval,
|
|
1911
|
+
founding: contract.founding,
|
|
1912
|
+
...contract.founding && contract.discountPolicy ? { cohortLabel: contract.discountPolicy.cohortLabel } : {},
|
|
1913
|
+
dueTodayCents: contract.dueTodayCents,
|
|
1914
|
+
renewalCents: contract.renewalCents,
|
|
1915
|
+
...discount ? { discountDuration: discount.duration === "once" ? "first_term" : "while_active" } : {},
|
|
1916
|
+
...contract.initialTermEndsAt !== void 0 ? { initialTermEndsAt: contract.initialTermEndsAt } : {}
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
async function authoritativeState(db, input) {
|
|
1920
|
+
const state = await load(db, "membershipAuthorityState", `${input.chapterId}:${input.environment}`);
|
|
1890
1921
|
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");
|
|
1891
|
-
|
|
1892
|
-
|
|
1922
|
+
return state;
|
|
1923
|
+
}
|
|
1924
|
+
async function currentPolicy(db, input, restartAware, state) {
|
|
1893
1925
|
const rows5 = await db.query({
|
|
1894
1926
|
membershipOfferingVersion: { $: { where: { chapterId: input.chapterId }, limit: 200 } },
|
|
1895
1927
|
discountPolicyVersion: { $: { where: { chapterId: input.chapterId }, limit: 200 } }
|
|
1896
1928
|
});
|
|
1897
1929
|
if (!Array.isArray(rows5.membershipOfferingVersion) || !Array.isArray(rows5.discountPolicyVersion) || rows5.membershipOfferingVersion.length >= 200 || rows5.discountPolicyVersion.length >= 200) return reject("policy_history_incomplete");
|
|
1898
|
-
const offerings = rows5.membershipOfferingVersion.map((
|
|
1899
|
-
const policies = rows5.discountPolicyVersion.map((
|
|
1930
|
+
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));
|
|
1931
|
+
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);
|
|
1900
1932
|
const offering = offerings.sort((a, b) => b.version - a.version)[0];
|
|
1901
|
-
const restart = await load(db, "membershipRestart", (await membershipOfferIdForRequest({ ...input, requestId: "checkout_binding" })).replace(/^offer_/, "membership_"));
|
|
1933
|
+
const restart = restartAware && input.applicationId ? await load(db, "membershipRestart", (await membershipOfferIdForRequest({ ...input, applicationId: input.applicationId, requestId: "checkout_binding" })).replace(/^offer_/, "membership_")) : void 0;
|
|
1902
1934
|
const policy = restart ? void 0 : policies.sort((a, b) => b.version - a.version)[0];
|
|
1903
1935
|
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");
|
|
1936
|
+
return { offering, policy };
|
|
1937
|
+
}
|
|
1938
|
+
async function issueMembershipCommercialOffer(db, input) {
|
|
1939
|
+
validate(input);
|
|
1940
|
+
const stateId = `${input.chapterId}:${input.environment}`;
|
|
1941
|
+
const state = await authoritativeState(db, input);
|
|
1942
|
+
const existing = await load(db, "membershipCheckoutOffer", input.offerId);
|
|
1943
|
+
if (existing) return restore(existing, input);
|
|
1944
|
+
const { offering, policy } = await currentPolicy(db, input, true, state);
|
|
1904
1945
|
const approvedFoundingCount = policy ? state.cohortCounts[policy.cohortLabel] ?? 0 : 0;
|
|
1905
1946
|
if (!Number.isSafeInteger(approvedFoundingCount) || Number(approvedFoundingCount) < 0) reject("invalid_cohort_count");
|
|
1906
1947
|
const contract = createMembershipCommercialOffer({
|
|
@@ -2208,7 +2249,7 @@ function findTier(tiers, tierId) {
|
|
|
2208
2249
|
if (!tierId) return tiers.length === 1 ? tiers[0] : null;
|
|
2209
2250
|
return tiers.find((tier) => tier.id === tierId) ?? null;
|
|
2210
2251
|
}
|
|
2211
|
-
function joinConfigTiers(tiers, defaults = {}) {
|
|
2252
|
+
function joinConfigTiers(tiers, defaults = {}, founding = /* @__PURE__ */ new Map()) {
|
|
2212
2253
|
return tiers.map((tier) => ({
|
|
2213
2254
|
id: tier.id,
|
|
2214
2255
|
name: tier.name,
|
|
@@ -2216,7 +2257,8 @@ function joinConfigTiers(tiers, defaults = {}) {
|
|
|
2216
2257
|
currency: tier.currency ?? defaults.currency ?? "usd",
|
|
2217
2258
|
interval: tier.interval ?? defaults.interval ?? "year",
|
|
2218
2259
|
blurb: tier.blurb ?? "",
|
|
2219
|
-
free: tierIsFree(tier)
|
|
2260
|
+
free: tierIsFree(tier),
|
|
2261
|
+
...founding.has(tier.id) ? { founding: founding.get(tier.id) } : {}
|
|
2220
2262
|
}));
|
|
2221
2263
|
}
|
|
2222
2264
|
|
|
@@ -3451,6 +3493,267 @@ var handlePaymentRecovery = async (req, url, env, ctx) => {
|
|
|
3451
3493
|
}
|
|
3452
3494
|
};
|
|
3453
3495
|
|
|
3496
|
+
// src/worker-routes-admin-named-seat.ts
|
|
3497
|
+
var PATH = /^\/api\/admin\/applications\/([0-9a-f-]+)\/named-seat(\/refund)?$/;
|
|
3498
|
+
var REFUNDABLE = /* @__PURE__ */ new Set(["renewal_pending", "pending_acceptance", "pending_approval", "pending_primary", "active"]);
|
|
3499
|
+
var handleAdminNamedSeat = async (req, url, env, ctx) => {
|
|
3500
|
+
const match = url.pathname.match(PATH);
|
|
3501
|
+
if (!match) return null;
|
|
3502
|
+
const seats = ctx.membershipAuthority?.namedSeats;
|
|
3503
|
+
if (ctx.chapter.mode !== "chapter" || !seats) return json({ error: "named seats are not configured" }, 404);
|
|
3504
|
+
const refund = Boolean(match[2]);
|
|
3505
|
+
if (req.method !== (refund ? "POST" : "GET")) return json({ error: "method not allowed" }, 405);
|
|
3506
|
+
const raw2 = ctx.makeDb(env), user = await ctx.verifyUser(req, env);
|
|
3507
|
+
if (!user) return json({ error: "unauthorized" }, 401);
|
|
3508
|
+
if (!await ctx.isSuperAdminEmail(raw2, user.email, user.userId)) return json({ error: "super-admin required", code: "super_admin_required" }, 403);
|
|
3509
|
+
const db = raw2, app = await membershipLoad(db, "applications", match[1]);
|
|
3510
|
+
if (!app || !chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME) || app.namedSeatId) return json({ error: "not found" }, 404);
|
|
3511
|
+
const intent = await membershipLoad(db, "subscriptionCheckoutIntents", String(app.id));
|
|
3512
|
+
if (!intent) return json({ error: "paid membership required" }, 409);
|
|
3513
|
+
try {
|
|
3514
|
+
const target = await membershipTargetForIntent(intent, app, env, ctx);
|
|
3515
|
+
const offer = await seats.offer(target, env);
|
|
3516
|
+
const seat = offer.seat ?? null, refundable = Boolean(seat && REFUNDABLE.has(seat.status));
|
|
3517
|
+
if (!refund) {
|
|
3518
|
+
return json({
|
|
3519
|
+
seat,
|
|
3520
|
+
amountCents: offer.amountCents,
|
|
3521
|
+
currency: offer.currency,
|
|
3522
|
+
termEndsAt: offer.termEndsAt,
|
|
3523
|
+
purchaseEndsAt: offer.purchaseEndsAt,
|
|
3524
|
+
refundable
|
|
3525
|
+
});
|
|
3526
|
+
}
|
|
3527
|
+
if (!seat) return json({ error: "no purchased seat", code: "seat_not_found" }, 409);
|
|
3528
|
+
if (seat.status === "refunded") {
|
|
3529
|
+
await closeRecipientApplication(db, env, ctx, seat.seatId);
|
|
3530
|
+
return json({ ok: true, seatId: seat.seatId, status: "refunded", replayed: true });
|
|
3531
|
+
}
|
|
3532
|
+
if (!refundable) return json({ error: `seat is ${seat.status.replaceAll("_", " ")}`, code: "seat_not_refundable" }, 409);
|
|
3533
|
+
const receipt = await seats.operatorRefund({ ...target, seatId: seat.seatId }, env);
|
|
3534
|
+
await closeRecipientApplication(db, env, ctx, seat.seatId);
|
|
3535
|
+
return json({ ok: true, ...receipt });
|
|
3536
|
+
} catch {
|
|
3537
|
+
return json({ error: "named seat service unavailable; retry to confirm" }, 503);
|
|
3538
|
+
}
|
|
3539
|
+
};
|
|
3540
|
+
async function closeRecipientApplication(db, env, ctx, seatId) {
|
|
3541
|
+
const recipient2 = (await db.query({ applications: { $: { where: { namedSeatId: seatId }, limit: 1 } } })).applications?.[0];
|
|
3542
|
+
if (!recipient2 || !chapterBillingApplication(recipient2, ctx.chapter.id, env.ODLA_RUNTIME) || recipient2.status === "refunded") return;
|
|
3543
|
+
const attrs = { status: "refunded", renewalAt: 0, canceled: true };
|
|
3544
|
+
await db.transact(
|
|
3545
|
+
[{ t: "update", ns: "applications", id: String(recipient2.id), attrs }],
|
|
3546
|
+
{ mutationId: `verified-refund:${String(recipient2.id)}:${seatId}` }
|
|
3547
|
+
);
|
|
3548
|
+
await syncApplicationToCrm(crmDeps(db, ctx), { app: { ...recipient2, ...attrs }, stage: "refunded" }).catch(() => void 0);
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
// src/membership-seat-refund-stripe.ts
|
|
3552
|
+
var import_stripe6 = require("@odla-ai/stripe");
|
|
3553
|
+
var fail3 = (code = "named_seat_refund_mismatch") => {
|
|
3554
|
+
throw Object.assign(new Error(code), { code });
|
|
3555
|
+
};
|
|
3556
|
+
async function refundNamedSeatStripePayment(secretKey, seat) {
|
|
3557
|
+
if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail3();
|
|
3558
|
+
const call = async (method, path, params, idempotencyKey) => {
|
|
3559
|
+
const result = await (0, import_stripe6.stripeRequest)({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
|
|
3560
|
+
if (!result.ok || !result.body) return fail3("named_seat_refund_provider_unavailable");
|
|
3561
|
+
return result.body;
|
|
3562
|
+
};
|
|
3563
|
+
const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
|
|
3564
|
+
const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`);
|
|
3565
|
+
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();
|
|
3566
|
+
const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
|
|
3567
|
+
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();
|
|
3568
|
+
const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
|
|
3569
|
+
if (items.length > 1) fail3();
|
|
3570
|
+
if (items.length && sub.status !== "canceled") {
|
|
3571
|
+
const item = items[0];
|
|
3572
|
+
if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail3();
|
|
3573
|
+
const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
|
|
3574
|
+
if (removed.id !== item.id || removed.deleted !== true) fail3("named_seat_renewal_removal_unconfirmed");
|
|
3575
|
+
}
|
|
3576
|
+
const list = await call("GET", "/v1/refunds", { payment_intent: seat.providerPaymentIntentId, limit: 100 });
|
|
3577
|
+
if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail3("named_seat_refund_history_incomplete");
|
|
3578
|
+
let refundedCents = 0, pendingCents = 0;
|
|
3579
|
+
const refundIds = [];
|
|
3580
|
+
for (const refund of list.data) {
|
|
3581
|
+
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();
|
|
3582
|
+
if (refund.status === "succeeded") {
|
|
3583
|
+
refundedCents += refund.amount;
|
|
3584
|
+
refundIds.push(refund.id);
|
|
3585
|
+
} else if (refund.status === "pending" || refund.status === "requires_action") pendingCents += refund.amount;
|
|
3586
|
+
}
|
|
3587
|
+
if (refundedCents + pendingCents > seat.amountCents) fail3();
|
|
3588
|
+
if (pendingCents) fail3("named_seat_refund_pending");
|
|
3589
|
+
if (refundedCents < seat.amountCents) {
|
|
3590
|
+
const remaining = seat.amountCents - refundedCents;
|
|
3591
|
+
const refund = await call("POST", "/v1/refunds", {
|
|
3592
|
+
payment_intent: seat.providerPaymentIntentId,
|
|
3593
|
+
amount: remaining,
|
|
3594
|
+
reason: "requested_by_customer",
|
|
3595
|
+
metadata
|
|
3596
|
+
}, `named-seat-refund:${seat.id}:${remaining}`);
|
|
3597
|
+
if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail3();
|
|
3598
|
+
if (refund.status !== "succeeded") fail3("named_seat_refund_pending");
|
|
3599
|
+
refundedCents += remaining;
|
|
3600
|
+
refundIds.push(refund.id);
|
|
3601
|
+
}
|
|
3602
|
+
return { refundedCents, refundIds };
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3605
|
+
// src/membership-seat-refund.ts
|
|
3606
|
+
async function requestNamedSeatRefund(db, input, now) {
|
|
3607
|
+
membershipClock(now);
|
|
3608
|
+
const state = await loadMembershipAuthorityState(db, input);
|
|
3609
|
+
const seat = await membershipLoad(db, "networkSeat", input.seatId);
|
|
3610
|
+
requireMembershipScope(seat, input);
|
|
3611
|
+
const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
|
|
3612
|
+
requireMembershipScope(member, input);
|
|
3613
|
+
if (seat.status === "refunded" || seat.status === "refund_pending") return;
|
|
3614
|
+
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");
|
|
3615
|
+
const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
|
|
3616
|
+
await db.transact([
|
|
3617
|
+
membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
|
|
3618
|
+
...entitlement ? [membershipUpdate("networkEntitlement", String(entitlement.id), { status: "closed", revision: Number(entitlement.revision) + 1, updatedAt: now })] : [],
|
|
3619
|
+
membershipUpdate("membershipEffect", `${seat.id}:refund`, {
|
|
3620
|
+
id: `${seat.id}:refund`,
|
|
3621
|
+
chapterId: input.chapterId,
|
|
3622
|
+
environment: input.environment,
|
|
3623
|
+
runtime: input.runtime,
|
|
3624
|
+
membershipId: member.id,
|
|
3625
|
+
seatId: seat.id,
|
|
3626
|
+
kind: "refund_seat",
|
|
3627
|
+
status: "pending",
|
|
3628
|
+
attempts: 0,
|
|
3629
|
+
createdAt: now,
|
|
3630
|
+
updatedAt: now
|
|
3631
|
+
}),
|
|
3632
|
+
...seat.recipientUserId ? [membershipUpdate("membershipEffect", `${seat.id}:revoke`, {
|
|
3633
|
+
id: `${seat.id}:revoke`,
|
|
3634
|
+
chapterId: input.chapterId,
|
|
3635
|
+
environment: input.environment,
|
|
3636
|
+
runtime: input.runtime,
|
|
3637
|
+
membershipId: member.id,
|
|
3638
|
+
seatId: seat.id,
|
|
3639
|
+
kind: "revoke_named_recipient_access",
|
|
3640
|
+
status: "pending",
|
|
3641
|
+
attempts: 0,
|
|
3642
|
+
createdAt: now,
|
|
3643
|
+
updatedAt: now
|
|
3644
|
+
})] : []
|
|
3645
|
+
], { mutationId: `named-seat-refund-request:${seat.id}`, guards: [
|
|
3646
|
+
{ ns: "networkSeat", id: input.seatId, exists: true, equals: { revision: seat.revision } },
|
|
3647
|
+
{ ns: "networkMembership", id: String(member.id), exists: true, equals: { revision: member.revision } },
|
|
3648
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
|
|
3649
|
+
...entitlement ? [{ ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision } }] : []
|
|
3650
|
+
] });
|
|
3651
|
+
}
|
|
3652
|
+
async function completeNamedSeatRefund(db, seatId, secretKey, now) {
|
|
3653
|
+
membershipClock(now);
|
|
3654
|
+
const seat = await membershipLoad(db, "networkSeat", seatId);
|
|
3655
|
+
if (!seat) return membershipFail("named_seat_not_found");
|
|
3656
|
+
await loadMembershipAuthorityState(db, seat);
|
|
3657
|
+
if (seat.status === "refunded") return;
|
|
3658
|
+
if (seat.status !== "refund_pending") membershipFail("named_seat_refund_not_ready");
|
|
3659
|
+
const result = await refundNamedSeatStripePayment(secretKey, seat);
|
|
3660
|
+
await db.transact([
|
|
3661
|
+
membershipUpdate("networkSeat", seatId, {
|
|
3662
|
+
status: "refunded",
|
|
3663
|
+
refundedCents: result.refundedCents,
|
|
3664
|
+
providerRefundIds: result.refundIds,
|
|
3665
|
+
revision: Number(seat.revision) + 1,
|
|
3666
|
+
updatedAt: now
|
|
3667
|
+
}),
|
|
3668
|
+
membershipUpdate("membershipEffect", `${seatId}:refund`, { status: "complete", updatedAt: now })
|
|
3669
|
+
], { mutationId: `named-seat-refunded:${seatId}`, guards: [
|
|
3670
|
+
{ ns: "networkSeat", id: seatId, exists: true, equals: { status: "refund_pending", revision: seat.revision } }
|
|
3671
|
+
] });
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
// src/membership-seat-operator-refund.ts
|
|
3675
|
+
async function refundNamedSeatByOperator(db, input, stripeSecretKey, now) {
|
|
3676
|
+
const seat = await membershipLoad(db, "networkSeat", input.seatId);
|
|
3677
|
+
requireMembershipScope(seat, input);
|
|
3678
|
+
await requestNamedSeatRefund(db, { ...input, reason: "operator" }, now);
|
|
3679
|
+
await completeNamedSeatRefund(db, input.seatId, stripeSecretKey, now);
|
|
3680
|
+
const refunded = await membershipLoad(db, "networkSeat", input.seatId);
|
|
3681
|
+
if (refunded?.status !== "refunded" || !Number.isSafeInteger(refunded.refundedCents) || Number(refunded.refundedCents) <= 0) {
|
|
3682
|
+
throw new Error("named seat refund unconfirmed");
|
|
3683
|
+
}
|
|
3684
|
+
return { seatId: String(seat.id), status: "refunded", refundedCents: Number(refunded.refundedCents), subscriptionCanceled: false };
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3687
|
+
// src/worker-routes-admin-membership-seats.ts
|
|
3688
|
+
var LIST = "/api/admin/membership-seats";
|
|
3689
|
+
var ONE = /^\/api\/admin\/membership-seats\/([A-Za-z0-9][A-Za-z0-9_.:-]{2,159})\/refund$/;
|
|
3690
|
+
var PUBLIC = [
|
|
3691
|
+
"id",
|
|
3692
|
+
"appId",
|
|
3693
|
+
"chapterId",
|
|
3694
|
+
"environment",
|
|
3695
|
+
"runtime",
|
|
3696
|
+
"applicationId",
|
|
3697
|
+
"skuId",
|
|
3698
|
+
"status",
|
|
3699
|
+
"amountCents",
|
|
3700
|
+
"currency",
|
|
3701
|
+
"recipientName",
|
|
3702
|
+
"recipientEmailNormalized",
|
|
3703
|
+
"purchaserUserId",
|
|
3704
|
+
"purchasedAt",
|
|
3705
|
+
"acceptedAt",
|
|
3706
|
+
"journeyApprovedAt",
|
|
3707
|
+
"refundedCents",
|
|
3708
|
+
"refundReason",
|
|
3709
|
+
"termEndsAt",
|
|
3710
|
+
"updatedAt"
|
|
3711
|
+
];
|
|
3712
|
+
var project = (row2) => Object.fromEntries(PUBLIC.filter((key3) => row2[key3] !== void 0).map((key3) => [key3, row2[key3]]));
|
|
3713
|
+
var handleAdminMembershipSeats = async (req, url, env, ctx) => {
|
|
3714
|
+
const one = url.pathname.match(ONE);
|
|
3715
|
+
if (url.pathname !== LIST && !one) return null;
|
|
3716
|
+
const operator = ctx.membershipAuthorityOperator;
|
|
3717
|
+
if (!operator) return json({ error: "not found" }, 404);
|
|
3718
|
+
if (req.method !== (one ? "POST" : "GET")) return json({ error: "method not allowed" }, 405);
|
|
3719
|
+
const raw2 = ctx.makeDb(env), user = await ctx.verifyUser(req, env);
|
|
3720
|
+
if (!user) return json({ error: "unauthorized" }, 401);
|
|
3721
|
+
if (!await ctx.isSuperAdminEmail(raw2, user.email, user.userId)) return json({ error: "super-admin required", code: "super_admin_required" }, 403);
|
|
3722
|
+
let db;
|
|
3723
|
+
try {
|
|
3724
|
+
db = operator.makeDb(env);
|
|
3725
|
+
} catch {
|
|
3726
|
+
return json({ error: "the membership authority is not hosted by this deployment" }, 404);
|
|
3727
|
+
}
|
|
3728
|
+
if (!one) {
|
|
3729
|
+
const rows5 = (await db.query({ networkSeat: { $: { where: { environment: env.ODLA_ENV }, order: { updatedAt: "desc" }, limit: 201 } } })).networkSeat ?? [];
|
|
3730
|
+
return json({ seats: rows5.slice(0, 200).map(project), truncated: rows5.length > 200 });
|
|
3731
|
+
}
|
|
3732
|
+
const seat = await membershipLoad(db, "networkSeat", one[1]);
|
|
3733
|
+
if (!seat || seat.environment !== env.ODLA_ENV) return json({ error: "not found" }, 404);
|
|
3734
|
+
if (seat.status === "refunded") {
|
|
3735
|
+
return json({ ok: true, seatId: seat.id, status: "refunded", refundedCents: seat.refundedCents, subscriptionCanceled: false, replayed: true });
|
|
3736
|
+
}
|
|
3737
|
+
if (!seat.purchasedAt) return json({ error: "seat has no completed payment to refund", code: "seat_not_refundable" }, 409);
|
|
3738
|
+
const secret = await getVaultSecret(db, operator.stripeSecretName ?? "stripe_secret_key");
|
|
3739
|
+
if (!secret) return json({ error: "network payment provider unavailable" }, 503);
|
|
3740
|
+
try {
|
|
3741
|
+
const receipt = await refundNamedSeatByOperator(db, {
|
|
3742
|
+
appId: String(seat.appId),
|
|
3743
|
+
chapterId: String(seat.chapterId),
|
|
3744
|
+
environment: String(seat.environment),
|
|
3745
|
+
runtime: String(seat.runtime),
|
|
3746
|
+
applicationId: String(seat.applicationId),
|
|
3747
|
+
skuId: String(seat.skuId),
|
|
3748
|
+
seatId: String(seat.id)
|
|
3749
|
+
}, secret, Date.now());
|
|
3750
|
+
return json({ ok: true, ...receipt });
|
|
3751
|
+
} catch (error) {
|
|
3752
|
+
const code = error.code;
|
|
3753
|
+
return json({ error: "seat refund is pending; retry to confirm", ...code ? { code } : {} }, code ? 409 : 503);
|
|
3754
|
+
}
|
|
3755
|
+
};
|
|
3756
|
+
|
|
3454
3757
|
// src/worker-routes-membership-cancel.ts
|
|
3455
3758
|
var handleMembershipCancellation = async (req, url, env, ctx) => {
|
|
3456
3759
|
const match = url.pathname.match(/^\/api\/admin\/applications\/([0-9a-f-]+)\/cancel-renewal$/);
|
|
@@ -4047,6 +4350,29 @@ async function authorizeCrmDiscussionReference(req, env, fetcher = fetch) {
|
|
|
4047
4350
|
}
|
|
4048
4351
|
}
|
|
4049
4352
|
|
|
4353
|
+
// src/join-founding-preview.ts
|
|
4354
|
+
async function foundingPreviews(tiers, env, ctx) {
|
|
4355
|
+
const result = /* @__PURE__ */ new Map();
|
|
4356
|
+
const preview = ctx.membershipAuthority?.previewOffer;
|
|
4357
|
+
if (!preview) return result;
|
|
4358
|
+
const scope2 = { appId: env.ODLA_APP_ID, chapterId: ctx.chapter.id, environment: env.ODLA_ENV, runtime: stripeRuntimeIdentity(env.ODLA_RUNTIME) };
|
|
4359
|
+
await Promise.all(tiers.filter((tier) => !tierIsFree(tier) && tier.stripePriceId).map(async (tier) => {
|
|
4360
|
+
try {
|
|
4361
|
+
const offer = await preview({ ...scope2, skuId: tier.id }, env);
|
|
4362
|
+
if (!offer.founding || !offer.cohortLabel || offer.basePriceCents !== tier.priceCents || offer.dueTodayCents >= offer.basePriceCents) return;
|
|
4363
|
+
result.set(tier.id, {
|
|
4364
|
+
cohortLabel: offer.cohortLabel,
|
|
4365
|
+
dueTodayCents: offer.dueTodayCents,
|
|
4366
|
+
discountCents: offer.basePriceCents - offer.dueTodayCents,
|
|
4367
|
+
renewalCents: offer.renewalCents,
|
|
4368
|
+
...offer.discountDuration ? { discountDuration: offer.discountDuration } : {}
|
|
4369
|
+
});
|
|
4370
|
+
} catch {
|
|
4371
|
+
}
|
|
4372
|
+
}));
|
|
4373
|
+
return result;
|
|
4374
|
+
}
|
|
4375
|
+
|
|
4050
4376
|
// src/admission-grants-contract.ts
|
|
4051
4377
|
var ADMISSION_GRANTS_SCHEMA_VERSION = 1;
|
|
4052
4378
|
var ADMISSION_GRANTS_PATH = "/api/admission-grants";
|
|
@@ -4573,10 +4899,24 @@ async function materializeSignupControl(db, envelope, previous, receivedAt) {
|
|
|
4573
4899
|
}
|
|
4574
4900
|
|
|
4575
4901
|
// src/payment-quote.ts
|
|
4902
|
+
function tierPresentationFor(document, tierId) {
|
|
4903
|
+
const compareAt = document?.membershipPage?.tiers?.find((tier) => tier.tierId === tierId)?.compareAtPriceCents;
|
|
4904
|
+
return Number.isSafeInteger(compareAt) && Number(compareAt) > 0 ? { compareAtPriceCents: Number(compareAt) } : void 0;
|
|
4905
|
+
}
|
|
4906
|
+
function foundingOf(offer) {
|
|
4907
|
+
return offer?.contract?.founding === true && offer.contract.discountPolicy ? { cohortLabel: offer.contract.discountPolicy.cohortLabel } : void 0;
|
|
4908
|
+
}
|
|
4576
4909
|
async function createPaymentQuote(input) {
|
|
4577
4910
|
const { group, tier, policy } = input;
|
|
4578
4911
|
const terms2 = input.billingTerms ? subscriptionBillingTerms(input.billingTerms) : void 0;
|
|
4912
|
+
const compareAt = input.presentation?.compareAtPriceCents;
|
|
4913
|
+
const presentation = Number.isSafeInteger(compareAt) && Number(compareAt) > tier.priceCents ? { compareAtPriceCents: Number(compareAt) } : void 0;
|
|
4579
4914
|
const dueToday = terms2 ? subscriptionAmountDue({ priceCents: tier.priceCents, billingTerms: terms2 }) : void 0;
|
|
4915
|
+
const lines = dueToday === void 0 ? paymentLineItems(group, tier) : {
|
|
4916
|
+
standardCents: tier.priceCents,
|
|
4917
|
+
discountCents: tier.priceCents - dueToday,
|
|
4918
|
+
dueTodayCents: dueToday
|
|
4919
|
+
};
|
|
4580
4920
|
const quote = {
|
|
4581
4921
|
applicationId: input.applicationId,
|
|
4582
4922
|
chapterId: input.chapterId,
|
|
@@ -4584,11 +4924,12 @@ async function createPaymentQuote(input) {
|
|
|
4584
4924
|
tierName: tier.name,
|
|
4585
4925
|
currency: tier.currency ?? String(group.currency ?? "usd"),
|
|
4586
4926
|
interval: tier.interval ?? (group.interval === "month" ? "month" : "year"),
|
|
4587
|
-
lineItems:
|
|
4588
|
-
standardCents:
|
|
4589
|
-
discountCents:
|
|
4590
|
-
dueTodayCents:
|
|
4591
|
-
},
|
|
4927
|
+
lineItems: presentation ? {
|
|
4928
|
+
standardCents: presentation.compareAtPriceCents,
|
|
4929
|
+
discountCents: presentation.compareAtPriceCents - lines.dueTodayCents,
|
|
4930
|
+
dueTodayCents: lines.dueTodayCents
|
|
4931
|
+
} : lines,
|
|
4932
|
+
...presentation ? { presentation } : {},
|
|
4592
4933
|
refundPolicyText: policy?.refundPolicyText ?? String(group.refundPolicyText ?? ""),
|
|
4593
4934
|
merchantDisclosureText: String(group.merchantDisclosureText ?? ""),
|
|
4594
4935
|
trustCopy: policy ? policy.trustCopy ?? "" : String(group.trustCopy ?? ""),
|
|
@@ -4613,37 +4954,39 @@ async function createPaymentQuote(input) {
|
|
|
4613
4954
|
});
|
|
4614
4955
|
const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(contract));
|
|
4615
4956
|
const digest = [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
4616
|
-
return { ...quote, digest };
|
|
4957
|
+
return { ...quote, digest, ...input.founding ? { founding: input.founding } : {} };
|
|
4617
4958
|
}
|
|
4618
4959
|
|
|
4619
4960
|
// src/membership-quote-projection.ts
|
|
4620
4961
|
var MEMBERSHIP_QUOTE_NAMESPACE = "membershipQuoteProjections";
|
|
4621
4962
|
var keys2 = ["appId", "chapterId", "environment", "runtime", "applicationId", "skuId"];
|
|
4622
|
-
var
|
|
4963
|
+
var fail4 = (code) => {
|
|
4623
4964
|
throw Object.assign(new Error(code), { code });
|
|
4624
4965
|
};
|
|
4625
4966
|
var load3 = async (db, ns, id6) => (await db.query({ [ns]: { $: { where: { id: id6 }, limit: 1 } } }))[ns]?.[0];
|
|
4626
4967
|
async function restore2(row2, target, now, allowExpired = false) {
|
|
4627
|
-
if (!row2 || keys2.some((key3) => row2[key3] !== target[key3]) || !Number.isSafeInteger(row2.revision) || Number(row2.revision) < 1 || typeof row2.requestId !== "string") return
|
|
4628
|
-
if (row2.status !== "ready") return
|
|
4968
|
+
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");
|
|
4969
|
+
if (row2.status !== "ready") return fail4("membership_quote_pending");
|
|
4629
4970
|
const offer = await verifyIssuedMembershipOffer(row2.offer, { ...target, offerId: await membershipOfferIdForRequest({
|
|
4630
4971
|
...target,
|
|
4631
4972
|
requestId: row2.requestId
|
|
4632
4973
|
}), now, lifetimeMs: 6e4 });
|
|
4633
|
-
if (offer.issuedAt > now + 3e4 || !allowExpired && offer.expiresAt <= now) return
|
|
4974
|
+
if (offer.issuedAt > now + 3e4 || !allowExpired && offer.expiresAt <= now) return fail4("membership_quote_expired");
|
|
4634
4975
|
const tier = row2.tier, quote = row2.quote;
|
|
4635
4976
|
const offering = offer.contract.offering;
|
|
4636
4977
|
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) {
|
|
4637
|
-
return
|
|
4978
|
+
return fail4("membership_catalog_mismatch");
|
|
4638
4979
|
}
|
|
4639
4980
|
const expected = await createPaymentQuote({
|
|
4640
4981
|
...target,
|
|
4641
4982
|
tier,
|
|
4642
4983
|
group: { merchantDisclosureText: quote.merchantDisclosureText },
|
|
4643
4984
|
policy: { refundPolicyText: quote.refundPolicyText, trustCopy: quote.trustCopy },
|
|
4644
|
-
billingTerms: offer.contract.billingTerms
|
|
4985
|
+
billingTerms: offer.contract.billingTerms,
|
|
4986
|
+
presentation: quote.presentation,
|
|
4987
|
+
founding: foundingOf(offer)
|
|
4645
4988
|
});
|
|
4646
|
-
if (quote.digest !== expected.digest) return
|
|
4989
|
+
if (quote.digest !== expected.digest) return fail4("membership_quote_conflict");
|
|
4647
4990
|
return { tier, quote: expected, billingTerms: offer.contract.billingTerms, membershipOffer: offer, projectionRevision: Number(row2.revision) };
|
|
4648
4991
|
}
|
|
4649
4992
|
async function readMembershipQuote(db, target, now) {
|
|
@@ -4651,9 +4994,9 @@ async function readMembershipQuote(db, target, now) {
|
|
|
4651
4994
|
}
|
|
4652
4995
|
async function prepareMembershipQuote(db, input, issue) {
|
|
4653
4996
|
const { applicationId: id6, now, app } = input;
|
|
4654
|
-
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
|
|
4997
|
+
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");
|
|
4655
4998
|
let row2 = await load3(db, MEMBERSHIP_QUOTE_NAMESPACE, id6);
|
|
4656
|
-
if (row2 && keys2.some((key3) => row2[key3] !== input[key3])) return
|
|
4999
|
+
if (row2 && keys2.some((key3) => row2[key3] !== input[key3])) return fail4("membership_quote_conflict");
|
|
4657
5000
|
if (row2?.status === "ready") {
|
|
4658
5001
|
try {
|
|
4659
5002
|
return await restore2(row2, input, now);
|
|
@@ -4698,7 +5041,7 @@ async function prepareMembershipQuote(db, input, issue) {
|
|
|
4698
5041
|
}
|
|
4699
5042
|
if (row2?.status === "ready") return restore2(row2, input, now);
|
|
4700
5043
|
if (!row2 || row2.status !== "pending" || keys2.some((key3) => row2[key3] !== input[key3]) || typeof row2.requestId !== "string") {
|
|
4701
|
-
return
|
|
5044
|
+
return fail4("membership_quote_pending");
|
|
4702
5045
|
}
|
|
4703
5046
|
const target = Object.fromEntries(keys2.map((key3) => [key3, input[key3]]));
|
|
4704
5047
|
const offer = await verifyIssuedMembershipOffer(await issue({ ...target, requestId: row2.requestId }), {
|
|
@@ -4712,7 +5055,15 @@ async function prepareMembershipQuote(db, input, issue) {
|
|
|
4712
5055
|
currency: input.tier.currency ?? String(input.group.currency ?? "usd"),
|
|
4713
5056
|
interval: input.tier.interval ?? (input.group.interval === "month" ? "month" : "year")
|
|
4714
5057
|
};
|
|
4715
|
-
const quote = await createPaymentQuote({
|
|
5058
|
+
const quote = await createPaymentQuote({
|
|
5059
|
+
...target,
|
|
5060
|
+
tier,
|
|
5061
|
+
group: input.group,
|
|
5062
|
+
policy: input.policy,
|
|
5063
|
+
billingTerms: offer.contract.billingTerms,
|
|
5064
|
+
presentation: input.presentation,
|
|
5065
|
+
founding: foundingOf(offer)
|
|
5066
|
+
});
|
|
4716
5067
|
const ready = { ...row2, offer, tier, quote, status: "ready", updatedAt: now };
|
|
4717
5068
|
await restore2(ready, target, now, true);
|
|
4718
5069
|
try {
|
|
@@ -4767,7 +5118,9 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
|
|
|
4767
5118
|
group: { merchantDisclosureText: quote2.merchantDisclosureText },
|
|
4768
5119
|
tier: tier2,
|
|
4769
5120
|
policy: { refundPolicyText: quote2.refundPolicyText, trustCopy: quote2.trustCopy },
|
|
4770
|
-
billingTerms
|
|
5121
|
+
billingTerms,
|
|
5122
|
+
presentation: quote2.presentation,
|
|
5123
|
+
founding: foundingOf(intent.membershipOffer)
|
|
4771
5124
|
});
|
|
4772
5125
|
if (expected.digest !== quote2.digest) throw new Error("invalid commercial snapshot");
|
|
4773
5126
|
return { tier: tier2, quote: expected, billingTerms, membershipOffer: intent.membershipOffer };
|
|
@@ -4794,9 +5147,10 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
|
|
|
4794
5147
|
if (!tier || tierIsFree(tier) || !tier.stripePriceId) throw Object.assign(new Error("tier unavailable"), { code: "tier_unresolved" });
|
|
4795
5148
|
const control3 = await loadActiveSignupControl(db, ctx.chapter.id, env.ODLA_ENV);
|
|
4796
5149
|
const policy = control3?.document.policy?.tierPolicies?.[tier.id];
|
|
5150
|
+
const presentation = tierPresentationFor(control3?.document, tier.id);
|
|
4797
5151
|
if (ctx.membershipAuthority) return prepareMembershipQuote(
|
|
4798
5152
|
db,
|
|
4799
|
-
{ ...target, app, group, tier, policy, now: Date.now() },
|
|
5153
|
+
{ ...target, app, group, tier, policy, presentation, now: Date.now() },
|
|
4800
5154
|
(input) => ctx.membershipAuthority.issueOffer(input, env)
|
|
4801
5155
|
);
|
|
4802
5156
|
const quote = await createPaymentQuote({
|
|
@@ -4807,7 +5161,8 @@ async function checkoutQuoteForApplication(db, app, group, env, ctx, prepare = f
|
|
|
4807
5161
|
runtime: stripeRuntimeIdentity(env.ODLA_RUNTIME),
|
|
4808
5162
|
group,
|
|
4809
5163
|
tier,
|
|
4810
|
-
policy
|
|
5164
|
+
policy,
|
|
5165
|
+
presentation
|
|
4811
5166
|
});
|
|
4812
5167
|
return { tier, quote, billingTerms: void 0 };
|
|
4813
5168
|
}
|
|
@@ -5461,10 +5816,11 @@ var handleMember = async (req, url, env, ctx) => {
|
|
|
5461
5816
|
group,
|
|
5462
5817
|
Boolean(stripeKey)
|
|
5463
5818
|
);
|
|
5819
|
+
const founding = await foundingPreviews(offered, env, ctx);
|
|
5464
5820
|
const activeControl = chapter.signupControl ? await loadActiveSignupControl(db, chapter.id, env.ODLA_ENV) : null;
|
|
5465
5821
|
const application = controlledApplication(chapter.application, activeControl?.document);
|
|
5466
5822
|
return json({
|
|
5467
|
-
...joinConfig(group, paymentsReady, joinConfigTiers(offered, { currency: typeof group.currency === "string" ? group.currency : void 0, interval: group.interval === "month" ? "month" : "year" }), application.conditions),
|
|
5823
|
+
...joinConfig(group, paymentsReady, joinConfigTiers(offered, { currency: typeof group.currency === "string" ? group.currency : void 0, interval: group.interval === "month" ? "month" : "year" }, founding), application.conditions),
|
|
5468
5824
|
fieldContract: chapterFlowFieldContract(chapter, "join", application),
|
|
5469
5825
|
copy: { ...chapter.copy.join, ...activeControl?.document.application?.copy ?? {} },
|
|
5470
5826
|
...activeControl ? { signupControl: {
|
|
@@ -7157,7 +7513,7 @@ async function deliverAdmissionGrants(options) {
|
|
|
7157
7513
|
}
|
|
7158
7514
|
|
|
7159
7515
|
// src/worker-routes-network-admission.ts
|
|
7160
|
-
var
|
|
7516
|
+
var PATH2 = "/api/admin/network/admissions";
|
|
7161
7517
|
var RETRY_PATH = "/api/admin/network/admissions/retry";
|
|
7162
7518
|
var REVOKE_PATH = "/api/admin/network/admissions/revoke";
|
|
7163
7519
|
var MUTATION_ID2 = /^[A-Za-z0-9_-]{16,128}$/;
|
|
@@ -7237,11 +7593,11 @@ async function parseBody(req) {
|
|
|
7237
7593
|
}
|
|
7238
7594
|
}
|
|
7239
7595
|
var handleAdminNetworkAdmissions = async (req, url, env, ctx) => {
|
|
7240
|
-
if (![
|
|
7596
|
+
if (![PATH2, RETRY_PATH, REVOKE_PATH].includes(url.pathname)) return null;
|
|
7241
7597
|
const auth = await gate2(req, env, ctx);
|
|
7242
7598
|
if ("response" in auth) return auth.response;
|
|
7243
7599
|
const db = auth.db;
|
|
7244
|
-
if (url.pathname ===
|
|
7600
|
+
if (url.pathname === PATH2 && req.method === "GET") {
|
|
7245
7601
|
const targetId2 = url.searchParams.get("targetId") ?? "";
|
|
7246
7602
|
const target2 = targetFor(ctx, targetId2);
|
|
7247
7603
|
if (!target2) return reject6(404, "unknown_target", "admission target is not configured");
|
|
@@ -7485,28 +7841,28 @@ var handleSchedule = async (req, url, env, ctx) => {
|
|
|
7485
7841
|
};
|
|
7486
7842
|
|
|
7487
7843
|
// src/payments-stripe.ts
|
|
7488
|
-
var
|
|
7844
|
+
var import_stripe10 = require("@odla-ai/stripe");
|
|
7489
7845
|
|
|
7490
7846
|
// src/payments-refund.ts
|
|
7491
|
-
var
|
|
7847
|
+
var import_stripe7 = require("@odla-ai/stripe");
|
|
7492
7848
|
var row = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
7493
7849
|
var id2 = (value) => typeof value === "string" ? value : String(row(value).id ?? "");
|
|
7494
7850
|
var providerId2 = (value, prefix) => new RegExp(`^${prefix}_[A-Za-z0-9]+$`).test(value);
|
|
7495
|
-
function
|
|
7851
|
+
function fail5(code = "refund_target_mismatch") {
|
|
7496
7852
|
throw Object.assign(new Error(code), { code });
|
|
7497
7853
|
}
|
|
7498
7854
|
async function refundSubscriptionPayment(secretKey, input) {
|
|
7499
|
-
if (!providerId2(input.customerId, "cus") || !providerId2(input.subscriptionId, "sub"))
|
|
7855
|
+
if (!providerId2(input.customerId, "cus") || !providerId2(input.subscriptionId, "sub")) fail5();
|
|
7500
7856
|
const call = async (method, path, params, key3) => {
|
|
7501
|
-
const result = await (0,
|
|
7502
|
-
if (!result.ok)
|
|
7503
|
-
if (!result.body || typeof result.body !== "object" || Array.isArray(result.body))
|
|
7857
|
+
const result = await (0, import_stripe7.stripeRequest)({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
|
|
7858
|
+
if (!result.ok) fail5("stripe_error");
|
|
7859
|
+
if (!result.body || typeof result.body !== "object" || Array.isArray(result.body)) fail5("stripe_invalid_response");
|
|
7504
7860
|
return result.body;
|
|
7505
7861
|
};
|
|
7506
7862
|
const sub = await call("GET", `/v1/subscriptions/${input.subscriptionId}`);
|
|
7507
|
-
if (id2(sub) !== input.subscriptionId || id2(sub.customer) !== input.customerId)
|
|
7863
|
+
if (id2(sub) !== input.subscriptionId || id2(sub.customer) !== input.customerId) fail5();
|
|
7508
7864
|
for (const [key3, value] of Object.entries(input.metadata ?? {})) {
|
|
7509
|
-
if (row(sub.metadata)[key3] !== value)
|
|
7865
|
+
if (row(sub.metadata)[key3] !== value) fail5();
|
|
7510
7866
|
}
|
|
7511
7867
|
let cursor;
|
|
7512
7868
|
let earliest;
|
|
@@ -7519,69 +7875,69 @@ async function refundSubscriptionPayment(secretKey, input) {
|
|
|
7519
7875
|
limit: 100,
|
|
7520
7876
|
...cursor ? { starting_after: cursor } : {}
|
|
7521
7877
|
});
|
|
7522
|
-
if (!Array.isArray(list.data) || typeof list.has_more !== "boolean")
|
|
7878
|
+
if (!Array.isArray(list.data) || typeof list.has_more !== "boolean") fail5("stripe_invalid_response");
|
|
7523
7879
|
for (const value of list.data) {
|
|
7524
7880
|
const invoice2 = row(value);
|
|
7525
7881
|
const subscription = invoice2.subscription ?? row(row(invoice2.parent).subscription_details).subscription;
|
|
7526
|
-
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)
|
|
7882
|
+
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();
|
|
7527
7883
|
seen.add(id2(invoice2));
|
|
7528
7884
|
if (Number(invoice2.amount_paid) > 0 && (!earliest || Number(invoice2.created) < Number(earliest.created))) earliest = invoice2;
|
|
7529
7885
|
}
|
|
7530
7886
|
if (!list.has_more) break;
|
|
7531
|
-
if (!list.data.length || page === 9)
|
|
7887
|
+
if (!list.data.length || page === 9) fail5("refund_history_incomplete");
|
|
7532
7888
|
cursor = id2(list.data[list.data.length - 1]);
|
|
7533
7889
|
}
|
|
7534
|
-
if (!earliest)
|
|
7890
|
+
if (!earliest) fail5("no_charge");
|
|
7535
7891
|
const invoice = earliest;
|
|
7536
|
-
if (input.expectedInvoiceId !== void 0 && id2(invoice) !== input.expectedInvoiceId || input.expectedAmountCents !== void 0 && invoice.amount_paid !== input.expectedAmountCents)
|
|
7892
|
+
if (input.expectedInvoiceId !== void 0 && id2(invoice) !== input.expectedInvoiceId || input.expectedAmountCents !== void 0 && invoice.amount_paid !== input.expectedAmountCents) fail5();
|
|
7537
7893
|
let chargeId = id2(invoice.charge);
|
|
7538
7894
|
let paymentIntentId = id2(invoice.payment_intent);
|
|
7539
7895
|
if (!chargeId && !paymentIntentId) {
|
|
7540
7896
|
const payments = await call("GET", "/v1/invoice_payments", { invoice: id2(invoice), status: "paid", limit: 100 });
|
|
7541
|
-
if (payments.has_more !== false || !Array.isArray(payments.data) || payments.data.length !== 1)
|
|
7897
|
+
if (payments.has_more !== false || !Array.isArray(payments.data) || payments.data.length !== 1) fail5("refund_payment_ambiguous");
|
|
7542
7898
|
const payment = row(payments.data[0]);
|
|
7543
|
-
if (id2(payment.invoice) !== id2(invoice) || payment.status !== "paid" || row(payment.payment).type !== "payment_intent")
|
|
7899
|
+
if (id2(payment.invoice) !== id2(invoice) || payment.status !== "paid" || row(payment.payment).type !== "payment_intent") fail5();
|
|
7544
7900
|
paymentIntentId = id2(row(payment.payment).payment_intent);
|
|
7545
7901
|
}
|
|
7546
7902
|
if (!chargeId) {
|
|
7547
|
-
if (!providerId2(paymentIntentId, "pi"))
|
|
7903
|
+
if (!providerId2(paymentIntentId, "pi")) fail5();
|
|
7548
7904
|
const payment = await call("GET", `/v1/payment_intents/${paymentIntentId}`);
|
|
7549
|
-
if (id2(payment) !== paymentIntentId || id2(payment.customer) !== input.customerId || payment.status !== "succeeded")
|
|
7905
|
+
if (id2(payment) !== paymentIntentId || id2(payment.customer) !== input.customerId || payment.status !== "succeeded") fail5();
|
|
7550
7906
|
chargeId = id2(payment.latest_charge);
|
|
7551
7907
|
}
|
|
7552
|
-
if (!providerId2(chargeId, "ch"))
|
|
7908
|
+
if (!providerId2(chargeId, "ch")) fail5();
|
|
7553
7909
|
const charge = await call("GET", `/v1/charges/${chargeId}`);
|
|
7554
|
-
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))
|
|
7555
|
-
if (input.alreadyRefundedChargeId !== void 0 && (chargeId !== input.alreadyRefundedChargeId || charge.amount_refunded !== charge.amount || charge.refunded !== true))
|
|
7910
|
+
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();
|
|
7911
|
+
if (input.alreadyRefundedChargeId !== void 0 && (chargeId !== input.alreadyRefundedChargeId || charge.amount_refunded !== charge.amount || charge.refunded !== true)) fail5();
|
|
7556
7912
|
if (charge.amount_refunded !== charge.amount) {
|
|
7557
7913
|
const refund = await call("POST", "/v1/refunds", {
|
|
7558
7914
|
charge: chargeId,
|
|
7559
7915
|
...input.metadata ? { metadata: input.metadata } : {}
|
|
7560
7916
|
}, `chapter-initial-refund:${input.subscriptionId}:${chargeId}`);
|
|
7561
|
-
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))
|
|
7917
|
+
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");
|
|
7562
7918
|
}
|
|
7563
7919
|
let subscriptionCanceled = sub.status === "canceled";
|
|
7564
7920
|
if (input.cancelSubscription !== false && !subscriptionCanceled) {
|
|
7565
7921
|
const canceled = await call("DELETE", `/v1/subscriptions/${input.subscriptionId}`);
|
|
7566
|
-
if (id2(canceled) !== input.subscriptionId || canceled.status !== "canceled")
|
|
7922
|
+
if (id2(canceled) !== input.subscriptionId || canceled.status !== "canceled") fail5("refund_cancel_incomplete");
|
|
7567
7923
|
subscriptionCanceled = true;
|
|
7568
7924
|
}
|
|
7569
7925
|
const confirmed = await call("GET", `/v1/charges/${chargeId}`);
|
|
7570
|
-
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)
|
|
7926
|
+
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");
|
|
7571
7927
|
return { refundedCents: Number(charge.amount), subscriptionCanceled };
|
|
7572
7928
|
}
|
|
7573
7929
|
|
|
7574
7930
|
// src/subscription-payment-identity.ts
|
|
7575
|
-
var
|
|
7931
|
+
var import_stripe9 = require("@odla-ai/stripe");
|
|
7576
7932
|
|
|
7577
7933
|
// src/subscription-provider-contract.ts
|
|
7578
|
-
var
|
|
7934
|
+
var import_stripe8 = require("@odla-ai/stripe");
|
|
7579
7935
|
function subscriptionContractMismatch() {
|
|
7580
7936
|
throw Object.assign(new Error("Stripe subscription does not match the checkout contract"), { code: "stripe_contract_mismatch" });
|
|
7581
7937
|
}
|
|
7582
7938
|
async function verifySubscriptionPrice(secretKey, input, resume = false) {
|
|
7583
7939
|
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();
|
|
7584
|
-
const result = await (0,
|
|
7940
|
+
const result = await (0, import_stripe8.stripeRequest)({ secretKey }, "GET", `/v1/prices/${encodeURIComponent(input.priceId)}`);
|
|
7585
7941
|
const price = result.body;
|
|
7586
7942
|
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();
|
|
7587
7943
|
return typeof price.product === "string" ? price.product : void 0;
|
|
@@ -7600,7 +7956,7 @@ async function tagSubscriptionPayment(secretKey, invoice, customerId, metadata,
|
|
|
7600
7956
|
let paymentId = id3(invoice.payment_intent);
|
|
7601
7957
|
if (!paymentId) {
|
|
7602
7958
|
if (!/^in_[A-Za-z0-9]+$/.test(id3(invoice))) subscriptionContractMismatch();
|
|
7603
|
-
const result2 = await (0,
|
|
7959
|
+
const result2 = await (0, import_stripe9.stripeRequest)({ secretKey }, "GET", "/v1/invoice_payments", { invoice: id3(invoice), limit: 100 });
|
|
7604
7960
|
const list = result2.body;
|
|
7605
7961
|
if (!result2.ok || list?.has_more !== false || !Array.isArray(list.data) || list.data.length !== 1) subscriptionContractMismatch();
|
|
7606
7962
|
const link = list.data[0];
|
|
@@ -7609,11 +7965,11 @@ async function tagSubscriptionPayment(secretKey, invoice, customerId, metadata,
|
|
|
7609
7965
|
}
|
|
7610
7966
|
if (!/^pi_[A-Za-z0-9]+$/.test(paymentId)) subscriptionContractMismatch();
|
|
7611
7967
|
const path = `/v1/payment_intents/${paymentId}`;
|
|
7612
|
-
const result = await (0,
|
|
7968
|
+
const result = await (0, import_stripe9.stripeRequest)({ secretKey }, "GET", path);
|
|
7613
7969
|
const payment = result.body;
|
|
7614
7970
|
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();
|
|
7615
7971
|
if (Object.entries(metadata).every(([key3, value]) => payment.metadata?.[key3] === value)) return;
|
|
7616
|
-
const tagged = await (0,
|
|
7972
|
+
const tagged = await (0, import_stripe9.stripeRequest)(
|
|
7617
7973
|
{ secretKey },
|
|
7618
7974
|
"POST",
|
|
7619
7975
|
path,
|
|
@@ -7625,7 +7981,7 @@ async function tagSubscriptionPayment(secretKey, invoice, customerId, metadata,
|
|
|
7625
7981
|
|
|
7626
7982
|
// src/payments-stripe.ts
|
|
7627
7983
|
async function stripeCall(sk, method, path, params, idempotencyKey) {
|
|
7628
|
-
return (0,
|
|
7984
|
+
return (0, import_stripe10.stripeRequest)(
|
|
7629
7985
|
{ secretKey: sk },
|
|
7630
7986
|
method,
|
|
7631
7987
|
path,
|
|
@@ -7633,7 +7989,7 @@ async function stripeCall(sk, method, path, params, idempotencyKey) {
|
|
|
7633
7989
|
idempotencyKey ? { idempotencyKey } : {}
|
|
7634
7990
|
);
|
|
7635
7991
|
}
|
|
7636
|
-
function
|
|
7992
|
+
function fail6(op, r) {
|
|
7637
7993
|
const err = new Error(`stripe ${op} failed: ${r.status}`);
|
|
7638
7994
|
err.code = "stripe_error";
|
|
7639
7995
|
throw err;
|
|
@@ -7683,7 +8039,7 @@ function createStripeProvider(config) {
|
|
|
7683
8039
|
{ email: input.email, name: input.name, metadata: meta },
|
|
7684
8040
|
`cus:${input.applicationId}`
|
|
7685
8041
|
);
|
|
7686
|
-
if (!cust.ok)
|
|
8042
|
+
if (!cust.ok) fail6("customer create", cust);
|
|
7687
8043
|
if (typeof cust.body.id !== "string" || !/^cus_[A-Za-z0-9]+$/.test(cust.body.id)) subscriptionContractMismatch();
|
|
7688
8044
|
customerId = cust.body.id;
|
|
7689
8045
|
}
|
|
@@ -7715,7 +8071,7 @@ function createStripeProvider(config) {
|
|
|
7715
8071
|
// retry between create and the db write can't orphan a second one.
|
|
7716
8072
|
`sub:${input.applicationId}${input.purchaseAttemptId ? `:${input.purchaseAttemptId}` : ""}`
|
|
7717
8073
|
);
|
|
7718
|
-
if (!sub.ok)
|
|
8074
|
+
if (!sub.ok) fail6("subscription create", sub);
|
|
7719
8075
|
if (input.billingTerms?.discount?.duration === "once" && Array.isArray(sub.body.discounts) && sub.body.discounts.length === 0) {
|
|
7720
8076
|
const subscriptionId = sub.body.id;
|
|
7721
8077
|
if (typeof subscriptionId !== "string" || !/^sub_[A-Za-z0-9]+$/.test(subscriptionId)) subscriptionContractMismatch();
|
|
@@ -7725,14 +8081,14 @@ function createStripeProvider(config) {
|
|
|
7725
8081
|
"expand[2]": "discounts",
|
|
7726
8082
|
"expand[3]": "latest_invoice.discounts"
|
|
7727
8083
|
});
|
|
7728
|
-
if (!expanded.ok)
|
|
8084
|
+
if (!expanded.ok) fail6("subscription invoice evidence", expanded);
|
|
7729
8085
|
if (expanded.body.id !== subscriptionId) subscriptionContractMismatch();
|
|
7730
8086
|
sub = expanded;
|
|
7731
8087
|
}
|
|
7732
8088
|
verifySubscriptionResult(sub.body, input, customerId, meta);
|
|
7733
8089
|
const invoice = sub.body.latest_invoice;
|
|
7734
8090
|
const clientSecret2 = clientSecretOf(sub.body);
|
|
7735
|
-
if (!clientSecret2)
|
|
8091
|
+
if (!clientSecret2) fail6("subscription confirmation-secret missing", sub);
|
|
7736
8092
|
await tagSubscriptionPayment(
|
|
7737
8093
|
sk,
|
|
7738
8094
|
invoice,
|
|
@@ -7744,7 +8100,7 @@ function createStripeProvider(config) {
|
|
|
7744
8100
|
return { customerId, subscriptionId: String(sub.body.id), clientSecret: clientSecret2 };
|
|
7745
8101
|
},
|
|
7746
8102
|
async ingestWebhook(rawBody, sigHeader) {
|
|
7747
|
-
if (!webhookSecret || !await (0,
|
|
8103
|
+
if (!webhookSecret || !await (0, import_stripe10.verifyStripeSignature)(rawBody, sigHeader, webhookSecret)) {
|
|
7748
8104
|
return { ok: false, reason: "bad_signature" };
|
|
7749
8105
|
}
|
|
7750
8106
|
const event = JSON.parse(rawBody);
|
|
@@ -8404,7 +8760,7 @@ async function namedSeatOfferDigest(offer) {
|
|
|
8404
8760
|
"named-only",
|
|
8405
8761
|
"full-upfront",
|
|
8406
8762
|
"auto-renew-coterm",
|
|
8407
|
-
"
|
|
8763
|
+
"no-automatic-refund"
|
|
8408
8764
|
])));
|
|
8409
8765
|
return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
8410
8766
|
}
|
|
@@ -8809,6 +9165,23 @@ async function updateChapterPerson(opts) {
|
|
|
8809
9165
|
});
|
|
8810
9166
|
}
|
|
8811
9167
|
|
|
9168
|
+
// src/application-actions.ts
|
|
9169
|
+
function isApplicationStage(ctx, stage) {
|
|
9170
|
+
return ctx.chapter.pipeline.stages.includes(stage);
|
|
9171
|
+
}
|
|
9172
|
+
function applicationActionStages(app, env, ctx) {
|
|
9173
|
+
if (!chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME)) return [];
|
|
9174
|
+
const from = String(app.status ?? "");
|
|
9175
|
+
if (app.canceled === true || ["refunded", "canceled"].includes(from)) return [];
|
|
9176
|
+
return ctx.chapter.pipeline.stages.filter((to) => {
|
|
9177
|
+
if (to === from) return false;
|
|
9178
|
+
if (to === "approved") return canApprove(from, ctx.chapter.pipeline) && from !== "declined";
|
|
9179
|
+
if (to === "refunded") return Boolean(app.namedSeatId || app.stripeCustomerId && app.stripeSubscriptionId) && (!ctx.chapter.operations.refund.allowedFrom || ctx.chapter.operations.refund.allowedFrom.includes(from));
|
|
9180
|
+
if (ctx.membershipAuthority && ["submitted", "paid_pending_vetting", "declined", "canceled"].includes(to)) return false;
|
|
9181
|
+
return canTransition(from, to, ctx.chapter.pipeline);
|
|
9182
|
+
});
|
|
9183
|
+
}
|
|
9184
|
+
|
|
8812
9185
|
// src/worker-routes-people-profile.ts
|
|
8813
9186
|
var handlePeopleProfile = async (req, url, env, ctx) => {
|
|
8814
9187
|
if (ctx.chapter.mode !== "chapter") return null;
|
|
@@ -8830,7 +9203,16 @@ var handlePeopleProfile = async (req, url, env, ctx) => {
|
|
|
8830
9203
|
const record4 = await (0, import_crm10.getRecord)({ crm: ctx.chapter.crm, db }, id6);
|
|
8831
9204
|
if (!record4) return json({ error: "not found" }, 404);
|
|
8832
9205
|
if (record4.type !== "person") return dedicated ? json({ error: "person required" }, 422) : null;
|
|
8833
|
-
if (match?.[2])
|
|
9206
|
+
if (match?.[2]) {
|
|
9207
|
+
const linked = typeof record4.fields.applicationId === "string" && record4.fields.applicationId !== "";
|
|
9208
|
+
let to = "";
|
|
9209
|
+
try {
|
|
9210
|
+
to = String((await req.clone().json()).to ?? "");
|
|
9211
|
+
} catch {
|
|
9212
|
+
}
|
|
9213
|
+
if (!linked && to && !isApplicationStage(ctx, to)) return null;
|
|
9214
|
+
return json({ error: "use the application lifecycle workflow", code: "application_workflow_required" }, 409);
|
|
9215
|
+
}
|
|
8834
9216
|
if (req.method === "GET" && dedicated) {
|
|
8835
9217
|
const excluded = profileReadOnlyFields(record4, ctx);
|
|
8836
9218
|
const fields = Object.entries(ctx.chapter.crm.type(record4.type).fields).filter(([id7]) => !excluded.includes(id7)).map(([id7, field]) => ({
|
|
@@ -9521,20 +9903,6 @@ var handleAdminBilling = async (req, url, env, ctx) => {
|
|
|
9521
9903
|
// src/worker-routes-admin-lifecycle.ts
|
|
9522
9904
|
var import_calendar3 = require("@odla-ai/calendar");
|
|
9523
9905
|
|
|
9524
|
-
// src/application-actions.ts
|
|
9525
|
-
function applicationActionStages(app, env, ctx) {
|
|
9526
|
-
if (!chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME)) return [];
|
|
9527
|
-
const from = String(app.status ?? "");
|
|
9528
|
-
if (app.canceled === true || ["refunded", "canceled"].includes(from)) return [];
|
|
9529
|
-
return ctx.chapter.pipeline.stages.filter((to) => {
|
|
9530
|
-
if (to === from) return false;
|
|
9531
|
-
if (to === "approved") return canApprove(from, ctx.chapter.pipeline) && from !== "declined";
|
|
9532
|
-
if (to === "refunded") return Boolean(app.namedSeatId || app.stripeCustomerId && app.stripeSubscriptionId) && (!ctx.chapter.operations.refund.allowedFrom || ctx.chapter.operations.refund.allowedFrom.includes(from));
|
|
9533
|
-
if (ctx.membershipAuthority && ["submitted", "paid_pending_vetting", "declined", "canceled"].includes(to)) return false;
|
|
9534
|
-
return canTransition(from, to, ctx.chapter.pipeline);
|
|
9535
|
-
});
|
|
9536
|
-
}
|
|
9537
|
-
|
|
9538
9906
|
// src/refund-application.ts
|
|
9539
9907
|
async function refundApplication(db, env, ctx, app) {
|
|
9540
9908
|
if (!chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME)) return json({ error: "not found" }, 404);
|
|
@@ -9725,6 +10093,12 @@ var handleAdminRefund = async (req, url, env, ctx) => {
|
|
|
9725
10093
|
const app = await loadApp(db, m[1]);
|
|
9726
10094
|
if (!app) return json({ error: "not found" }, 404);
|
|
9727
10095
|
if (!chapterBillingApplication(app, ctx.chapter.id, env.ODLA_RUNTIME)) return json({ error: "not found" }, 404);
|
|
10096
|
+
if (app.namedSeatId) {
|
|
10097
|
+
const actor = await ctx.verifyUser(req, env);
|
|
10098
|
+
if (!actor || !await ctx.isSuperAdminEmail(db, actor.email, actor.userId)) {
|
|
10099
|
+
return json({ error: "super-admin required", code: "super_admin_required" }, 403);
|
|
10100
|
+
}
|
|
10101
|
+
}
|
|
9728
10102
|
return refundApplication(db, env, ctx, app);
|
|
9729
10103
|
};
|
|
9730
10104
|
var handleAdminApplicationPatch = async (req, url, env, ctx) => {
|
|
@@ -10334,7 +10708,7 @@ async function operationalReaderRecord(record4, reader, db, env, ctx) {
|
|
|
10334
10708
|
const appId = record4.fields.applicationId;
|
|
10335
10709
|
const app = typeof appId === "string" ? (await db.query({ applications: { $: { where: { id: appId }, limit: 1 } } })).applications?.[0] : void 0;
|
|
10336
10710
|
if (reader.stageTransitions.includes(record4.type)) {
|
|
10337
|
-
result.edit.allowedStages = app ? applicationActionStages(app, env, ctx).map((id6) => ({ id: id6, label: id6.replaceAll("_", " ") })) :
|
|
10711
|
+
result.edit.allowedStages = app ? applicationActionStages(app, env, ctx).map((id6) => ({ id: id6, label: id6.replaceAll("_", " ") })) : result.edit.allowedStages.filter((stage) => !isApplicationStage(ctx, stage.id));
|
|
10338
10712
|
const lock = (await db.query({ crm_activity: { $: { where: { id: `network-action-lock:${record4.id}` }, limit: 1 } } })).crm_activity?.[0];
|
|
10339
10713
|
const meta = lock?.meta;
|
|
10340
10714
|
if (meta?.state === "pending" && typeof meta.activityId === "string") {
|
|
@@ -10552,7 +10926,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
|
|
|
10552
10926
|
}
|
|
10553
10927
|
};
|
|
10554
10928
|
try {
|
|
10555
|
-
if (hasStage && ctx.chapter.mode === "chapter" && record4.type === "person") {
|
|
10929
|
+
if (hasStage && ctx.chapter.mode === "chapter" && record4.type === "person" && (record4.fields.applicationId || isApplicationStage(ctx, input.stage))) {
|
|
10556
10930
|
const updated2 = await networkApplicationAction({
|
|
10557
10931
|
db,
|
|
10558
10932
|
env,
|
|
@@ -11908,7 +12282,7 @@ async function revealNetworkPersonRegistryRecord(db, input, mutationId, actor, n
|
|
|
11908
12282
|
}
|
|
11909
12283
|
|
|
11910
12284
|
// src/worker-routes-network-person-registry.ts
|
|
11911
|
-
var
|
|
12285
|
+
var PATH3 = "/api/admin/network-person-registry";
|
|
11912
12286
|
var MAX_BODY2 = 8 * 1024 * 1024;
|
|
11913
12287
|
var MUTATION_ID4 = /^[A-Za-z0-9_-]{16,128}$/;
|
|
11914
12288
|
var positiveRevision = (value) => {
|
|
@@ -11933,7 +12307,7 @@ function registryError(error) {
|
|
|
11933
12307
|
return null;
|
|
11934
12308
|
}
|
|
11935
12309
|
var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
|
|
11936
|
-
if (url.pathname !==
|
|
12310
|
+
if (url.pathname !== PATH3) return null;
|
|
11937
12311
|
if (!ctx.chapter.networkPersonRegistry) return json({ error: "not found" }, 404);
|
|
11938
12312
|
if (req.method !== "GET" && req.method !== "POST") return json({ error: "method not allowed" }, 405);
|
|
11939
12313
|
if (req.method === "POST" && Number(req.headers.get("content-length") ?? 0) > MAX_BODY2) {
|
|
@@ -12108,7 +12482,7 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
|
|
|
12108
12482
|
};
|
|
12109
12483
|
|
|
12110
12484
|
// src/worker-routes-network-commercial.ts
|
|
12111
|
-
var
|
|
12485
|
+
var import_stripe11 = require("@odla-ai/stripe");
|
|
12112
12486
|
|
|
12113
12487
|
// src/worker-routes-network-commercial-parity.ts
|
|
12114
12488
|
var import_db13 = require("@odla-ai/db");
|
|
@@ -12484,7 +12858,7 @@ var handleNetworkCommercialStripeWebhook = async (req, url, env, ctx) => {
|
|
|
12484
12858
|
return json({ error: "commercial webhook is not configured" }, 503);
|
|
12485
12859
|
}
|
|
12486
12860
|
try {
|
|
12487
|
-
const event = await (0,
|
|
12861
|
+
const event = await (0, import_stripe11.constructStripeEvent)(body, signature, secret);
|
|
12488
12862
|
const fact = normalizeNetworkCommercialStripeEvent(event, await stripeEventDigest(body));
|
|
12489
12863
|
if (!fact.eventId || !fact.eventType || !fact.objectId) return json({ error: "Stripe event identity is invalid" }, 400);
|
|
12490
12864
|
const result = await ingestNetworkCommercialFact(db, ctx.chapter.networkCommercialReadModel.sources, fact, Date.now(), await stripePayloadDigest(body));
|
|
@@ -12493,7 +12867,7 @@ var handleNetworkCommercialStripeWebhook = async (req, url, env, ctx) => {
|
|
|
12493
12867
|
}
|
|
12494
12868
|
return json({ ok: true, status: result.status, eventId: fact.eventId });
|
|
12495
12869
|
} catch (error) {
|
|
12496
|
-
if (error instanceof
|
|
12870
|
+
if (error instanceof import_stripe11.StripeError) return json({ error: error.message, code: error.code }, 400);
|
|
12497
12871
|
throw error;
|
|
12498
12872
|
}
|
|
12499
12873
|
};
|
|
@@ -12535,7 +12909,7 @@ var handleAdminNetworkCommercialReadModel = async (req, url, env, ctx) => {
|
|
|
12535
12909
|
const secretKey = await getVaultSecret(db, "stripe_secret_key");
|
|
12536
12910
|
if (!secretKey) return json({ error: "Stripe reconciliation is not configured" }, 503);
|
|
12537
12911
|
try {
|
|
12538
|
-
const stripe = (0,
|
|
12912
|
+
const stripe = (0, import_stripe11.initStripe)({ secretKey });
|
|
12539
12913
|
const [customers, subscriptions, invoices, refunds, payments] = await Promise.all([
|
|
12540
12914
|
listStripeObjects(stripe, "/v1/customers"),
|
|
12541
12915
|
listStripeObjects(stripe, "/v1/subscriptions", { status: "all" }),
|
|
@@ -12549,7 +12923,7 @@ var handleAdminNetworkCommercialReadModel = async (req, url, env, ctx) => {
|
|
|
12549
12923
|
...reconcileStripeSnapshot(projection2, { customers, subscriptions, invoices, refunds, payments }, ctx.chapter.networkCommercialReadModel.sources)
|
|
12550
12924
|
});
|
|
12551
12925
|
} catch (error) {
|
|
12552
|
-
if (error instanceof
|
|
12926
|
+
if (error instanceof import_stripe11.StripeError) return json({ error: "Stripe reconciliation failed", code: error.code }, 502);
|
|
12553
12927
|
return json({ error: "Stripe reconciliation failed" }, 502);
|
|
12554
12928
|
}
|
|
12555
12929
|
}
|
|
@@ -13129,7 +13503,7 @@ var handleAdminBuilderLibrary = async (req, url, env, ctx) => {
|
|
|
13129
13503
|
};
|
|
13130
13504
|
|
|
13131
13505
|
// src/membership-cancel-authority.ts
|
|
13132
|
-
var
|
|
13506
|
+
var import_stripe12 = require("@odla-ai/stripe");
|
|
13133
13507
|
async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
|
|
13134
13508
|
membershipClock(now);
|
|
13135
13509
|
const id6 = await networkMembershipId(target);
|
|
@@ -13140,14 +13514,14 @@ async function cancelMembershipAtTermEnd(db, target, now, stripeSecret) {
|
|
|
13140
13514
|
membershipFail("membership_cancellation_unavailable");
|
|
13141
13515
|
}
|
|
13142
13516
|
const path = `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`;
|
|
13143
|
-
const read2 = await (0,
|
|
13517
|
+
const read2 = await (0, import_stripe12.stripeRequest)({ secretKey: stripeSecret }, "GET", path);
|
|
13144
13518
|
const sub = read2.body;
|
|
13145
13519
|
if (!read2.ok || sub.id !== member.providerSubscriptionId || sub.customer !== member.providerCustomerId || sub.livemode !== (target.environment === "prod") || !Object.entries({
|
|
13146
13520
|
...stripePartitionMetadata(target),
|
|
13147
13521
|
applicationId: target.applicationId
|
|
13148
13522
|
}).every(([key3, value]) => sub.metadata?.[key3] === value)) membershipFail("membership_cancellation_provider_mismatch");
|
|
13149
13523
|
if (sub.status !== "canceled" && sub.cancel_at_period_end !== true) {
|
|
13150
|
-
const write = await (0,
|
|
13524
|
+
const write = await (0, import_stripe12.stripeRequest)(
|
|
13151
13525
|
{ secretKey: stripeSecret },
|
|
13152
13526
|
"POST",
|
|
13153
13527
|
path,
|
|
@@ -13369,7 +13743,7 @@ async function refundNetworkMembership(db, input, secretKey, now) {
|
|
|
13369
13743
|
}
|
|
13370
13744
|
|
|
13371
13745
|
// src/membership-provider-lifecycle.ts
|
|
13372
|
-
var
|
|
13746
|
+
var import_stripe13 = require("@odla-ai/stripe");
|
|
13373
13747
|
|
|
13374
13748
|
// src/membership-renewal-authority.ts
|
|
13375
13749
|
async function reconcileMembershipRenewal(db, input, now) {
|
|
@@ -13527,7 +13901,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
|
|
|
13527
13901
|
}
|
|
13528
13902
|
if (fact.subscriptionId !== member.providerSubscriptionId || fact.objectKind !== "subscription") membershipFail("membership_billing_scope_mismatch");
|
|
13529
13903
|
if (["refund_pending", "refunded", "declined", "canceled", "restarting"].includes(String(member.status))) return receipt();
|
|
13530
|
-
const result = await (0,
|
|
13904
|
+
const result = await (0, import_stripe13.stripeRequest)({ secretKey: sk }, "GET", `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`);
|
|
13531
13905
|
const sub = result.body;
|
|
13532
13906
|
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)) {
|
|
13533
13907
|
membershipFail("membership_billing_provider_mismatch");
|
|
@@ -13642,7 +14016,7 @@ async function reserveNamedMembershipSeat(db, input, now) {
|
|
|
13642
14016
|
}
|
|
13643
14017
|
|
|
13644
14018
|
// src/membership-seat-stripe.ts
|
|
13645
|
-
var
|
|
14019
|
+
var import_stripe14 = require("@odla-ai/stripe");
|
|
13646
14020
|
var reject7 = () => {
|
|
13647
14021
|
throw Object.assign(new Error("named seat provider contract mismatch"), { code: "named_seat_provider_mismatch" });
|
|
13648
14022
|
};
|
|
@@ -13654,7 +14028,7 @@ var metadataFor = (seat) => ({
|
|
|
13654
14028
|
});
|
|
13655
14029
|
var matches = (value, expected) => value && Object.entries(expected).every(([key3, item]) => value[key3] === item);
|
|
13656
14030
|
async function read(sk, path, params) {
|
|
13657
|
-
const response = await (0,
|
|
14031
|
+
const response = await (0, import_stripe14.stripeRequest)({ secretKey: sk }, "GET", path, params);
|
|
13658
14032
|
if (!response.ok) return reject7();
|
|
13659
14033
|
return response.body;
|
|
13660
14034
|
}
|
|
@@ -13691,11 +14065,11 @@ async function createNamedSeatStripePayment(sk, seat, now) {
|
|
|
13691
14065
|
}
|
|
13692
14066
|
await subscriptionForSeat(sk, seat);
|
|
13693
14067
|
const metadata = metadataFor(seat);
|
|
13694
|
-
const result = seat.providerPaymentIntentId ? await (0,
|
|
14068
|
+
const result = seat.providerPaymentIntentId ? await (0, import_stripe14.stripeRequest)(
|
|
13695
14069
|
{ secretKey: sk },
|
|
13696
14070
|
"GET",
|
|
13697
14071
|
`/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`
|
|
13698
|
-
) : await (0,
|
|
14072
|
+
) : await (0, import_stripe14.stripeRequest)({ secretKey: sk }, "POST", "/v1/payment_intents", {
|
|
13699
14073
|
amount: seat.amountCents,
|
|
13700
14074
|
currency: seat.currency,
|
|
13701
14075
|
customer: seat.providerCustomerId,
|
|
@@ -13713,7 +14087,7 @@ async function attachNamedSeatStripeRenewal(sk, seat) {
|
|
|
13713
14087
|
const sub = await subscriptionForSeat(sk, seat);
|
|
13714
14088
|
const existing = sub.items.data.find((item2) => item2.metadata?.seatId === seat.id);
|
|
13715
14089
|
if (existing) return String(existing.id);
|
|
13716
|
-
const result = await (0,
|
|
14090
|
+
const result = await (0, import_stripe14.stripeRequest)({ secretKey: sk }, "POST", "/v1/subscription_items", {
|
|
13717
14091
|
subscription: seat.providerSubscriptionId,
|
|
13718
14092
|
price: seat.priceId,
|
|
13719
14093
|
quantity: 1,
|
|
@@ -13813,7 +14187,7 @@ async function confirmNamedMembershipSeatPayment(db, input, now) {
|
|
|
13813
14187
|
membershipUpdate("networkSeat", input.seatId, {
|
|
13814
14188
|
status: status2,
|
|
13815
14189
|
purchasedAt: paidAt,
|
|
13816
|
-
acceptanceEndsAt:
|
|
14190
|
+
acceptanceEndsAt: Number(member.termEndsAt),
|
|
13817
14191
|
termStartedAt: paidAt,
|
|
13818
14192
|
termEndsAt: member.termEndsAt,
|
|
13819
14193
|
paymentEventId: input.eventId,
|
|
@@ -13861,7 +14235,7 @@ async function completeNamedSeatRenewal(db, seatId, secretKey, now) {
|
|
|
13861
14235
|
if (seat.status !== "renewal_pending" || !seat.purchasedAt) membershipFail("named_seat_renewal_not_ready");
|
|
13862
14236
|
const member = await membershipLoad(db, "networkMembership", target.primaryMembershipId);
|
|
13863
14237
|
requireMembershipScope(member, target);
|
|
13864
|
-
if (now >= Number(
|
|
14238
|
+
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");
|
|
13865
14239
|
const itemId = await attachNamedSeatStripeRenewal(secretKey, target);
|
|
13866
14240
|
const entitlementId = `${seatId}:entitlement`, effectId = `${seatId}:attach-renewal`;
|
|
13867
14241
|
try {
|
|
@@ -13936,7 +14310,7 @@ async function recordRecipient(db, input, now, approve) {
|
|
|
13936
14310
|
requireMembershipScope(entitlement, input);
|
|
13937
14311
|
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");
|
|
13938
14312
|
if (seat.acceptedAt === void 0) {
|
|
13939
|
-
if (approve ||
|
|
14313
|
+
if (approve || normalizeChapterEmail(input.verifiedRecipientEmail) !== seat.recipientEmailNormalized) {
|
|
13940
14314
|
membershipFail("named_seat_recipient_verification_required");
|
|
13941
14315
|
}
|
|
13942
14316
|
} else if (seat.recipientUserId !== input.recipientUserId || seat.recipientApplicationId !== input.recipientApplicationId) {
|
|
@@ -13986,129 +14360,6 @@ async function recordRecipient(db, input, now, approve) {
|
|
|
13986
14360
|
return membershipFail("named_seat_recipient_retry_required");
|
|
13987
14361
|
}
|
|
13988
14362
|
|
|
13989
|
-
// src/membership-seat-refund-stripe.ts
|
|
13990
|
-
var import_stripe14 = require("@odla-ai/stripe");
|
|
13991
|
-
var fail6 = (code = "named_seat_refund_mismatch") => {
|
|
13992
|
-
throw Object.assign(new Error(code), { code });
|
|
13993
|
-
};
|
|
13994
|
-
async function refundNamedSeatStripePayment(secretKey, seat) {
|
|
13995
|
-
if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail6();
|
|
13996
|
-
const call = async (method, path, params, idempotencyKey) => {
|
|
13997
|
-
const result = await (0, import_stripe14.stripeRequest)({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
|
|
13998
|
-
if (!result.ok || !result.body) return fail6("named_seat_refund_provider_unavailable");
|
|
13999
|
-
return result.body;
|
|
14000
|
-
};
|
|
14001
|
-
const metadata = { ...stripePartitionMetadata(seat), applicationId: seat.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
|
|
14002
|
-
const payment = await call("GET", `/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`);
|
|
14003
|
-
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();
|
|
14004
|
-
const sub = await call("GET", `/v1/subscriptions/${encodeURIComponent(seat.providerSubscriptionId)}`);
|
|
14005
|
-
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();
|
|
14006
|
-
const items = sub.items.data.filter((item) => item.metadata?.seatId === seat.id);
|
|
14007
|
-
if (items.length > 1) fail6();
|
|
14008
|
-
if (items.length && sub.status !== "canceled") {
|
|
14009
|
-
const item = items[0];
|
|
14010
|
-
if (!/^si_[A-Za-z0-9_]+$/.test(item.id) || item.price?.id !== seat.priceId || item.quantity !== 1) fail6();
|
|
14011
|
-
const removed = await call("DELETE", `/v1/subscription_items/${encodeURIComponent(item.id)}`, { proration_behavior: "none" });
|
|
14012
|
-
if (removed.id !== item.id || removed.deleted !== true) fail6("named_seat_renewal_removal_unconfirmed");
|
|
14013
|
-
}
|
|
14014
|
-
const list = await call("GET", "/v1/refunds", { payment_intent: seat.providerPaymentIntentId, limit: 100 });
|
|
14015
|
-
if (!Array.isArray(list.data) || list.has_more !== false || list.data.length >= 100) fail6("named_seat_refund_history_incomplete");
|
|
14016
|
-
let refundedCents = 0, pendingCents = 0;
|
|
14017
|
-
const refundIds = [];
|
|
14018
|
-
for (const refund of list.data) {
|
|
14019
|
-
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();
|
|
14020
|
-
if (refund.status === "succeeded") {
|
|
14021
|
-
refundedCents += refund.amount;
|
|
14022
|
-
refundIds.push(refund.id);
|
|
14023
|
-
} else if (refund.status === "pending" || refund.status === "requires_action") pendingCents += refund.amount;
|
|
14024
|
-
}
|
|
14025
|
-
if (refundedCents + pendingCents > seat.amountCents) fail6();
|
|
14026
|
-
if (pendingCents) fail6("named_seat_refund_pending");
|
|
14027
|
-
if (refundedCents < seat.amountCents) {
|
|
14028
|
-
const remaining = seat.amountCents - refundedCents;
|
|
14029
|
-
const refund = await call("POST", "/v1/refunds", {
|
|
14030
|
-
payment_intent: seat.providerPaymentIntentId,
|
|
14031
|
-
amount: remaining,
|
|
14032
|
-
reason: "requested_by_customer",
|
|
14033
|
-
metadata
|
|
14034
|
-
}, `named-seat-refund:${seat.id}:${remaining}`);
|
|
14035
|
-
if (refund.payment_intent !== seat.providerPaymentIntentId || refund.amount !== remaining || refund.currency !== seat.currency || !/^re_[A-Za-z0-9_]+$/.test(refund.id)) fail6();
|
|
14036
|
-
if (refund.status !== "succeeded") fail6("named_seat_refund_pending");
|
|
14037
|
-
refundedCents += remaining;
|
|
14038
|
-
refundIds.push(refund.id);
|
|
14039
|
-
}
|
|
14040
|
-
return { refundedCents, refundIds };
|
|
14041
|
-
}
|
|
14042
|
-
|
|
14043
|
-
// src/membership-seat-refund.ts
|
|
14044
|
-
async function requestNamedSeatRefund(db, input, now) {
|
|
14045
|
-
membershipClock(now);
|
|
14046
|
-
const state = await loadMembershipAuthorityState(db, input);
|
|
14047
|
-
const seat = await membershipLoad(db, "networkSeat", input.seatId);
|
|
14048
|
-
requireMembershipScope(seat, input);
|
|
14049
|
-
const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
|
|
14050
|
-
requireMembershipScope(member, input);
|
|
14051
|
-
if (seat.status === "refunded" || seat.status === "refund_pending") return;
|
|
14052
|
-
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");
|
|
14053
|
-
const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
|
|
14054
|
-
await db.transact([
|
|
14055
|
-
membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
|
|
14056
|
-
...entitlement ? [membershipUpdate("networkEntitlement", String(entitlement.id), { status: "closed", revision: Number(entitlement.revision) + 1, updatedAt: now })] : [],
|
|
14057
|
-
membershipUpdate("membershipEffect", `${seat.id}:refund`, {
|
|
14058
|
-
id: `${seat.id}:refund`,
|
|
14059
|
-
chapterId: input.chapterId,
|
|
14060
|
-
environment: input.environment,
|
|
14061
|
-
runtime: input.runtime,
|
|
14062
|
-
membershipId: member.id,
|
|
14063
|
-
seatId: seat.id,
|
|
14064
|
-
kind: "refund_seat",
|
|
14065
|
-
status: "pending",
|
|
14066
|
-
attempts: 0,
|
|
14067
|
-
createdAt: now,
|
|
14068
|
-
updatedAt: now
|
|
14069
|
-
}),
|
|
14070
|
-
...seat.recipientUserId ? [membershipUpdate("membershipEffect", `${seat.id}:revoke`, {
|
|
14071
|
-
id: `${seat.id}:revoke`,
|
|
14072
|
-
chapterId: input.chapterId,
|
|
14073
|
-
environment: input.environment,
|
|
14074
|
-
runtime: input.runtime,
|
|
14075
|
-
membershipId: member.id,
|
|
14076
|
-
seatId: seat.id,
|
|
14077
|
-
kind: "revoke_named_recipient_access",
|
|
14078
|
-
status: "pending",
|
|
14079
|
-
attempts: 0,
|
|
14080
|
-
createdAt: now,
|
|
14081
|
-
updatedAt: now
|
|
14082
|
-
})] : []
|
|
14083
|
-
], { mutationId: `named-seat-refund-request:${seat.id}`, guards: [
|
|
14084
|
-
{ ns: "networkSeat", id: input.seatId, exists: true, equals: { revision: seat.revision } },
|
|
14085
|
-
{ ns: "networkMembership", id: String(member.id), exists: true, equals: { revision: member.revision } },
|
|
14086
|
-
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
|
|
14087
|
-
...entitlement ? [{ ns: "networkEntitlement", id: String(entitlement.id), exists: true, equals: { revision: entitlement.revision } }] : []
|
|
14088
|
-
] });
|
|
14089
|
-
}
|
|
14090
|
-
async function completeNamedSeatRefund(db, seatId, secretKey, now) {
|
|
14091
|
-
membershipClock(now);
|
|
14092
|
-
const seat = await membershipLoad(db, "networkSeat", seatId);
|
|
14093
|
-
if (!seat) return membershipFail("named_seat_not_found");
|
|
14094
|
-
await loadMembershipAuthorityState(db, seat);
|
|
14095
|
-
if (seat.status === "refunded") return;
|
|
14096
|
-
if (seat.status !== "refund_pending") membershipFail("named_seat_refund_not_ready");
|
|
14097
|
-
const result = await refundNamedSeatStripePayment(secretKey, seat);
|
|
14098
|
-
await db.transact([
|
|
14099
|
-
membershipUpdate("networkSeat", seatId, {
|
|
14100
|
-
status: "refunded",
|
|
14101
|
-
refundedCents: result.refundedCents,
|
|
14102
|
-
providerRefundIds: result.refundIds,
|
|
14103
|
-
revision: Number(seat.revision) + 1,
|
|
14104
|
-
updatedAt: now
|
|
14105
|
-
}),
|
|
14106
|
-
membershipUpdate("membershipEffect", `${seatId}:refund`, { status: "complete", updatedAt: now })
|
|
14107
|
-
], { mutationId: `named-seat-refunded:${seatId}`, guards: [
|
|
14108
|
-
{ ns: "networkSeat", id: seatId, exists: true, equals: { status: "refund_pending", revision: seat.revision } }
|
|
14109
|
-
] });
|
|
14110
|
-
}
|
|
14111
|
-
|
|
14112
14363
|
// src/membership-seat-provider-refund.ts
|
|
14113
14364
|
async function confirmNamedSeatProviderRefund(db, input, now) {
|
|
14114
14365
|
const seat = await membershipLoad(db, "networkSeat", input.seatId);
|
|
@@ -14130,7 +14381,7 @@ async function readNamedSeatInvitation(db, input, now) {
|
|
|
14130
14381
|
await loadMembershipAuthorityState(db, target);
|
|
14131
14382
|
const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
|
|
14132
14383
|
requireMembershipScope(member, target);
|
|
14133
|
-
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)
|
|
14384
|
+
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");
|
|
14134
14385
|
return {
|
|
14135
14386
|
seatId: input.seatId,
|
|
14136
14387
|
primaryApplicationId: target.applicationId,
|
|
@@ -14160,7 +14411,7 @@ async function readMembershipChapterEffect(db, input, now) {
|
|
|
14160
14411
|
requireMembershipScope(member, input);
|
|
14161
14412
|
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");
|
|
14162
14413
|
let applicable;
|
|
14163
|
-
if (input.kind === "invite_named_recipient") applicable = seat.status === "pending_acceptance" && seat.acceptedAt === void 0 && now < Number(
|
|
14414
|
+
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));
|
|
14164
14415
|
else if (input.kind === "revoke_named_recipient_access") applicable = ["refund_pending", "refunded"].includes(String(seat.status));
|
|
14165
14416
|
else applicable = (await readNamedSeatAccess(db, {
|
|
14166
14417
|
...input,
|
|
@@ -14189,6 +14440,7 @@ var MEMBERSHIP_SEAT_ACTION_FIELDS = {
|
|
|
14189
14440
|
"seat-reservations": ["recipientName", "recipientEmail", "purchaserEmail", "purchaserUserId"],
|
|
14190
14441
|
"seat-payments": ["seatId", "eventId"],
|
|
14191
14442
|
"seat-refunds": ["seatId", "eventId"],
|
|
14443
|
+
"seat-operator-refund": ["seatId"],
|
|
14192
14444
|
"seat-accept": recipient,
|
|
14193
14445
|
"seat-approve": recipient,
|
|
14194
14446
|
"seat-decline": recipient,
|
|
@@ -14257,6 +14509,7 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
|
|
|
14257
14509
|
if (refunded?.status !== "refunded" || refunded.refundedCents !== seat.amountCents) throw new Error("named seat refund unconfirmed");
|
|
14258
14510
|
return { seatId: seat.id, status: "refunded", refundedCents: refunded.refundedCents, subscriptionCanceled: false };
|
|
14259
14511
|
}
|
|
14512
|
+
if (action === "seat-operator-refund") return refundNamedSeatByOperator(db, { ...target, seatId: String(input.seatId) }, secret, now);
|
|
14260
14513
|
if (action === "seat-checkouts") return prepareNamedMembershipSeatPayment(db, {
|
|
14261
14514
|
...target,
|
|
14262
14515
|
priceId: peer.seatPriceId,
|
|
@@ -14345,7 +14598,7 @@ function createMembershipAuthorityHandler(options) {
|
|
|
14345
14598
|
if (req.method !== "POST") return respond({ error: "method_not_allowed" }, 405);
|
|
14346
14599
|
const sender = req.headers.get(import_db15.FEDERATION_HEADERS.sender);
|
|
14347
14600
|
const action = url.pathname.split("/").at(-1);
|
|
14348
|
-
if (!["offers", "checkouts", "payments", "approvals", "refunds", "renewals", "access", "restarts", "cancel-renewal", ...Object.keys(MEMBERSHIP_SEAT_ACTION_FIELDS)].includes(action ?? "")) return respond({ error: "unauthorized" }, 401);
|
|
14601
|
+
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);
|
|
14349
14602
|
const peer = peers.find((candidate) => candidate.appId === sender && url.pathname === `${MEMBERSHIP_AUTHORITY_PATH}/${candidate.runtime}/${action}` && !url.search);
|
|
14350
14603
|
if (!peer) return respond({ error: "unauthorized" }, 401);
|
|
14351
14604
|
let body;
|
|
@@ -14367,13 +14620,23 @@ function createMembershipAuthorityHandler(options) {
|
|
|
14367
14620
|
let input;
|
|
14368
14621
|
try {
|
|
14369
14622
|
input = JSON.parse(verified.body);
|
|
14370
|
-
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] ?? []];
|
|
14623
|
+
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] ?? []];
|
|
14371
14624
|
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])))) {
|
|
14372
14625
|
throw new TypeError("invalid request");
|
|
14373
14626
|
}
|
|
14374
14627
|
} catch {
|
|
14375
14628
|
return respond({ error: "invalid_offer_request" }, 400);
|
|
14376
14629
|
}
|
|
14630
|
+
if (action === "offer-preview") {
|
|
14631
|
+
return respond({ ok: true, preview: await previewMembershipCommercialOffer(db, {
|
|
14632
|
+
appId: peer.appId,
|
|
14633
|
+
chapterId: peer.chapterId,
|
|
14634
|
+
environment: peer.environment,
|
|
14635
|
+
runtime: peer.runtime,
|
|
14636
|
+
skuId: String(input.skuId),
|
|
14637
|
+
now: options.now?.() ?? Date.now()
|
|
14638
|
+
}) });
|
|
14639
|
+
}
|
|
14377
14640
|
if (Object.hasOwn(MEMBERSHIP_SEAT_ACTION_FIELDS, action)) {
|
|
14378
14641
|
return respond({ ok: true, seat: await handleMembershipSeatAction(
|
|
14379
14642
|
action,
|
|
@@ -14767,7 +15030,7 @@ async function drainMembershipEffects(db, options, now) {
|
|
|
14767
15030
|
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !options.stripeSecretKey) throw new TypeError("invalid membership recovery configuration");
|
|
14768
15031
|
await loadMembershipAuthorityState(db, scope2);
|
|
14769
15032
|
const matches2 = (row2) => Object.entries(scope2).every(([key3, value]) => row2[key3] === value);
|
|
14770
|
-
let
|
|
15033
|
+
let pending = 0, complete = 0, more = false;
|
|
14771
15034
|
for (const status2 of ["payment_pending", "payment_processing", "payment_canceling"]) {
|
|
14772
15035
|
const seats = (await db.query({ networkSeat: { $: { where: { ...scope2, status: status2 }, order: { updatedAt: "asc" }, limit: limit + 1 } } })).networkSeat ?? [];
|
|
14773
15036
|
more ||= seats.length > limit;
|
|
@@ -14785,26 +15048,6 @@ async function drainMembershipEffects(db, options, now) {
|
|
|
14785
15048
|
}
|
|
14786
15049
|
}
|
|
14787
15050
|
}
|
|
14788
|
-
for (const status2 of ["renewal_pending", "pending_acceptance"]) {
|
|
14789
|
-
const seats = (await db.query({ networkSeat: { $: { where: { ...scope2, status: status2 }, order: { acceptanceEndsAt: "asc" }, limit: limit + 1 } } })).networkSeat ?? [];
|
|
14790
|
-
more ||= seats.length > limit;
|
|
14791
|
-
for (const seat of seats.slice(0, limit)) {
|
|
14792
|
-
if (!matches2(seat)) throw new Error("membership recovery scope mismatch");
|
|
14793
|
-
if (seat.acceptedAt !== void 0 || now < Number(seat.acceptanceEndsAt)) continue;
|
|
14794
|
-
try {
|
|
14795
|
-
await requestNamedSeatRefund(db, {
|
|
14796
|
-
...scope2,
|
|
14797
|
-
applicationId: String(seat.applicationId),
|
|
14798
|
-
skuId: String(seat.skuId),
|
|
14799
|
-
seatId: String(seat.id),
|
|
14800
|
-
reason: "expired"
|
|
14801
|
-
}, now);
|
|
14802
|
-
expired++;
|
|
14803
|
-
} catch {
|
|
14804
|
-
pending++;
|
|
14805
|
-
}
|
|
14806
|
-
}
|
|
14807
|
-
}
|
|
14808
15051
|
const effects = (await db.query({ membershipEffect: { $: { where: {
|
|
14809
15052
|
chapterId: scope2.chapterId,
|
|
14810
15053
|
environment: scope2.environment,
|
|
@@ -14866,7 +15109,7 @@ async function drainMembershipEffects(db, options, now) {
|
|
|
14866
15109
|
pending++;
|
|
14867
15110
|
}
|
|
14868
15111
|
}
|
|
14869
|
-
return { complete, pending,
|
|
15112
|
+
return { complete, pending, more };
|
|
14870
15113
|
}
|
|
14871
15114
|
|
|
14872
15115
|
// src/approval-effects-retry.ts
|
|
@@ -15016,7 +15259,7 @@ function createMembershipChapterEffectRoute(options) {
|
|
|
15016
15259
|
config.emailTemplates = { ...config.emailTemplates, namedSeatInvitation: config.emailTemplates?.namedSeatInvitation ?? {
|
|
15017
15260
|
enabled: true,
|
|
15018
15261
|
subject: "{{purchaserName}} invited you to {{chapterName}}",
|
|
15019
|
-
text: "Hello {{firstName}},\n\n{{purchaserName}} has purchased a membership for you at {{chapterName}}. Your seat is already paid for.\n\nAccept your invitation
|
|
15262
|
+
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."
|
|
15020
15263
|
} };
|
|
15021
15264
|
const template = config.emailTemplates.namedSeatInvitation;
|
|
15022
15265
|
if (!template.text.includes("{{purchaserName}}")) config.emailTemplates.namedSeatInvitation = {
|
|
@@ -15089,6 +15332,12 @@ async function requestMembershipCommercialOffer(options) {
|
|
|
15089
15332
|
lifetimeMs: 6e4
|
|
15090
15333
|
});
|
|
15091
15334
|
}
|
|
15335
|
+
async function requestMembershipOfferPreview(options) {
|
|
15336
|
+
const result = await requestAuthority(options, "offer-preview", { skuId: options.skuId });
|
|
15337
|
+
const preview = result.preview;
|
|
15338
|
+
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");
|
|
15339
|
+
return preview;
|
|
15340
|
+
}
|
|
15092
15341
|
async function requestMembershipCheckoutBinding(options) {
|
|
15093
15342
|
const result = await requestAuthority(options, "checkouts", {
|
|
15094
15343
|
applicationId: options.applicationId,
|
|
@@ -15127,6 +15376,12 @@ async function requestNamedSeatDecline(options) {
|
|
|
15127
15376
|
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");
|
|
15128
15377
|
return seat;
|
|
15129
15378
|
}
|
|
15379
|
+
async function requestNamedSeatOperatorRefund(options) {
|
|
15380
|
+
const result = await requestAuthority(options, "seat-operator-refund", { applicationId: options.applicationId, skuId: options.skuId, seatId: options.seatId });
|
|
15381
|
+
const seat = result.seat;
|
|
15382
|
+
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");
|
|
15383
|
+
return seat;
|
|
15384
|
+
}
|
|
15130
15385
|
async function requestNamedSeatReservation(options) {
|
|
15131
15386
|
const result = await requestAuthority(options, "seat-reservations", {
|
|
15132
15387
|
applicationId: options.applicationId,
|
|
@@ -15312,6 +15567,7 @@ function createMembershipWorkerAdapter(options) {
|
|
|
15312
15567
|
return {
|
|
15313
15568
|
authority: {
|
|
15314
15569
|
issueOffer: async (input, env) => requestMembershipCommercialOffer({ ...input, ...await transport(input, env) }),
|
|
15570
|
+
previewOffer: async (input, env) => requestMembershipOfferPreview({ ...input, ...await transport(input, env) }),
|
|
15315
15571
|
bindCheckout: async (input, env) => requestMembershipCheckoutBinding({ ...input, ...await transport(input, env) }),
|
|
15316
15572
|
confirmPayment: async (input, env) => requestMembershipPayment({ ...input, ...await transport(input, env) }),
|
|
15317
15573
|
approveMembership: async (input, env) => requestMembershipApproval({ ...input, ...await transport(input, env) }),
|
|
@@ -15330,7 +15586,8 @@ function createMembershipWorkerAdapter(options) {
|
|
|
15330
15586
|
confirmRefund: async (input, env) => requestNamedSeatRefund2({ ...input, ...await transport(input, env) }),
|
|
15331
15587
|
accept: async (input, env) => requestNamedSeatAcceptance({ ...input, ...await transport(input, env) }),
|
|
15332
15588
|
approve: async (input, env) => requestNamedSeatApproval({ ...input, ...await transport(input, env) }),
|
|
15333
|
-
decline: async (input, env) => requestNamedSeatDecline({ ...input, ...await transport(input, env) })
|
|
15589
|
+
decline: async (input, env) => requestNamedSeatDecline({ ...input, ...await transport(input, env) }),
|
|
15590
|
+
operatorRefund: async (input, env) => requestNamedSeatOperatorRefund({ ...input, ...await transport(input, env) })
|
|
15334
15591
|
} } : {}
|
|
15335
15592
|
},
|
|
15336
15593
|
readEffect: async (input, env) => requestMembershipChapterEffect({ ...input, ...await transport(input, env) })
|
|
@@ -15585,6 +15842,8 @@ var BUILTIN_ROUTES = [
|
|
|
15585
15842
|
handleMembershipRestart,
|
|
15586
15843
|
handleMembershipCancellation,
|
|
15587
15844
|
handlePaymentRecovery,
|
|
15845
|
+
handleAdminNamedSeat,
|
|
15846
|
+
handleAdminMembershipSeats,
|
|
15588
15847
|
handleAdminMeetings,
|
|
15589
15848
|
handleAdminScheduling,
|
|
15590
15849
|
// Roster + identity
|