@odla-ai/chapter 0.53.0 → 0.54.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 +28 -1
- package/dist/{chunk-PTC43J7F.js → chunk-NDZCR3OW.js} +5 -5
- package/dist/chunk-NDZCR3OW.js.map +1 -0
- package/dist/{chunk-TEHCORNC.js → chunk-SPEL4OGE.js} +57 -23
- package/dist/chunk-SPEL4OGE.js.map +1 -0
- package/dist/index.cjs +219 -106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -10
- package/dist/index.d.ts +30 -10
- package/dist/index.js +219 -106
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.js +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +8 -2
- package/dist/ui/member/index.d.ts +32 -1
- package/dist/ui/member/index.js +7 -1
- package/dist/worker/index.cjs +296 -128
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +84 -9
- package/dist/worker/index.d.ts +84 -9
- package/dist/worker/index.js +296 -128
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-PTC43J7F.js.map +0 -1
- package/dist/chunk-TEHCORNC.js.map +0 -1
package/dist/worker/index.cjs
CHANGED
|
@@ -36,6 +36,11 @@ __export(worker_exports, {
|
|
|
36
36
|
deliverMembershipChapterEffect: () => deliverMembershipChapterEffect,
|
|
37
37
|
deliverSignupControl: () => deliverSignupControl,
|
|
38
38
|
drainMembershipEffects: () => drainMembershipEffects,
|
|
39
|
+
grantIncludedNamedSeat: () => grantIncludedNamedSeat,
|
|
40
|
+
lowerSeatPrice: () => lowerSeatPrice,
|
|
41
|
+
namedSeatIncluded: () => namedSeatIncluded,
|
|
42
|
+
namedSeatRecipientState: () => namedSeatRecipientState,
|
|
43
|
+
readNamedSeatOffer: () => readNamedSeatOffer,
|
|
39
44
|
refundNamedSeatByOperator: () => refundNamedSeatByOperator,
|
|
40
45
|
renderActiveSignupControlHtml: () => renderActiveSignupControlHtml,
|
|
41
46
|
resolveNetworkTargetTransport: () => resolveNetworkTargetTransport,
|
|
@@ -1803,8 +1808,8 @@ var object = (value) => {
|
|
|
1803
1808
|
var id = (value) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9_.:-]{2,159}$/.test(value) ? value : fail2("id");
|
|
1804
1809
|
var integer2 = (value, min, max) => typeof value === "number" && Number.isSafeInteger(value) && value >= min && value <= max ? value : fail2("integer");
|
|
1805
1810
|
var time = (value) => {
|
|
1806
|
-
const
|
|
1807
|
-
return integer2(
|
|
1811
|
+
const at2 = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
|
|
1812
|
+
return integer2(at2, 0, 864e13);
|
|
1808
1813
|
};
|
|
1809
1814
|
var ids = (value, allowEmpty = false) => {
|
|
1810
1815
|
if (!Array.isArray(value) || value.length > 100 || !allowEmpty && value.length === 0) return fail2("ids");
|
|
@@ -1848,7 +1853,7 @@ function membershipOfferingVersion(value) {
|
|
|
1848
1853
|
term: normalizedTerm,
|
|
1849
1854
|
autoRenew: true,
|
|
1850
1855
|
paymentGraceDays: integer2(input.paymentGraceDays, 0, 60),
|
|
1851
|
-
seatPriceCents: integer2(input.seatPriceCents,
|
|
1856
|
+
seatPriceCents: integer2(input.seatPriceCents, 0, Number.MAX_SAFE_INTEGER),
|
|
1852
1857
|
...input.seatPurchaseWindowCalendarMonths === void 0 || input.seatPurchaseWindowCalendarMonths === null ? {} : { seatPurchaseWindowCalendarMonths: integer2(input.seatPurchaseWindowCalendarMonths, 1, 120) },
|
|
1853
1858
|
requiredDisclosures: ids(input.requiredDisclosures)
|
|
1854
1859
|
};
|
|
@@ -2936,7 +2941,7 @@ async function checkoutVariables(db, group, app) {
|
|
|
2936
2941
|
const frozen = await frozenCheckoutVariables(db, app);
|
|
2937
2942
|
return frozen.tierName ? frozen : { ...frozen, tierName: await catalogTierName(db, group, app) };
|
|
2938
2943
|
}
|
|
2939
|
-
function formatInstant(
|
|
2944
|
+
function formatInstant(at2, timeZone) {
|
|
2940
2945
|
return new Intl.DateTimeFormat("en-US", {
|
|
2941
2946
|
weekday: "long",
|
|
2942
2947
|
month: "long",
|
|
@@ -2946,7 +2951,7 @@ function formatInstant(at, timeZone) {
|
|
|
2946
2951
|
minute: "2-digit",
|
|
2947
2952
|
timeZoneName: "short",
|
|
2948
2953
|
timeZone
|
|
2949
|
-
}).format(new Date(
|
|
2954
|
+
}).format(new Date(at2));
|
|
2950
2955
|
}
|
|
2951
2956
|
var adminUrlFor = (origin) => `${origin}/admin/`;
|
|
2952
2957
|
var membersUrlFor = (origin) => `${origin}/members/`;
|
|
@@ -3894,6 +3899,181 @@ async function refundNamedSeatStripePayment(secretKey, seat) {
|
|
|
3894
3899
|
return { refundedCents, refundIds };
|
|
3895
3900
|
}
|
|
3896
3901
|
|
|
3902
|
+
// src/membership-seat-reservation.ts
|
|
3903
|
+
async function recipientKey(input, email) {
|
|
3904
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([input.appId, input.chapterId, input.environment, input.runtime, email])));
|
|
3905
|
+
return `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
3906
|
+
}
|
|
3907
|
+
async function reserveNamedMembershipSeat(db, input, now) {
|
|
3908
|
+
membershipClock(now);
|
|
3909
|
+
const name = typeof input.recipientName === "string" ? input.recipientName.trim().replace(/\s+/g, " ") : "";
|
|
3910
|
+
const email = normalizeChapterEmail(typeof input.recipientEmail === "string" ? input.recipientEmail : "");
|
|
3911
|
+
const purchaser = normalizeChapterEmail(typeof input.purchaserEmail === "string" ? input.purchaserEmail : "");
|
|
3912
|
+
if (name.length < 2 || name.length > 160 || /[\u0000-\u001f\u007f]/.test(input.recipientName) || email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) || !purchaser || email === purchaser || !/^price_[A-Za-z0-9_]+$/.test(input.priceId) || !/^user_[A-Za-z0-9_]+$/.test(input.purchaserUserId)) membershipFail("invalid_named_seat_recipient");
|
|
3913
|
+
const state = await loadMembershipAuthorityState(db, input);
|
|
3914
|
+
const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
|
|
3915
|
+
requireMembershipScope(member, input);
|
|
3916
|
+
if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
|
|
3917
|
+
const id6 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
3918
|
+
let seat = await membershipLoad(db, "networkSeat", id6);
|
|
3919
|
+
if (!seat) {
|
|
3920
|
+
const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
|
|
3921
|
+
const purchaseEndsAt = seatPurchaseDeadline(offering, member);
|
|
3922
|
+
if (!["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || Number(member.refundedCents) !== 0 || now < Number(member.initialTermStartedAt) || now >= Number(member.termEndsAt) || now > purchaseEndsAt || !Number.isSafeInteger(purchaseEndsAt) || member.namedSeatId !== void 0) {
|
|
3923
|
+
membershipFail("named_seat_purchase_unavailable");
|
|
3924
|
+
}
|
|
3925
|
+
const attrs = {
|
|
3926
|
+
id: id6,
|
|
3927
|
+
...membershipScope(input),
|
|
3928
|
+
primaryMembershipId: memberId,
|
|
3929
|
+
recipientName: name,
|
|
3930
|
+
recipientEmailNormalized: email,
|
|
3931
|
+
purchaserUserId: input.purchaserUserId,
|
|
3932
|
+
recipientClaimId: claimId,
|
|
3933
|
+
priceId: input.priceId,
|
|
3934
|
+
primaryPriceId: member.providerPriceId,
|
|
3935
|
+
amountCents: offering.seatPriceCents,
|
|
3936
|
+
currency: offering.currency,
|
|
3937
|
+
interval: offering.interval,
|
|
3938
|
+
providerCustomerId: member.providerCustomerId,
|
|
3939
|
+
providerSubscriptionId: member.providerSubscriptionId,
|
|
3940
|
+
status: "payment_pending",
|
|
3941
|
+
preparedAt: now,
|
|
3942
|
+
termEndsAt: member.termEndsAt,
|
|
3943
|
+
seatPurchaseEndsAt: purchaseEndsAt,
|
|
3944
|
+
offeringSnapshot: offering,
|
|
3945
|
+
revision: 1,
|
|
3946
|
+
createdAt: now,
|
|
3947
|
+
updatedAt: now
|
|
3948
|
+
};
|
|
3949
|
+
try {
|
|
3950
|
+
await db.transact([
|
|
3951
|
+
membershipUpdate("networkSeat", id6, attrs),
|
|
3952
|
+
membershipUpdate("networkSeatRecipient", claimId, {
|
|
3953
|
+
id: claimId,
|
|
3954
|
+
appId: input.appId,
|
|
3955
|
+
chapterId: input.chapterId,
|
|
3956
|
+
environment: input.environment,
|
|
3957
|
+
runtime: input.runtime,
|
|
3958
|
+
seatId: id6,
|
|
3959
|
+
recipientEmailNormalized: email,
|
|
3960
|
+
createdAt: now
|
|
3961
|
+
}),
|
|
3962
|
+
membershipUpdate("networkMembership", memberId, { namedSeatId: id6, revision: Number(member.revision) + 1, updatedAt: now })
|
|
3963
|
+
], { mutationId: `named-seat-reserve:${id6}`, guards: [
|
|
3964
|
+
{ ns: "networkSeat", id: id6, exists: false },
|
|
3965
|
+
{ ns: "networkSeatRecipient", id: claimId, exists: false },
|
|
3966
|
+
{ ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
|
|
3967
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
3968
|
+
] });
|
|
3969
|
+
} catch (error) {
|
|
3970
|
+
if (error.code !== "transact_guard_failed") throw error;
|
|
3971
|
+
}
|
|
3972
|
+
seat = await membershipLoad(db, "networkSeat", id6);
|
|
3973
|
+
if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
|
|
3974
|
+
}
|
|
3975
|
+
requireMembershipScope(seat, input);
|
|
3976
|
+
if (seat.primaryMembershipId !== memberId || seat.recipientName !== name || seat.recipientEmailNormalized !== email || seat.priceId !== input.priceId || seat.purchaserUserId !== input.purchaserUserId) membershipFail("named_seat_purchase_conflict");
|
|
3977
|
+
return seat;
|
|
3978
|
+
}
|
|
3979
|
+
|
|
3980
|
+
// src/membership-seat-included.ts
|
|
3981
|
+
var namedSeatIncluded = (seat) => Number(seat.amountCents) === 0;
|
|
3982
|
+
var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean(seat.providerPaymentIntentId && seat.providerSubscriptionItemId);
|
|
3983
|
+
function namedSeatRecipientState(seat) {
|
|
3984
|
+
switch (String(seat.status)) {
|
|
3985
|
+
case "renewal_pending":
|
|
3986
|
+
case "pending_acceptance":
|
|
3987
|
+
return "invited";
|
|
3988
|
+
case "pending_approval":
|
|
3989
|
+
case "pending_primary":
|
|
3990
|
+
return "accepted";
|
|
3991
|
+
case "active":
|
|
3992
|
+
return "joined";
|
|
3993
|
+
case "refund_pending":
|
|
3994
|
+
case "refunded":
|
|
3995
|
+
return seat.refundReason === "declined" ? "declined" : "refunded";
|
|
3996
|
+
case "canceled":
|
|
3997
|
+
return "canceled";
|
|
3998
|
+
default:
|
|
3999
|
+
return "payment_pending";
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
var OPEN = ["pending_acceptance", "pending_approval", "pending_primary", "active"];
|
|
4003
|
+
async function grantIncludedNamedSeat(db, input, now) {
|
|
4004
|
+
membershipClock(now);
|
|
4005
|
+
const seat = await reserveNamedMembershipSeat(db, input, now);
|
|
4006
|
+
if (!namedSeatIncluded(seat)) membershipFail("named_seat_not_included");
|
|
4007
|
+
const state = await loadMembershipAuthorityState(db, input);
|
|
4008
|
+
const member = await membershipLoad(db, "networkMembership", seat.primaryMembershipId);
|
|
4009
|
+
requireMembershipScope(member, input);
|
|
4010
|
+
if (!["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || Number(member.refundedCents) !== 0 || member.namedSeatId !== seat.id || now >= Number(member.termEndsAt)) {
|
|
4011
|
+
membershipFail("named_seat_purchase_unavailable");
|
|
4012
|
+
}
|
|
4013
|
+
if (seat.status === "payment_pending") {
|
|
4014
|
+
const entitlementId = `${seat.id}:entitlement`, inviteId = `${seat.id}:invite`, source = `included:${seat.id}`;
|
|
4015
|
+
try {
|
|
4016
|
+
await db.transact([
|
|
4017
|
+
membershipUpdate("networkSeat", seat.id, {
|
|
4018
|
+
status: "pending_acceptance",
|
|
4019
|
+
purchasedAt: now,
|
|
4020
|
+
termStartedAt: now,
|
|
4021
|
+
termEndsAt: member.termEndsAt,
|
|
4022
|
+
acceptanceEndsAt: Number(member.termEndsAt),
|
|
4023
|
+
entitlementId,
|
|
4024
|
+
revision: Number(seat.revision) + 1,
|
|
4025
|
+
updatedAt: now
|
|
4026
|
+
}),
|
|
4027
|
+
membershipUpdate("networkEntitlement", entitlementId, {
|
|
4028
|
+
id: entitlementId,
|
|
4029
|
+
...membershipScope(input),
|
|
4030
|
+
membershipId: member.id,
|
|
4031
|
+
seatId: seat.id,
|
|
4032
|
+
kind: "named_seat",
|
|
4033
|
+
status: "pending_acceptance",
|
|
4034
|
+
termStartedAt: now,
|
|
4035
|
+
termEndsAt: member.termEndsAt,
|
|
4036
|
+
offeringSnapshot: member.offeringSnapshot,
|
|
4037
|
+
sourceEventId: source,
|
|
4038
|
+
revision: 1,
|
|
4039
|
+
createdAt: now,
|
|
4040
|
+
updatedAt: now
|
|
4041
|
+
}),
|
|
4042
|
+
membershipUpdate("membershipEffect", inviteId, {
|
|
4043
|
+
id: inviteId,
|
|
4044
|
+
chapterId: input.chapterId,
|
|
4045
|
+
environment: input.environment,
|
|
4046
|
+
runtime: input.runtime,
|
|
4047
|
+
membershipId: member.id,
|
|
4048
|
+
seatId: seat.id,
|
|
4049
|
+
kind: "invite_named_recipient",
|
|
4050
|
+
status: "pending",
|
|
4051
|
+
attempts: 0,
|
|
4052
|
+
createdAt: now,
|
|
4053
|
+
updatedAt: now
|
|
4054
|
+
})
|
|
4055
|
+
], { mutationId: `named-seat-included:${seat.id}`, guards: [
|
|
4056
|
+
{
|
|
4057
|
+
ns: "networkSeat",
|
|
4058
|
+
id: seat.id,
|
|
4059
|
+
exists: true,
|
|
4060
|
+
equals: { status: "payment_pending", revision: seat.revision, amountCents: 0 },
|
|
4061
|
+
absent: ["purchasedAt", "providerPaymentIntentId"]
|
|
4062
|
+
},
|
|
4063
|
+
{ ns: "networkEntitlement", id: entitlementId, exists: false },
|
|
4064
|
+
{ ns: "networkMembership", id: String(member.id), exists: true, equals: { revision: member.revision, namedSeatId: seat.id } },
|
|
4065
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
4066
|
+
] });
|
|
4067
|
+
} catch (error) {
|
|
4068
|
+
if (error.code !== "transact_guard_failed") throw error;
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
const current = await membershipLoad(db, "networkSeat", seat.id);
|
|
4072
|
+
requireMembershipScope(current, input);
|
|
4073
|
+
if (!namedSeatIncluded(current) || current.purchasedAt === void 0 || !OPEN.includes(String(current.status))) membershipFail("named_seat_purchase_unavailable");
|
|
4074
|
+
return { seatId: seat.id, amountCents: 0, currency: seat.currency, termEndsAt: Number(current.termEndsAt), status: String(current.status), included: true };
|
|
4075
|
+
}
|
|
4076
|
+
|
|
3897
4077
|
// src/membership-seat-refund.ts
|
|
3898
4078
|
async function requestNamedSeatRefund(db, input, now) {
|
|
3899
4079
|
membershipClock(now);
|
|
@@ -3903,7 +4083,7 @@ async function requestNamedSeatRefund(db, input, now) {
|
|
|
3903
4083
|
const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
|
|
3904
4084
|
requireMembershipScope(member, input);
|
|
3905
4085
|
if (seat.status === "refunded" || seat.status === "refund_pending") return;
|
|
3906
|
-
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");
|
|
4086
|
+
if (!seat.purchasedAt || !seat.providerPaymentIntentId && !namedSeatIncluded(seat) || input.reason === "declined" && seat.journeyApprovedAt !== void 0 || input.reason === "primary_closed" && !["refunded", "declined"].includes(String(member.status))) membershipFail("named_seat_refund_not_eligible");
|
|
3907
4087
|
const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
|
|
3908
4088
|
await db.transact([
|
|
3909
4089
|
membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
|
|
@@ -3948,7 +4128,7 @@ async function completeNamedSeatRefund(db, seatId, secretKey, now) {
|
|
|
3948
4128
|
await loadMembershipAuthorityState(db, seat);
|
|
3949
4129
|
if (seat.status === "refunded") return;
|
|
3950
4130
|
if (seat.status !== "refund_pending") membershipFail("named_seat_refund_not_ready");
|
|
3951
|
-
const result = await refundNamedSeatStripePayment(secretKey, seat);
|
|
4131
|
+
const result = namedSeatIncluded(seat) ? { refundedCents: 0, refundIds: [] } : await refundNamedSeatStripePayment(secretKey, seat);
|
|
3952
4132
|
await db.transact([
|
|
3953
4133
|
membershipUpdate("networkSeat", seatId, {
|
|
3954
4134
|
status: "refunded",
|
|
@@ -3970,7 +4150,7 @@ async function refundNamedSeatByOperator(db, input, stripeSecretKey, now) {
|
|
|
3970
4150
|
await requestNamedSeatRefund(db, { ...input, reason: "operator" }, now);
|
|
3971
4151
|
await completeNamedSeatRefund(db, input.seatId, stripeSecretKey, now);
|
|
3972
4152
|
const refunded = await membershipLoad(db, "networkSeat", input.seatId);
|
|
3973
|
-
if (refunded?.status !== "refunded" || !Number.isSafeInteger(refunded.refundedCents) || Number(refunded.refundedCents)
|
|
4153
|
+
if (refunded?.status !== "refunded" || !Number.isSafeInteger(refunded.refundedCents) || Number(refunded.refundedCents) !== Number(seat.amountCents)) {
|
|
3974
4154
|
throw new Error("named seat refund unconfirmed");
|
|
3975
4155
|
}
|
|
3976
4156
|
return { seatId: String(seat.id), status: "refunded", refundedCents: Number(refunded.refundedCents), subscriptionCanceled: false };
|
|
@@ -4002,9 +4182,56 @@ async function liftSeatPurchaseWindow(db, scope2, now, limit = 100) {
|
|
|
4002
4182
|
return { lifted, more: rows5.length > limit };
|
|
4003
4183
|
}
|
|
4004
4184
|
|
|
4185
|
+
// src/membership-seat-reprice.ts
|
|
4186
|
+
async function lowerSeatPrice(db, scope2, now, limit = 100) {
|
|
4187
|
+
membershipClock(now);
|
|
4188
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 200) throw new TypeError("invalid reprice limit");
|
|
4189
|
+
const state = await loadMembershipAuthorityState(db, scope2);
|
|
4190
|
+
const versions = (await db.query({ membershipOfferingVersion: { $: { where: { chapterId: scope2.chapterId }, limit: 200 } } })).membershipOfferingVersion ?? [];
|
|
4191
|
+
if (versions.length >= 200) throw Object.assign(new Error("policy_history_incomplete"), { code: "policy_history_incomplete" });
|
|
4192
|
+
const current = versions.map((row2) => membershipOfferingVersion(row2.snapshot)).filter((offer) => offer.baseSkuId === scope2.skuId && offer.status === "published" && now >= offer.effectiveFrom && (offer.effectiveUntil === void 0 || now < offer.effectiveUntil)).sort((a, b) => b.version - a.version)[0];
|
|
4193
|
+
if (!current) throw Object.assign(new Error("ambiguous_membership_policy"), { code: "ambiguous_membership_policy" });
|
|
4194
|
+
const target = current.seatPriceCents;
|
|
4195
|
+
const rows5 = (await db.query({ networkMembership: { $: { where: { ...scope2 }, order: { updatedAt: "asc" }, limit: limit + 1 } } })).networkMembership ?? [];
|
|
4196
|
+
const receipt = { seatPriceCents: target, repriced: 0, skipped: [], more: rows5.length > limit };
|
|
4197
|
+
for (const member of rows5.slice(0, limit)) {
|
|
4198
|
+
if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) throw new Error("membership reprice scope mismatch");
|
|
4199
|
+
const id6 = String(member.id), skip = (reason) => {
|
|
4200
|
+
receipt.skipped.push({ membershipId: id6, reason });
|
|
4201
|
+
};
|
|
4202
|
+
const offering = membershipOfferingVersion(member.offeringSnapshot);
|
|
4203
|
+
if (offering.seatPriceCents <= target) {
|
|
4204
|
+
skip("seat price already at or below the current offering");
|
|
4205
|
+
continue;
|
|
4206
|
+
}
|
|
4207
|
+
if (!["paid_pending_approval", "active", "cancel_at_term_end", "payment_grace"].includes(String(member.status))) {
|
|
4208
|
+
skip(`membership is ${String(member.status)}`);
|
|
4209
|
+
continue;
|
|
4210
|
+
}
|
|
4211
|
+
const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
|
|
4212
|
+
if (seat && (seat.status !== "payment_pending" || seat.providerPaymentIntentId !== void 0)) {
|
|
4213
|
+
skip(`seat is ${String(seat.status)}`);
|
|
4214
|
+
continue;
|
|
4215
|
+
}
|
|
4216
|
+
const ops = [membershipUpdate("networkMembership", id6, { offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(member.revision) + 1, updatedAt: now })];
|
|
4217
|
+
const guards = [
|
|
4218
|
+
{ ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
|
|
4219
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative" } }
|
|
4220
|
+
];
|
|
4221
|
+
if (seat) {
|
|
4222
|
+
ops.push(membershipUpdate("networkSeat", String(seat.id), { amountCents: target, offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(seat.revision) + 1, updatedAt: now }));
|
|
4223
|
+
guards.push({ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision, status: "payment_pending" } });
|
|
4224
|
+
}
|
|
4225
|
+
await db.transact(ops, { mutationId: `seat-reprice:${id6}:${member.revision}`, guards });
|
|
4226
|
+
receipt.repriced++;
|
|
4227
|
+
}
|
|
4228
|
+
return receipt;
|
|
4229
|
+
}
|
|
4230
|
+
|
|
4005
4231
|
// src/worker-routes-admin-membership-seats.ts
|
|
4006
4232
|
var LIST = "/api/admin/membership-seats";
|
|
4007
4233
|
var LIFT = "/api/admin/membership-seats/lift-purchase-window";
|
|
4234
|
+
var REPRICE = "/api/admin/membership-seats/reprice";
|
|
4008
4235
|
var ONE = /^\/api\/admin\/membership-seats\/([A-Za-z0-9][A-Za-z0-9_.:-]{2,159})\/refund$/;
|
|
4009
4236
|
var SCOPE = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
4010
4237
|
var PUBLIC = [
|
|
@@ -4031,11 +4258,11 @@ var PUBLIC = [
|
|
|
4031
4258
|
];
|
|
4032
4259
|
var project = (row2) => Object.fromEntries(PUBLIC.filter((key3) => row2[key3] !== void 0).map((key3) => [key3, row2[key3]]));
|
|
4033
4260
|
var handleAdminMembershipSeats = async (req, url, env, ctx) => {
|
|
4034
|
-
const one = url.pathname.match(ONE), lift = url.pathname === LIFT;
|
|
4035
|
-
if (url.pathname !== LIST && !one && !lift) return null;
|
|
4261
|
+
const one = url.pathname.match(ONE), lift = url.pathname === LIFT, reprice = url.pathname === REPRICE;
|
|
4262
|
+
if (url.pathname !== LIST && !one && !lift && !reprice) return null;
|
|
4036
4263
|
const operator = ctx.membershipAuthorityOperator;
|
|
4037
4264
|
if (!operator) return json({ error: "not found" }, 404);
|
|
4038
|
-
if (req.method !== (one || lift ? "POST" : "GET")) return json({ error: "method not allowed" }, 405);
|
|
4265
|
+
if (req.method !== (one || lift || reprice ? "POST" : "GET")) return json({ error: "method not allowed" }, 405);
|
|
4039
4266
|
const raw2 = ctx.makeDb(env), user = await ctx.verifyUser(req, env);
|
|
4040
4267
|
if (!user) return json({ error: "unauthorized" }, 401);
|
|
4041
4268
|
if (!await ctx.isSuperAdminEmail(raw2, user.email, user.userId)) return json({ error: "super-admin required", code: "super_admin_required" }, 403);
|
|
@@ -4045,7 +4272,7 @@ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
|
|
|
4045
4272
|
} catch {
|
|
4046
4273
|
return json({ error: "the membership authority is not hosted by this deployment" }, 404);
|
|
4047
4274
|
}
|
|
4048
|
-
if (lift) {
|
|
4275
|
+
if (lift || reprice) {
|
|
4049
4276
|
let body;
|
|
4050
4277
|
try {
|
|
4051
4278
|
body = await req.json();
|
|
@@ -4053,14 +4280,16 @@ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
|
|
|
4053
4280
|
return json({ error: "chapterId and runtime are required" }, 400);
|
|
4054
4281
|
}
|
|
4055
4282
|
const appId = body.appId ?? body.chapterId;
|
|
4056
|
-
if (typeof body.chapterId !== "string" || !SCOPE.test(body.chapterId) || typeof appId !== "string" || !SCOPE.test(appId) || typeof body.runtime !== "string" || !/^[a-z0-9]{2,12}$/.test(body.runtime)) {
|
|
4057
|
-
return json({ error: "chapterId and runtime are required" }, 400);
|
|
4283
|
+
if (typeof body.chapterId !== "string" || !SCOPE.test(body.chapterId) || typeof appId !== "string" || !SCOPE.test(appId) || typeof body.runtime !== "string" || !/^[a-z0-9]{2,12}$/.test(body.runtime) || reprice && (typeof body.skuId !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.:-]{2,159}$/.test(body.skuId))) {
|
|
4284
|
+
return json({ error: reprice ? "chapterId, runtime and skuId are required" : "chapterId and runtime are required" }, 400);
|
|
4058
4285
|
}
|
|
4286
|
+
const scope2 = { appId, chapterId: body.chapterId, environment: env.ODLA_ENV, runtime: body.runtime };
|
|
4059
4287
|
try {
|
|
4060
|
-
return json({ ok: true, ...await
|
|
4288
|
+
if (reprice) return json({ ok: true, ...await lowerSeatPrice(db, { ...scope2, skuId: String(body.skuId) }, Date.now()) });
|
|
4289
|
+
return json({ ok: true, ...await liftSeatPurchaseWindow(db, scope2, Date.now()) });
|
|
4061
4290
|
} catch (error) {
|
|
4062
4291
|
const code = error.code;
|
|
4063
|
-
return json({ error: "purchase window lift is pending; retry to confirm
|
|
4292
|
+
return json({ error: `${reprice ? "seat reprice" : "purchase window lift"} is pending; retry to confirm`, ...code ? { code } : {} }, code ? 409 : 503);
|
|
4064
4293
|
}
|
|
4065
4294
|
}
|
|
4066
4295
|
if (!one) {
|
|
@@ -4096,8 +4325,8 @@ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
|
|
|
4096
4325
|
var LIMIT = 200;
|
|
4097
4326
|
var text2 = (value) => typeof value === "string" && value.length > 0 && value.length <= 200 ? value : void 0;
|
|
4098
4327
|
var instant = (value) => {
|
|
4099
|
-
const
|
|
4100
|
-
return Number.isSafeInteger(
|
|
4328
|
+
const at2 = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
|
|
4329
|
+
return Number.isSafeInteger(at2) && at2 >= 0 ? at2 : void 0;
|
|
4101
4330
|
};
|
|
4102
4331
|
function provenance(row2) {
|
|
4103
4332
|
const source = text2(row2.source), actor = text2(row2.actor ?? row2.$createdBy), recordedAt = instant(row2.recordedAt ?? row2.createdAt ?? row2.$createdAt);
|
|
@@ -6470,7 +6699,7 @@ var handleMe = async (req, url, env, ctx) => {
|
|
|
6470
6699
|
} catch {
|
|
6471
6700
|
}
|
|
6472
6701
|
}
|
|
6473
|
-
return json({ ...base, memberAccess, membershipRestart, application: member?.application ?? null, namedSeatsEnabled: Boolean(
|
|
6702
|
+
return json({ ...base, memberAccess, membershipRestart, application: member?.application ?? null, namedSeatsEnabled: Boolean(ctx.membershipAuthority?.namedSeats && member?.application.paid && member.source.status !== "submitted") });
|
|
6474
6703
|
};
|
|
6475
6704
|
var handleCrm = async (req, url, env, ctx) => {
|
|
6476
6705
|
const crmBase = ctx.crmBase;
|
|
@@ -9468,6 +9697,7 @@ async function namedSeatOfferDigest(offer) {
|
|
|
9468
9697
|
])));
|
|
9469
9698
|
return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
9470
9699
|
}
|
|
9700
|
+
var at = (value) => Number.isSafeInteger(value) ? Number(value) : void 0;
|
|
9471
9701
|
async function readNamedSeatOffer(db, input, now) {
|
|
9472
9702
|
membershipClock(now);
|
|
9473
9703
|
await loadMembershipAuthorityState(db, input);
|
|
@@ -9490,10 +9720,19 @@ async function readNamedSeatOffer(db, input, now) {
|
|
|
9490
9720
|
seatId: String(seat.id),
|
|
9491
9721
|
status: String(seat.status),
|
|
9492
9722
|
recipientName: String(seat.recipientName),
|
|
9493
|
-
recipientEmail: String(seat.recipientEmailNormalized)
|
|
9723
|
+
recipientEmail: String(seat.recipientEmailNormalized),
|
|
9724
|
+
recipientState: namedSeatRecipientState(seat),
|
|
9725
|
+
...at(seat.purchasedAt) !== void 0 ? { purchasedAt: at(seat.purchasedAt) } : {},
|
|
9726
|
+
...at(seat.acceptedAt) !== void 0 ? { acceptedAt: at(seat.acceptedAt) } : {},
|
|
9727
|
+
...seat.status === "active" && at(seat.journeyApprovedAt) !== void 0 ? { joinedAt: at(seat.journeyApprovedAt) } : {}
|
|
9494
9728
|
} } : {}
|
|
9495
9729
|
};
|
|
9496
|
-
return {
|
|
9730
|
+
return {
|
|
9731
|
+
...offer,
|
|
9732
|
+
digest: await namedSeatOfferDigest(offer),
|
|
9733
|
+
included: offering.seatPriceCents === 0,
|
|
9734
|
+
purchaseWindowed: offering.seatPurchaseWindowCalendarMonths !== void 0
|
|
9735
|
+
};
|
|
9497
9736
|
}
|
|
9498
9737
|
|
|
9499
9738
|
// src/named-seat-request.ts
|
|
@@ -9569,8 +9808,9 @@ var handleNamedSeat = async (req, url, env, ctx) => {
|
|
|
9569
9808
|
if (recipientName.length < 2 || /[\u0000-\u001f\u007f]/.test(body.recipientName) || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(recipientEmail) || recipientEmail === normalizeChapterEmail(user.email)) {
|
|
9570
9809
|
return json({ error: "a valid name and email for someone other than the purchaser are required" }, 400);
|
|
9571
9810
|
}
|
|
9572
|
-
|
|
9573
|
-
if (!
|
|
9811
|
+
const granted = offer.included && offer.seat !== void 0 && offer.seat.recipientEmail === recipientEmail && offer.seat.recipientName === recipientName && ["pending_acceptance", "pending_approval", "pending_primary", "active"].includes(offer.seat.status);
|
|
9812
|
+
if (!offer.eligible && !granted || body.quoteDigest !== offer.digest) return json({ error: "seat offer changed or is unavailable; refresh before paying" }, 409);
|
|
9813
|
+
if (!group?.refundPolicyText || !group.merchantDisclosureText || !offer.included && !group.stripePublishableKey) return json({ error: "seat checkout disclosures unavailable" }, 503);
|
|
9574
9814
|
const consentId = `${offer.membershipId}:named-seat-consent`;
|
|
9575
9815
|
const prior = await membershipLoad(db, "namedSeatConsents", consentId);
|
|
9576
9816
|
const attrs = {
|
|
@@ -9609,7 +9849,7 @@ var handleNamedSeat = async (req, url, env, ctx) => {
|
|
|
9609
9849
|
}
|
|
9610
9850
|
return json({
|
|
9611
9851
|
...receipt,
|
|
9612
|
-
publishableKey: String(group.stripePublishableKey),
|
|
9852
|
+
...group.stripePublishableKey ? { publishableKey: String(group.stripePublishableKey) } : {},
|
|
9613
9853
|
lineItems: { standardCents: offer.amountCents, discountCents: 0, dueTodayCents: offer.amountCents }
|
|
9614
9854
|
});
|
|
9615
9855
|
} catch {
|
|
@@ -11406,7 +11646,7 @@ async function networkApplicationAction(opts) {
|
|
|
11406
11646
|
if (app.status !== input.expectedStage || !applicationActionStages(app, env, ctx).includes(input.stage)) {
|
|
11407
11647
|
return json({ error: "application action is no longer available; reload", code: "invalid_application_action" }, 409);
|
|
11408
11648
|
}
|
|
11409
|
-
const
|
|
11649
|
+
const at2 = Date.now();
|
|
11410
11650
|
const meta = { [ACTION_META]: {
|
|
11411
11651
|
version: 1,
|
|
11412
11652
|
state: "pending",
|
|
@@ -11426,7 +11666,7 @@ async function networkApplicationAction(opts) {
|
|
|
11426
11666
|
recordId: record4.id,
|
|
11427
11667
|
kind: "note",
|
|
11428
11668
|
authorId: `network:${sourceId}`,
|
|
11429
|
-
createdAt:
|
|
11669
|
+
createdAt: at2,
|
|
11430
11670
|
body: `Requested ${input.expectedStage} \u2192 ${input.stage} from ${sourceId}`,
|
|
11431
11671
|
meta
|
|
11432
11672
|
} },
|
|
@@ -11435,7 +11675,7 @@ async function networkApplicationAction(opts) {
|
|
|
11435
11675
|
recordId: record4.id,
|
|
11436
11676
|
kind: "note",
|
|
11437
11677
|
authorId: `network:${sourceId}`,
|
|
11438
|
-
createdAt:
|
|
11678
|
+
createdAt: at2,
|
|
11439
11679
|
body: "Application operation reconciliation",
|
|
11440
11680
|
meta: { state: "pending", activityId }
|
|
11441
11681
|
} }
|
|
@@ -12282,9 +12522,9 @@ function normalizeNetworkIdentity(kind, value) {
|
|
|
12282
12522
|
return trimmed;
|
|
12283
12523
|
}
|
|
12284
12524
|
function emailLooksReal(value) {
|
|
12285
|
-
const
|
|
12286
|
-
if (
|
|
12287
|
-
const domain = value.slice(
|
|
12525
|
+
const at2 = value.lastIndexOf("@");
|
|
12526
|
+
if (at2 <= 0 || at2 === value.length - 1) return false;
|
|
12527
|
+
const domain = value.slice(at2 + 1).toLowerCase();
|
|
12288
12528
|
return !domain.endsWith(".invalid") && domain !== "example.com" && domain !== "example.test";
|
|
12289
12529
|
}
|
|
12290
12530
|
function validateNetworkPersonImport(input, allowedChapterIds) {
|
|
@@ -14603,7 +14843,9 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
14603
14843
|
if (start !== member.termEndsAt || start > now + 3e4) membershipFail("membership_renewal_period_gap");
|
|
14604
14844
|
const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
|
|
14605
14845
|
if (seat) requireMembershipScope(seat, input);
|
|
14606
|
-
const
|
|
14846
|
+
const openSeat = seat && !["refunded", "refund_pending", "canceled"].includes(String(seat.status)) ? seat : void 0;
|
|
14847
|
+
const billedSeat = openSeat && openSeat.providerSubscriptionId === member.providerSubscriptionId && openSeat.providerSubscriptionItemId ? openSeat : void 0;
|
|
14848
|
+
const carriedSeat = billedSeat ?? (openSeat && namedSeatIncluded(openSeat) && openSeat.purchasedAt !== void 0 ? openSeat : void 0);
|
|
14607
14849
|
const prices = [String(member.providerPriceId), ...billedSeat ? [String(billedSeat.priceId)] : []].sort();
|
|
14608
14850
|
const amount = Number(member.renewalAmountCents) + Number(billedSeat?.amountCents ?? 0);
|
|
14609
14851
|
if (!Number.isSafeInteger(amount) || amount <= 0 || fact.invoiceTotalCents !== amount || JSON.stringify(lines.map((line) => line.priceId).sort()) !== JSON.stringify(prices)) membershipFail("membership_renewal_amount_mismatch");
|
|
@@ -14643,19 +14885,19 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
14643
14885
|
evidenceGuard
|
|
14644
14886
|
];
|
|
14645
14887
|
if (seat) guards.push({ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision } });
|
|
14646
|
-
if (
|
|
14647
|
-
const seatEntitlement = await membershipLoad(db, "networkEntitlement", String(
|
|
14888
|
+
if (carriedSeat?.entitlementId) {
|
|
14889
|
+
const seatEntitlement = await membershipLoad(db, "networkEntitlement", String(carriedSeat.entitlementId));
|
|
14648
14890
|
requireMembershipScope(seatEntitlement, input);
|
|
14649
14891
|
guards.push({ ns: "networkEntitlement", id: String(seatEntitlement.id), exists: true, equals: { revision: seatEntitlement.revision } });
|
|
14650
14892
|
ops.push(
|
|
14651
|
-
membershipUpdate("networkSeat", String(
|
|
14893
|
+
membershipUpdate("networkSeat", String(carriedSeat.id), {
|
|
14652
14894
|
...paid ? { termStartedAt: start, termEndsAt: end } : {},
|
|
14653
|
-
revision: Number(
|
|
14895
|
+
revision: Number(carriedSeat.revision) + 1,
|
|
14654
14896
|
updatedAt: now
|
|
14655
14897
|
}),
|
|
14656
14898
|
membershipUpdate("networkEntitlement", String(seatEntitlement.id), {
|
|
14657
14899
|
...paid ? { termStartedAt: start, termEndsAt: end } : {},
|
|
14658
|
-
status:
|
|
14900
|
+
status: carriedSeat.journeyApprovedAt ? entitlementStatus : seatEntitlement.status,
|
|
14659
14901
|
revision: Number(seatEntitlement.revision) + 1,
|
|
14660
14902
|
updatedAt: now
|
|
14661
14903
|
})
|
|
@@ -14765,84 +15007,6 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
|
|
|
14765
15007
|
return membershipFail("membership_billing_retry_required");
|
|
14766
15008
|
}
|
|
14767
15009
|
|
|
14768
|
-
// src/membership-seat-reservation.ts
|
|
14769
|
-
async function recipientKey(input, email) {
|
|
14770
|
-
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([input.appId, input.chapterId, input.environment, input.runtime, email])));
|
|
14771
|
-
return `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
14772
|
-
}
|
|
14773
|
-
async function reserveNamedMembershipSeat(db, input, now) {
|
|
14774
|
-
membershipClock(now);
|
|
14775
|
-
const name = typeof input.recipientName === "string" ? input.recipientName.trim().replace(/\s+/g, " ") : "";
|
|
14776
|
-
const email = normalizeChapterEmail(typeof input.recipientEmail === "string" ? input.recipientEmail : "");
|
|
14777
|
-
const purchaser = normalizeChapterEmail(typeof input.purchaserEmail === "string" ? input.purchaserEmail : "");
|
|
14778
|
-
if (name.length < 2 || name.length > 160 || /[\u0000-\u001f\u007f]/.test(input.recipientName) || email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) || !purchaser || email === purchaser || !/^price_[A-Za-z0-9_]+$/.test(input.priceId) || !/^user_[A-Za-z0-9_]+$/.test(input.purchaserUserId)) membershipFail("invalid_named_seat_recipient");
|
|
14779
|
-
const state = await loadMembershipAuthorityState(db, input);
|
|
14780
|
-
const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
|
|
14781
|
-
requireMembershipScope(member, input);
|
|
14782
|
-
if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
|
|
14783
|
-
const id6 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
14784
|
-
let seat = await membershipLoad(db, "networkSeat", id6);
|
|
14785
|
-
if (!seat) {
|
|
14786
|
-
const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
|
|
14787
|
-
const purchaseEndsAt = seatPurchaseDeadline(offering, member);
|
|
14788
|
-
if (!["paid_pending_approval", "active", "cancel_at_term_end"].includes(String(member.status)) || Number(member.refundedCents) !== 0 || now < Number(member.initialTermStartedAt) || now >= Number(member.termEndsAt) || now > purchaseEndsAt || !Number.isSafeInteger(purchaseEndsAt) || member.namedSeatId !== void 0) {
|
|
14789
|
-
membershipFail("named_seat_purchase_unavailable");
|
|
14790
|
-
}
|
|
14791
|
-
const attrs = {
|
|
14792
|
-
id: id6,
|
|
14793
|
-
...membershipScope(input),
|
|
14794
|
-
primaryMembershipId: memberId,
|
|
14795
|
-
recipientName: name,
|
|
14796
|
-
recipientEmailNormalized: email,
|
|
14797
|
-
purchaserUserId: input.purchaserUserId,
|
|
14798
|
-
recipientClaimId: claimId,
|
|
14799
|
-
priceId: input.priceId,
|
|
14800
|
-
primaryPriceId: member.providerPriceId,
|
|
14801
|
-
amountCents: offering.seatPriceCents,
|
|
14802
|
-
currency: offering.currency,
|
|
14803
|
-
interval: offering.interval,
|
|
14804
|
-
providerCustomerId: member.providerCustomerId,
|
|
14805
|
-
providerSubscriptionId: member.providerSubscriptionId,
|
|
14806
|
-
status: "payment_pending",
|
|
14807
|
-
preparedAt: now,
|
|
14808
|
-
termEndsAt: member.termEndsAt,
|
|
14809
|
-
seatPurchaseEndsAt: purchaseEndsAt,
|
|
14810
|
-
offeringSnapshot: offering,
|
|
14811
|
-
revision: 1,
|
|
14812
|
-
createdAt: now,
|
|
14813
|
-
updatedAt: now
|
|
14814
|
-
};
|
|
14815
|
-
try {
|
|
14816
|
-
await db.transact([
|
|
14817
|
-
membershipUpdate("networkSeat", id6, attrs),
|
|
14818
|
-
membershipUpdate("networkSeatRecipient", claimId, {
|
|
14819
|
-
id: claimId,
|
|
14820
|
-
appId: input.appId,
|
|
14821
|
-
chapterId: input.chapterId,
|
|
14822
|
-
environment: input.environment,
|
|
14823
|
-
runtime: input.runtime,
|
|
14824
|
-
seatId: id6,
|
|
14825
|
-
recipientEmailNormalized: email,
|
|
14826
|
-
createdAt: now
|
|
14827
|
-
}),
|
|
14828
|
-
membershipUpdate("networkMembership", memberId, { namedSeatId: id6, revision: Number(member.revision) + 1, updatedAt: now })
|
|
14829
|
-
], { mutationId: `named-seat-reserve:${id6}`, guards: [
|
|
14830
|
-
{ ns: "networkSeat", id: id6, exists: false },
|
|
14831
|
-
{ ns: "networkSeatRecipient", id: claimId, exists: false },
|
|
14832
|
-
{ ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
|
|
14833
|
-
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
14834
|
-
] });
|
|
14835
|
-
} catch (error) {
|
|
14836
|
-
if (error.code !== "transact_guard_failed") throw error;
|
|
14837
|
-
}
|
|
14838
|
-
seat = await membershipLoad(db, "networkSeat", id6);
|
|
14839
|
-
if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
|
|
14840
|
-
}
|
|
14841
|
-
requireMembershipScope(seat, input);
|
|
14842
|
-
if (seat.primaryMembershipId !== memberId || seat.recipientName !== name || seat.recipientEmailNormalized !== email || seat.priceId !== input.priceId || seat.purchaserUserId !== input.purchaserUserId) membershipFail("named_seat_purchase_conflict");
|
|
14843
|
-
return seat;
|
|
14844
|
-
}
|
|
14845
|
-
|
|
14846
15010
|
// src/membership-seat-stripe.ts
|
|
14847
15011
|
var import_stripe15 = require("@odla-ai/stripe");
|
|
14848
15012
|
var reject7 = () => {
|
|
@@ -14930,6 +15094,7 @@ async function attachNamedSeatStripeRenewal(sk, seat) {
|
|
|
14930
15094
|
// src/membership-seat-checkout.ts
|
|
14931
15095
|
async function prepareNamedMembershipSeatPayment(db, input, secretKey, now) {
|
|
14932
15096
|
let seat = await reserveNamedMembershipSeat(db, input, now);
|
|
15097
|
+
if (namedSeatIncluded(seat)) membershipFail("named_seat_included");
|
|
14933
15098
|
const state = await loadMembershipAuthorityState(db, input);
|
|
14934
15099
|
const member = await membershipLoad(db, "networkMembership", seat.primaryMembershipId);
|
|
14935
15100
|
requireMembershipScope(member, input);
|
|
@@ -15136,7 +15301,7 @@ async function recordRecipient(db, input, now, approve) {
|
|
|
15136
15301
|
requireMembershipScope(member, input);
|
|
15137
15302
|
const entitlement = await membershipLoad(db, "networkEntitlement", String(seat.entitlementId));
|
|
15138
15303
|
requireMembershipScope(entitlement, input);
|
|
15139
|
-
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
|
|
15304
|
+
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) || !namedSeatSettled(seat)) membershipFail("named_seat_acceptance_unavailable");
|
|
15140
15305
|
if (seat.acceptedAt === void 0) {
|
|
15141
15306
|
if (approve || normalizeChapterEmail(input.verifiedRecipientEmail) !== seat.recipientEmailNormalized) {
|
|
15142
15307
|
membershipFail("named_seat_recipient_verification_required");
|
|
@@ -15323,6 +15488,15 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
|
|
|
15323
15488
|
};
|
|
15324
15489
|
if (action === "seat-accept") return acceptNamedSeatRecipient(db, recipientInput, now);
|
|
15325
15490
|
if (action === "seat-approve") return approveNamedSeatRecipient(db, recipientInput, now);
|
|
15491
|
+
const checkout = {
|
|
15492
|
+
...target,
|
|
15493
|
+
priceId: peer.seatPriceId,
|
|
15494
|
+
recipientName: String(input.recipientName),
|
|
15495
|
+
recipientEmail: String(input.recipientEmail),
|
|
15496
|
+
purchaserEmail: String(input.purchaserEmail),
|
|
15497
|
+
purchaserUserId: String(input.purchaserUserId)
|
|
15498
|
+
};
|
|
15499
|
+
if (action === "seat-checkouts" && namedSeatIncluded(await reserveNamedMembershipSeat(db, checkout, now))) return grantIncludedNamedSeat(db, checkout, now);
|
|
15326
15500
|
const secret = await getVaultSecret(db, stripeSecretName);
|
|
15327
15501
|
if (!secret) throw new Error("network Stripe provider is unavailable");
|
|
15328
15502
|
if (action === "seat-decline") {
|
|
@@ -15338,14 +15512,7 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
|
|
|
15338
15512
|
return { seatId: seat.id, status: "refunded", refundedCents: refunded.refundedCents, subscriptionCanceled: false };
|
|
15339
15513
|
}
|
|
15340
15514
|
if (action === "seat-operator-refund") return refundNamedSeatByOperator(db, { ...target, seatId: String(input.seatId) }, secret, now);
|
|
15341
|
-
if (action === "seat-checkouts") return prepareNamedMembershipSeatPayment(db,
|
|
15342
|
-
...target,
|
|
15343
|
-
priceId: peer.seatPriceId,
|
|
15344
|
-
recipientName: String(input.recipientName),
|
|
15345
|
-
recipientEmail: String(input.recipientEmail),
|
|
15346
|
-
purchaserEmail: String(input.purchaserEmail),
|
|
15347
|
-
purchaserUserId: String(input.purchaserUserId)
|
|
15348
|
-
}, secret, now);
|
|
15515
|
+
if (action === "seat-checkouts") return prepareNamedMembershipSeatPayment(db, checkout, secret, now);
|
|
15349
15516
|
const evidence = { ...target, seatId: String(input.seatId), eventId: String(input.eventId) };
|
|
15350
15517
|
if (action === "seat-payments") {
|
|
15351
15518
|
const receipt = await confirmNamedMembershipSeatPayment(db, evidence, now);
|
|
@@ -16201,13 +16368,13 @@ async function requestNamedSeatDecline(options) {
|
|
|
16201
16368
|
verifiedRecipientEmail: options.verifiedRecipientEmail
|
|
16202
16369
|
});
|
|
16203
16370
|
const seat = result.seat;
|
|
16204
|
-
if (!seat || seat.seatId !== options.seatId || seat.status !== "refunded" || seat.subscriptionCanceled !== false || !Number.isSafeInteger(seat.refundedCents) || seat.refundedCents
|
|
16371
|
+
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");
|
|
16205
16372
|
return seat;
|
|
16206
16373
|
}
|
|
16207
16374
|
async function requestNamedSeatOperatorRefund(options) {
|
|
16208
16375
|
const result = await requestAuthority(options, "seat-operator-refund", { applicationId: options.applicationId, skuId: options.skuId, seatId: options.seatId });
|
|
16209
16376
|
const seat = result.seat;
|
|
16210
|
-
if (!seat || seat.seatId !== options.seatId || seat.status !== "refunded" || seat.subscriptionCanceled !== false || !Number.isSafeInteger(seat.refundedCents) || seat.refundedCents
|
|
16377
|
+
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");
|
|
16211
16378
|
return seat;
|
|
16212
16379
|
}
|
|
16213
16380
|
async function requestNamedSeatReservation(options) {
|
|
@@ -16221,13 +16388,13 @@ async function requestNamedSeatReservation(options) {
|
|
|
16221
16388
|
});
|
|
16222
16389
|
const seat = result.seat;
|
|
16223
16390
|
const expected = (await membershipOfferIdForRequest({ ...options, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
16224
|
-
if (!seat || seat.seatId !== expected || !Number.isSafeInteger(seat.amountCents) || seat.amountCents
|
|
16391
|
+
if (!seat || seat.seatId !== expected || !Number.isSafeInteger(seat.amountCents) || seat.amountCents < 0 || !/^[a-z]{3}$/.test(seat.currency) || !Number.isSafeInteger(seat.termEndsAt)) throw new Error("invalid seat reservation receipt");
|
|
16225
16392
|
return { seatId: seat.seatId, amountCents: seat.amountCents, currency: seat.currency, termEndsAt: seat.termEndsAt };
|
|
16226
16393
|
}
|
|
16227
16394
|
async function requestNamedSeatOffer(options) {
|
|
16228
16395
|
const result = await requestAuthority(options, "seat-offer", { applicationId: options.applicationId, skuId: options.skuId });
|
|
16229
16396
|
const offer = result.seat;
|
|
16230
|
-
if (!offer || offer.membershipId !== await networkMembershipId(options) || !Number.isSafeInteger(offer.amountCents) || offer.amountCents
|
|
16397
|
+
if (!offer || offer.membershipId !== await networkMembershipId(options) || !Number.isSafeInteger(offer.amountCents) || offer.amountCents < 0 || offer.included !== (offer.amountCents === 0) || !/^[a-z]{3}$/.test(offer.currency) || !["month", "year"].includes(offer.interval) || typeof offer.eligible !== "boolean" || typeof offer.autoRenew !== "boolean" || !Number.isSafeInteger(offer.termEndsAt) || !Number.isSafeInteger(offer.purchaseEndsAt) || offer.purchaseWindowed !== void 0 && typeof offer.purchaseWindowed !== "boolean" || offer.digest !== await namedSeatOfferDigest(offer)) throw new Error("invalid named seat offer");
|
|
16231
16398
|
return offer;
|
|
16232
16399
|
}
|
|
16233
16400
|
async function requestNamedSeatInvitation(options) {
|
|
@@ -16263,7 +16430,8 @@ async function requestNamedSeatCheckout(options) {
|
|
|
16263
16430
|
});
|
|
16264
16431
|
const seat = result.seat;
|
|
16265
16432
|
const expected = (await membershipOfferIdForRequest({ ...options, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
16266
|
-
|
|
16433
|
+
const included = seat?.amountCents === 0;
|
|
16434
|
+
if (!seat || seat.seatId !== expected || !Number.isSafeInteger(seat.amountCents) || seat.amountCents < 0 || !/^[a-z]{3}$/.test(seat.currency) || !Number.isSafeInteger(seat.termEndsAt) || (included ? seat.included !== true || seat.paymentIntentId !== void 0 || seat.clientSecret !== void 0 || !["pending_acceptance", "pending_approval", "pending_primary", "active"].includes(seat.status) : seat.included !== void 0 || !/^pi_[A-Za-z0-9_]+$/.test(String(seat.paymentIntentId)) || typeof seat.clientSecret !== "string" || !["payment_processing", "renewal_pending", "pending_acceptance", "pending_approval", "active"].includes(seat.status))) {
|
|
16267
16435
|
throw new Error("invalid named seat checkout receipt");
|
|
16268
16436
|
}
|
|
16269
16437
|
return seat;
|