@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/index.js
CHANGED
|
@@ -1442,6 +1442,7 @@ var DEFAULT_ADMIN_COPY = {
|
|
|
1442
1442
|
},
|
|
1443
1443
|
records: {
|
|
1444
1444
|
workflowMissing: "This record is not linked to an application workflow.",
|
|
1445
|
+
invitation: "Invitation",
|
|
1445
1446
|
comms: "Comms",
|
|
1446
1447
|
commsHistory: "Comms history",
|
|
1447
1448
|
scheduling: "Scheduling",
|
|
@@ -1526,7 +1527,9 @@ var DEFAULT_JOIN_COPY = {
|
|
|
1526
1527
|
processing: "Processing\u2026",
|
|
1527
1528
|
payAndContinue: "Pay and continue",
|
|
1528
1529
|
incomplete: "The payment did not complete. Please try again.",
|
|
1529
|
-
consent: "I have read and agree to the pricing and refund policy above."
|
|
1530
|
+
consent: "I have read and agree to the pricing and refund policy above.",
|
|
1531
|
+
foundingMember: "Founding member rate",
|
|
1532
|
+
foundingDiscount: "Founding discount"
|
|
1530
1533
|
},
|
|
1531
1534
|
done: {
|
|
1532
1535
|
label: "You're booked",
|
|
@@ -3083,7 +3086,7 @@ function findTier(tiers2, tierId) {
|
|
|
3083
3086
|
if (!tierId) return tiers2.length === 1 ? tiers2[0] : null;
|
|
3084
3087
|
return tiers2.find((tier) => tier.id === tierId) ?? null;
|
|
3085
3088
|
}
|
|
3086
|
-
function joinConfigTiers(tiers2, defaults = {}) {
|
|
3089
|
+
function joinConfigTiers(tiers2, defaults = {}, founding = /* @__PURE__ */ new Map()) {
|
|
3087
3090
|
return tiers2.map((tier) => ({
|
|
3088
3091
|
id: tier.id,
|
|
3089
3092
|
name: tier.name,
|
|
@@ -3091,7 +3094,8 @@ function joinConfigTiers(tiers2, defaults = {}) {
|
|
|
3091
3094
|
currency: tier.currency ?? defaults.currency ?? "usd",
|
|
3092
3095
|
interval: tier.interval ?? defaults.interval ?? "year",
|
|
3093
3096
|
blurb: tier.blurb ?? "",
|
|
3094
|
-
free: tierIsFree(tier)
|
|
3097
|
+
free: tierIsFree(tier),
|
|
3098
|
+
...founding.has(tier.id) ? { founding: founding.get(tier.id) } : {}
|
|
3095
3099
|
}));
|
|
3096
3100
|
}
|
|
3097
3101
|
|
|
@@ -3377,24 +3381,63 @@ async function verifyIssuedMembershipOffer(value, expected) {
|
|
|
3377
3381
|
if (!value || typeof value !== "object" || Array.isArray(value)) return reject("invalid_offer_snapshot");
|
|
3378
3382
|
return restore({ ...value, approvedFoundingCount: 0 }, expected);
|
|
3379
3383
|
}
|
|
3380
|
-
async function
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
+
async function previewMembershipCommercialOffer(db, input) {
|
|
3385
|
+
for (const key3 of ["appId", "chapterId", "environment", "runtime", "skuId"]) {
|
|
3386
|
+
if (typeof input[key3] !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.:-]{1,159}$/.test(input[key3])) reject("invalid_offer_identity");
|
|
3387
|
+
}
|
|
3388
|
+
if (!Number.isSafeInteger(input.now) || input.now < 0) reject("invalid_offer_window");
|
|
3389
|
+
const state = await authoritativeState(db, input);
|
|
3390
|
+
const { offering, policy } = await currentPolicy(db, input, false, state);
|
|
3391
|
+
const approvedFoundingCount = policy ? state.cohortCounts[policy.cohortLabel] ?? 0 : 0;
|
|
3392
|
+
if (!Number.isSafeInteger(approvedFoundingCount) || Number(approvedFoundingCount) < 0) reject("invalid_cohort_count");
|
|
3393
|
+
const contract = createMembershipCommercialOffer({
|
|
3394
|
+
offering,
|
|
3395
|
+
discountPolicy: policy,
|
|
3396
|
+
approvedFoundingCount: Number(approvedFoundingCount),
|
|
3397
|
+
now: input.now,
|
|
3398
|
+
couponId: await couponId(input, policy?.id)
|
|
3399
|
+
});
|
|
3400
|
+
const discount = contract.billingTerms.discount;
|
|
3401
|
+
return {
|
|
3402
|
+
skuId: input.skuId,
|
|
3403
|
+
offeringVersionId: offering.id,
|
|
3404
|
+
basePriceCents: offering.basePriceCents,
|
|
3405
|
+
currency: offering.currency,
|
|
3406
|
+
interval: offering.interval,
|
|
3407
|
+
founding: contract.founding,
|
|
3408
|
+
...contract.founding && contract.discountPolicy ? { cohortLabel: contract.discountPolicy.cohortLabel } : {},
|
|
3409
|
+
dueTodayCents: contract.dueTodayCents,
|
|
3410
|
+
renewalCents: contract.renewalCents,
|
|
3411
|
+
...discount ? { discountDuration: discount.duration === "once" ? "first_term" : "while_active" } : {},
|
|
3412
|
+
...contract.initialTermEndsAt !== void 0 ? { initialTermEndsAt: contract.initialTermEndsAt } : {}
|
|
3413
|
+
};
|
|
3414
|
+
}
|
|
3415
|
+
async function authoritativeState(db, input) {
|
|
3416
|
+
const state = await load(db, "membershipAuthorityState", `${input.chapterId}:${input.environment}`);
|
|
3384
3417
|
if (!state || state.mode !== "authoritative" || state.chapterId !== input.chapterId || state.environment !== input.environment || !Number.isSafeInteger(state.revision) || Number(state.revision) < 1 || !state.cohortCounts || typeof state.cohortCounts !== "object" || Array.isArray(state.cohortCounts)) return reject("membership_authority_unavailable");
|
|
3385
|
-
|
|
3386
|
-
|
|
3418
|
+
return state;
|
|
3419
|
+
}
|
|
3420
|
+
async function currentPolicy(db, input, restartAware, state) {
|
|
3387
3421
|
const rows3 = await db.query({
|
|
3388
3422
|
membershipOfferingVersion: { $: { where: { chapterId: input.chapterId }, limit: 200 } },
|
|
3389
3423
|
discountPolicyVersion: { $: { where: { chapterId: input.chapterId }, limit: 200 } }
|
|
3390
3424
|
});
|
|
3391
3425
|
if (!Array.isArray(rows3.membershipOfferingVersion) || !Array.isArray(rows3.discountPolicyVersion) || rows3.membershipOfferingVersion.length >= 200 || rows3.discountPolicyVersion.length >= 200) return reject("policy_history_incomplete");
|
|
3392
|
-
const offerings = rows3.membershipOfferingVersion.map((
|
|
3393
|
-
const policies = rows3.discountPolicyVersion.map((
|
|
3426
|
+
const offerings = rows3.membershipOfferingVersion.map((row2) => membershipOfferingVersion(row2.snapshot)).filter((offer) => offer.chapterId === input.chapterId && offer.baseSkuId === input.skuId && offer.status === "published" && input.now >= offer.effectiveFrom && (offer.effectiveUntil === void 0 || input.now < offer.effectiveUntil));
|
|
3427
|
+
const policies = rows3.discountPolicyVersion.map((row2) => membershipDiscountPolicyVersion(row2.snapshot)).filter((policy2) => policy2.chapterId === input.chapterId && policy2.status === "published" && policy2.cohortEligibleSkuIds.includes(input.skuId) && input.now >= policy2.qualifyingStartsAt && input.now < policy2.qualifyingEndsAt);
|
|
3394
3428
|
const offering = offerings.sort((a, b) => b.version - a.version)[0];
|
|
3395
|
-
const restart = await load(db, "membershipRestart", (await membershipOfferIdForRequest({ ...input, requestId: "checkout_binding" })).replace(/^offer_/, "membership_"));
|
|
3429
|
+
const restart = restartAware && input.applicationId ? await load(db, "membershipRestart", (await membershipOfferIdForRequest({ ...input, applicationId: input.applicationId, requestId: "checkout_binding" })).replace(/^offer_/, "membership_")) : void 0;
|
|
3396
3430
|
const policy = restart ? void 0 : policies.sort((a, b) => b.version - a.version)[0];
|
|
3397
3431
|
if (!offering || offerings.filter((item) => item.version === offering.version).length !== 1 || policy && policies.filter((item) => item.version === policy.version).length !== 1) reject("ambiguous_membership_policy");
|
|
3432
|
+
return { offering, policy };
|
|
3433
|
+
}
|
|
3434
|
+
async function issueMembershipCommercialOffer(db, input) {
|
|
3435
|
+
validate(input);
|
|
3436
|
+
const stateId = `${input.chapterId}:${input.environment}`;
|
|
3437
|
+
const state = await authoritativeState(db, input);
|
|
3438
|
+
const existing = await load(db, "membershipCheckoutOffer", input.offerId);
|
|
3439
|
+
if (existing) return restore(existing, input);
|
|
3440
|
+
const { offering, policy } = await currentPolicy(db, input, true, state);
|
|
3398
3441
|
const approvedFoundingCount = policy ? state.cohortCounts[policy.cohortLabel] ?? 0 : 0;
|
|
3399
3442
|
if (!Number.isSafeInteger(approvedFoundingCount) || Number(approvedFoundingCount) < 0) reject("invalid_cohort_count");
|
|
3400
3443
|
const contract = createMembershipCommercialOffer({
|
|
@@ -4581,7 +4624,7 @@ async function confirmNamedMembershipSeatPayment(db, input, now) {
|
|
|
4581
4624
|
membershipUpdate("networkSeat", input.seatId, {
|
|
4582
4625
|
status: status2,
|
|
4583
4626
|
purchasedAt: paidAt,
|
|
4584
|
-
acceptanceEndsAt:
|
|
4627
|
+
acceptanceEndsAt: Number(member.termEndsAt),
|
|
4585
4628
|
termStartedAt: paidAt,
|
|
4586
4629
|
termEndsAt: member.termEndsAt,
|
|
4587
4630
|
paymentEventId: input.eventId,
|
|
@@ -4629,7 +4672,7 @@ async function completeNamedSeatRenewal(db, seatId, secretKey, now) {
|
|
|
4629
4672
|
if (seat.status !== "renewal_pending" || !seat.purchasedAt) membershipFail("named_seat_renewal_not_ready");
|
|
4630
4673
|
const member = await membershipLoad(db, "networkMembership", target.primaryMembershipId);
|
|
4631
4674
|
requireMembershipScope(member, target);
|
|
4632
|
-
if (now >= Number(
|
|
4675
|
+
if (now >= Number(member.termEndsAt) || Number(member.refundedCents) !== 0 || !["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status))) membershipFail("named_seat_refund_required");
|
|
4633
4676
|
const itemId = await attachNamedSeatStripeRenewal(secretKey, target);
|
|
4634
4677
|
const entitlementId = `${seatId}:entitlement`, effectId = `${seatId}:attach-renewal`;
|
|
4635
4678
|
try {
|
|
@@ -4704,7 +4747,7 @@ async function recordRecipient(db, input, now, approve) {
|
|
|
4704
4747
|
requireMembershipScope(entitlement, input);
|
|
4705
4748
|
if (!["pending_acceptance", "pending_approval", "pending_primary", "active"].includes(String(seat.status)) || !["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || Number(member.refundedCents) !== 0 || now >= Number(member.termEndsAt) || !seat.providerPaymentIntentId || !seat.providerSubscriptionItemId) membershipFail("named_seat_acceptance_unavailable");
|
|
4706
4749
|
if (seat.acceptedAt === void 0) {
|
|
4707
|
-
if (approve ||
|
|
4750
|
+
if (approve || normalizeChapterEmail(input.verifiedRecipientEmail) !== seat.recipientEmailNormalized) {
|
|
4708
4751
|
membershipFail("named_seat_recipient_verification_required");
|
|
4709
4752
|
}
|
|
4710
4753
|
} else if (seat.recipientUserId !== input.recipientUserId || seat.recipientApplicationId !== input.recipientApplicationId) {
|
|
@@ -4817,7 +4860,7 @@ async function requestNamedSeatRefund(db, input, now) {
|
|
|
4817
4860
|
const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
|
|
4818
4861
|
requireMembershipScope(member, input);
|
|
4819
4862
|
if (seat.status === "refunded" || seat.status === "refund_pending") return;
|
|
4820
|
-
if (!seat.purchasedAt || !seat.providerPaymentIntentId || input.reason === "declined" && seat.journeyApprovedAt !== void 0 || input.reason === "
|
|
4863
|
+
if (!seat.purchasedAt || !seat.providerPaymentIntentId || input.reason === "declined" && seat.journeyApprovedAt !== void 0 || input.reason === "primary_closed" && !["refunded", "declined"].includes(String(member.status))) membershipFail("named_seat_refund_not_eligible");
|
|
4821
4864
|
const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
|
|
4822
4865
|
await db.transact([
|
|
4823
4866
|
membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
|
|
@@ -4889,6 +4932,19 @@ async function confirmNamedSeatProviderRefund(db, input, now) {
|
|
|
4889
4932
|
await requestNamedSeatRefund(db, { ...input, reason: "provider_refund" }, now);
|
|
4890
4933
|
}
|
|
4891
4934
|
|
|
4935
|
+
// src/membership-seat-operator-refund.ts
|
|
4936
|
+
async function refundNamedSeatByOperator(db, input, stripeSecretKey, now) {
|
|
4937
|
+
const seat = await membershipLoad(db, "networkSeat", input.seatId);
|
|
4938
|
+
requireMembershipScope(seat, input);
|
|
4939
|
+
await requestNamedSeatRefund(db, { ...input, reason: "operator" }, now);
|
|
4940
|
+
await completeNamedSeatRefund(db, input.seatId, stripeSecretKey, now);
|
|
4941
|
+
const refunded = await membershipLoad(db, "networkSeat", input.seatId);
|
|
4942
|
+
if (refunded?.status !== "refunded" || !Number.isSafeInteger(refunded.refundedCents) || Number(refunded.refundedCents) <= 0) {
|
|
4943
|
+
throw new Error("named seat refund unconfirmed");
|
|
4944
|
+
}
|
|
4945
|
+
return { seatId: String(seat.id), status: "refunded", refundedCents: Number(refunded.refundedCents), subscriptionCanceled: false };
|
|
4946
|
+
}
|
|
4947
|
+
|
|
4892
4948
|
// src/membership-seat-offer.ts
|
|
4893
4949
|
async function namedSeatOfferDigest(offer) {
|
|
4894
4950
|
const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([
|
|
@@ -4901,7 +4957,7 @@ async function namedSeatOfferDigest(offer) {
|
|
|
4901
4957
|
"named-only",
|
|
4902
4958
|
"full-upfront",
|
|
4903
4959
|
"auto-renew-coterm",
|
|
4904
|
-
"
|
|
4960
|
+
"no-automatic-refund"
|
|
4905
4961
|
])));
|
|
4906
4962
|
return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
4907
4963
|
}
|
|
@@ -4941,7 +4997,7 @@ async function readNamedSeatInvitation(db, input, now) {
|
|
|
4941
4997
|
await loadMembershipAuthorityState(db, target);
|
|
4942
4998
|
const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
|
|
4943
4999
|
requireMembershipScope(member, target);
|
|
4944
|
-
if (!["pending_acceptance", "pending_approval", "pending_primary", "active"].includes(String(seat.status)) || !["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || now >= Number(member.termEndsAt)
|
|
5000
|
+
if (!["pending_acceptance", "pending_approval", "pending_primary", "active"].includes(String(seat.status)) || !["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || now >= Number(member.termEndsAt)) membershipFail("named_seat_invitation_unavailable");
|
|
4945
5001
|
return {
|
|
4946
5002
|
seatId: input.seatId,
|
|
4947
5003
|
primaryApplicationId: target.applicationId,
|
|
@@ -4971,7 +5027,7 @@ async function readMembershipChapterEffect(db, input, now) {
|
|
|
4971
5027
|
requireMembershipScope(member, input);
|
|
4972
5028
|
if (!effect || effect.kind !== input.kind || effect.seatId !== seat.id || effect.membershipId !== member.id || ["chapterId", "environment", "runtime"].some((key3) => effect[key3] !== seat[key3]) || !["invite_named_recipient", "grant_named_recipient_access", "revoke_named_recipient_access"].includes(input.kind)) throw new Error("invalid chapter effect");
|
|
4973
5029
|
let applicable;
|
|
4974
|
-
if (input.kind === "invite_named_recipient") applicable = seat.status === "pending_acceptance" && seat.acceptedAt === void 0 && now < Number(
|
|
5030
|
+
if (input.kind === "invite_named_recipient") applicable = seat.status === "pending_acceptance" && seat.acceptedAt === void 0 && now < Number(member.termEndsAt) && ["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status));
|
|
4975
5031
|
else if (input.kind === "revoke_named_recipient_access") applicable = ["refund_pending", "refunded"].includes(String(seat.status));
|
|
4976
5032
|
else applicable = (await readNamedSeatAccess(db, {
|
|
4977
5033
|
...input,
|
|
@@ -5000,6 +5056,7 @@ var MEMBERSHIP_SEAT_ACTION_FIELDS = {
|
|
|
5000
5056
|
"seat-reservations": ["recipientName", "recipientEmail", "purchaserEmail", "purchaserUserId"],
|
|
5001
5057
|
"seat-payments": ["seatId", "eventId"],
|
|
5002
5058
|
"seat-refunds": ["seatId", "eventId"],
|
|
5059
|
+
"seat-operator-refund": ["seatId"],
|
|
5003
5060
|
"seat-accept": recipient,
|
|
5004
5061
|
"seat-approve": recipient,
|
|
5005
5062
|
"seat-decline": recipient,
|
|
@@ -5068,6 +5125,7 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
|
|
|
5068
5125
|
if (refunded?.status !== "refunded" || refunded.refundedCents !== seat.amountCents) throw new Error("named seat refund unconfirmed");
|
|
5069
5126
|
return { seatId: seat.id, status: "refunded", refundedCents: refunded.refundedCents, subscriptionCanceled: false };
|
|
5070
5127
|
}
|
|
5128
|
+
if (action === "seat-operator-refund") return refundNamedSeatByOperator(db, { ...target, seatId: String(input.seatId) }, secret, now);
|
|
5071
5129
|
if (action === "seat-checkouts") return prepareNamedMembershipSeatPayment(db, {
|
|
5072
5130
|
...target,
|
|
5073
5131
|
priceId: peer.seatPriceId,
|
|
@@ -5156,7 +5214,7 @@ function createMembershipAuthorityHandler(options) {
|
|
|
5156
5214
|
if (req.method !== "POST") return respond({ error: "method_not_allowed" }, 405);
|
|
5157
5215
|
const sender = req.headers.get(FEDERATION_HEADERS.sender);
|
|
5158
5216
|
const action = url.pathname.split("/").at(-1);
|
|
5159
|
-
if (!["offers", "checkouts", "payments", "approvals", "refunds", "renewals", "access", "restarts", "cancel-renewal", ...Object.keys(MEMBERSHIP_SEAT_ACTION_FIELDS)].includes(action ?? "")) return respond({ error: "unauthorized" }, 401);
|
|
5217
|
+
if (!["offers", "offer-preview", "checkouts", "payments", "approvals", "refunds", "renewals", "access", "restarts", "cancel-renewal", ...Object.keys(MEMBERSHIP_SEAT_ACTION_FIELDS)].includes(action ?? "")) return respond({ error: "unauthorized" }, 401);
|
|
5160
5218
|
const peer = peers.find((candidate) => candidate.appId === sender && url.pathname === `${MEMBERSHIP_AUTHORITY_PATH}/${candidate.runtime}/${action}` && !url.search);
|
|
5161
5219
|
if (!peer) return respond({ error: "unauthorized" }, 401);
|
|
5162
5220
|
let body;
|
|
@@ -5178,13 +5236,23 @@ function createMembershipAuthorityHandler(options) {
|
|
|
5178
5236
|
let input;
|
|
5179
5237
|
try {
|
|
5180
5238
|
input = JSON.parse(verified.body);
|
|
5181
|
-
const fields = action === "seat-invitation" ? MEMBERSHIP_SEAT_ACTION_FIELDS[action] : ["applicationId", "skuId", ...action === "offers" ? ["requestId"] : action === "checkouts" ? ["offerId", "offerDigest", "quoteDigest", "priceId"] : action === "payments" ? ["eventId", "providerSubscriptionId", "providerCustomerId"] : action === "renewals" ? ["eventId"] : action === "restarts" ? ["previousSubscriptionId"] : MEMBERSHIP_SEAT_ACTION_FIELDS[action] ?? []];
|
|
5239
|
+
const fields = action === "seat-invitation" ? MEMBERSHIP_SEAT_ACTION_FIELDS[action] : action === "offer-preview" ? ["skuId"] : ["applicationId", "skuId", ...action === "offers" ? ["requestId"] : action === "checkouts" ? ["offerId", "offerDigest", "quoteDigest", "priceId"] : action === "payments" ? ["eventId", "providerSubscriptionId", "providerCustomerId"] : action === "renewals" ? ["eventId"] : action === "restarts" ? ["previousSubscriptionId"] : MEMBERSHIP_SEAT_ACTION_FIELDS[action] ?? []];
|
|
5182
5240
|
if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key3) => !fields.includes(key3)) || !fields.every((key3) => typeof input[key3] === "string" && (["recipientName", "recipientEmail", "purchaserEmail", "verifiedRecipientEmail"].includes(key3) ? input[key3].length > 0 && input[key3].length <= 254 && !/[\u0000-\u001f\u007f]/.test(input[key3]) : SAFE.test(input[key3])))) {
|
|
5183
5241
|
throw new TypeError("invalid request");
|
|
5184
5242
|
}
|
|
5185
5243
|
} catch {
|
|
5186
5244
|
return respond({ error: "invalid_offer_request" }, 400);
|
|
5187
5245
|
}
|
|
5246
|
+
if (action === "offer-preview") {
|
|
5247
|
+
return respond({ ok: true, preview: await previewMembershipCommercialOffer(db, {
|
|
5248
|
+
appId: peer.appId,
|
|
5249
|
+
chapterId: peer.chapterId,
|
|
5250
|
+
environment: peer.environment,
|
|
5251
|
+
runtime: peer.runtime,
|
|
5252
|
+
skuId: String(input.skuId),
|
|
5253
|
+
now: options.now?.() ?? Date.now()
|
|
5254
|
+
}) });
|
|
5255
|
+
}
|
|
5188
5256
|
if (Object.hasOwn(MEMBERSHIP_SEAT_ACTION_FIELDS, action)) {
|
|
5189
5257
|
return respond({ ok: true, seat: await handleMembershipSeatAction(
|
|
5190
5258
|
action,
|
|
@@ -5335,6 +5403,12 @@ async function requestMembershipCommercialOffer(options) {
|
|
|
5335
5403
|
lifetimeMs: 6e4
|
|
5336
5404
|
});
|
|
5337
5405
|
}
|
|
5406
|
+
async function requestMembershipOfferPreview(options) {
|
|
5407
|
+
const result = await requestAuthority(options, "offer-preview", { skuId: options.skuId });
|
|
5408
|
+
const preview = result.preview;
|
|
5409
|
+
if (!preview || preview.skuId !== options.skuId || typeof preview.founding !== "boolean" || !Number.isSafeInteger(preview.basePriceCents) || !Number.isSafeInteger(preview.dueTodayCents) || preview.dueTodayCents < 0 || preview.dueTodayCents > preview.basePriceCents || !Number.isSafeInteger(preview.renewalCents) || !/^[a-z]{3}$/.test(preview.currency) || !["month", "year"].includes(preview.interval) || preview.cohortLabel !== void 0 && typeof preview.cohortLabel !== "string" || preview.discountDuration !== void 0 && !["first_term", "while_active"].includes(preview.discountDuration)) throw new Error("invalid offer preview");
|
|
5410
|
+
return preview;
|
|
5411
|
+
}
|
|
5338
5412
|
async function requestMembershipCheckoutBinding(options) {
|
|
5339
5413
|
const result = await requestAuthority(options, "checkouts", {
|
|
5340
5414
|
applicationId: options.applicationId,
|
|
@@ -5373,6 +5447,12 @@ async function requestNamedSeatDecline(options) {
|
|
|
5373
5447
|
if (!seat || seat.seatId !== options.seatId || seat.status !== "refunded" || seat.subscriptionCanceled !== false || !Number.isSafeInteger(seat.refundedCents) || seat.refundedCents <= 0) throw new Error("invalid named seat refund receipt");
|
|
5374
5448
|
return seat;
|
|
5375
5449
|
}
|
|
5450
|
+
async function requestNamedSeatOperatorRefund(options) {
|
|
5451
|
+
const result = await requestAuthority(options, "seat-operator-refund", { applicationId: options.applicationId, skuId: options.skuId, seatId: options.seatId });
|
|
5452
|
+
const seat = result.seat;
|
|
5453
|
+
if (!seat || seat.seatId !== options.seatId || seat.status !== "refunded" || seat.subscriptionCanceled !== false || !Number.isSafeInteger(seat.refundedCents) || seat.refundedCents <= 0) throw new Error("invalid named seat refund receipt");
|
|
5454
|
+
return seat;
|
|
5455
|
+
}
|
|
5376
5456
|
async function requestNamedSeatReservation(options) {
|
|
5377
5457
|
const result = await requestAuthority(options, "seat-reservations", {
|
|
5378
5458
|
applicationId: options.applicationId,
|
|
@@ -5617,7 +5697,7 @@ async function drainMembershipEffects(db, options, now) {
|
|
|
5617
5697
|
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !options.stripeSecretKey) throw new TypeError("invalid membership recovery configuration");
|
|
5618
5698
|
await loadMembershipAuthorityState(db, scope2);
|
|
5619
5699
|
const matches2 = (row2) => Object.entries(scope2).every(([key3, value]) => row2[key3] === value);
|
|
5620
|
-
let
|
|
5700
|
+
let pending = 0, complete = 0, more = false;
|
|
5621
5701
|
for (const status2 of ["payment_pending", "payment_processing", "payment_canceling"]) {
|
|
5622
5702
|
const seats = (await db.query({ networkSeat: { $: { where: { ...scope2, status: status2 }, order: { updatedAt: "asc" }, limit: limit + 1 } } })).networkSeat ?? [];
|
|
5623
5703
|
more ||= seats.length > limit;
|
|
@@ -5635,26 +5715,6 @@ async function drainMembershipEffects(db, options, now) {
|
|
|
5635
5715
|
}
|
|
5636
5716
|
}
|
|
5637
5717
|
}
|
|
5638
|
-
for (const status2 of ["renewal_pending", "pending_acceptance"]) {
|
|
5639
|
-
const seats = (await db.query({ networkSeat: { $: { where: { ...scope2, status: status2 }, order: { acceptanceEndsAt: "asc" }, limit: limit + 1 } } })).networkSeat ?? [];
|
|
5640
|
-
more ||= seats.length > limit;
|
|
5641
|
-
for (const seat of seats.slice(0, limit)) {
|
|
5642
|
-
if (!matches2(seat)) throw new Error("membership recovery scope mismatch");
|
|
5643
|
-
if (seat.acceptedAt !== void 0 || now < Number(seat.acceptanceEndsAt)) continue;
|
|
5644
|
-
try {
|
|
5645
|
-
await requestNamedSeatRefund(db, {
|
|
5646
|
-
...scope2,
|
|
5647
|
-
applicationId: String(seat.applicationId),
|
|
5648
|
-
skuId: String(seat.skuId),
|
|
5649
|
-
seatId: String(seat.id),
|
|
5650
|
-
reason: "expired"
|
|
5651
|
-
}, now);
|
|
5652
|
-
expired++;
|
|
5653
|
-
} catch {
|
|
5654
|
-
pending++;
|
|
5655
|
-
}
|
|
5656
|
-
}
|
|
5657
|
-
}
|
|
5658
5718
|
const effects = (await db.query({ membershipEffect: { $: { where: {
|
|
5659
5719
|
chapterId: scope2.chapterId,
|
|
5660
5720
|
environment: scope2.environment,
|
|
@@ -5716,7 +5776,7 @@ async function drainMembershipEffects(db, options, now) {
|
|
|
5716
5776
|
pending++;
|
|
5717
5777
|
}
|
|
5718
5778
|
}
|
|
5719
|
-
return { complete, pending,
|
|
5779
|
+
return { complete, pending, more };
|
|
5720
5780
|
}
|
|
5721
5781
|
|
|
5722
5782
|
// src/schema-membership-offers.ts
|
|
@@ -9500,6 +9560,7 @@ export {
|
|
|
9500
9560
|
planMemberSystemEventReplay,
|
|
9501
9561
|
planMemberSystemMigration,
|
|
9502
9562
|
planPrivateRoleMigration,
|
|
9563
|
+
previewMembershipCommercialOffer,
|
|
9503
9564
|
projectApplicant,
|
|
9504
9565
|
projectBuilderLibraryDelivery,
|
|
9505
9566
|
projectMemberContent,
|
|
@@ -9532,6 +9593,7 @@ export {
|
|
|
9532
9593
|
requestMembershipChapterEffect,
|
|
9533
9594
|
requestMembershipCheckoutBinding,
|
|
9534
9595
|
requestMembershipCommercialOffer,
|
|
9596
|
+
requestMembershipOfferPreview,
|
|
9535
9597
|
requestMembershipPayment,
|
|
9536
9598
|
requestMembershipRefund,
|
|
9537
9599
|
requestMembershipRenewal,
|
|
@@ -9543,6 +9605,7 @@ export {
|
|
|
9543
9605
|
requestNamedSeatDecline,
|
|
9544
9606
|
requestNamedSeatInvitation,
|
|
9545
9607
|
requestNamedSeatOffer,
|
|
9608
|
+
requestNamedSeatOperatorRefund,
|
|
9546
9609
|
requestNamedSeatPayment,
|
|
9547
9610
|
requestNamedSeatRefund2 as requestNamedSeatRefund,
|
|
9548
9611
|
requestNamedSeatReservation,
|