@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.js
CHANGED
|
@@ -1760,8 +1760,8 @@ var object = (value) => {
|
|
|
1760
1760
|
var id = (value) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9_.:-]{2,159}$/.test(value) ? value : fail2("id");
|
|
1761
1761
|
var integer2 = (value, min, max) => typeof value === "number" && Number.isSafeInteger(value) && value >= min && value <= max ? value : fail2("integer");
|
|
1762
1762
|
var time = (value) => {
|
|
1763
|
-
const
|
|
1764
|
-
return integer2(
|
|
1763
|
+
const at2 = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
|
|
1764
|
+
return integer2(at2, 0, 864e13);
|
|
1765
1765
|
};
|
|
1766
1766
|
var ids = (value, allowEmpty = false) => {
|
|
1767
1767
|
if (!Array.isArray(value) || value.length > 100 || !allowEmpty && value.length === 0) return fail2("ids");
|
|
@@ -1805,7 +1805,7 @@ function membershipOfferingVersion(value) {
|
|
|
1805
1805
|
term: normalizedTerm,
|
|
1806
1806
|
autoRenew: true,
|
|
1807
1807
|
paymentGraceDays: integer2(input.paymentGraceDays, 0, 60),
|
|
1808
|
-
seatPriceCents: integer2(input.seatPriceCents,
|
|
1808
|
+
seatPriceCents: integer2(input.seatPriceCents, 0, Number.MAX_SAFE_INTEGER),
|
|
1809
1809
|
...input.seatPurchaseWindowCalendarMonths === void 0 || input.seatPurchaseWindowCalendarMonths === null ? {} : { seatPurchaseWindowCalendarMonths: integer2(input.seatPurchaseWindowCalendarMonths, 1, 120) },
|
|
1810
1810
|
requiredDisclosures: ids(input.requiredDisclosures)
|
|
1811
1811
|
};
|
|
@@ -2893,7 +2893,7 @@ async function checkoutVariables(db, group, app) {
|
|
|
2893
2893
|
const frozen = await frozenCheckoutVariables(db, app);
|
|
2894
2894
|
return frozen.tierName ? frozen : { ...frozen, tierName: await catalogTierName(db, group, app) };
|
|
2895
2895
|
}
|
|
2896
|
-
function formatInstant(
|
|
2896
|
+
function formatInstant(at2, timeZone) {
|
|
2897
2897
|
return new Intl.DateTimeFormat("en-US", {
|
|
2898
2898
|
weekday: "long",
|
|
2899
2899
|
month: "long",
|
|
@@ -2903,7 +2903,7 @@ function formatInstant(at, timeZone) {
|
|
|
2903
2903
|
minute: "2-digit",
|
|
2904
2904
|
timeZoneName: "short",
|
|
2905
2905
|
timeZone
|
|
2906
|
-
}).format(new Date(
|
|
2906
|
+
}).format(new Date(at2));
|
|
2907
2907
|
}
|
|
2908
2908
|
var adminUrlFor = (origin) => `${origin}/admin/`;
|
|
2909
2909
|
var membersUrlFor = (origin) => `${origin}/members/`;
|
|
@@ -3851,6 +3851,181 @@ async function refundNamedSeatStripePayment(secretKey, seat) {
|
|
|
3851
3851
|
return { refundedCents, refundIds };
|
|
3852
3852
|
}
|
|
3853
3853
|
|
|
3854
|
+
// src/membership-seat-reservation.ts
|
|
3855
|
+
async function recipientKey(input, email) {
|
|
3856
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([input.appId, input.chapterId, input.environment, input.runtime, email])));
|
|
3857
|
+
return `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
3858
|
+
}
|
|
3859
|
+
async function reserveNamedMembershipSeat(db, input, now) {
|
|
3860
|
+
membershipClock(now);
|
|
3861
|
+
const name = typeof input.recipientName === "string" ? input.recipientName.trim().replace(/\s+/g, " ") : "";
|
|
3862
|
+
const email = normalizeChapterEmail(typeof input.recipientEmail === "string" ? input.recipientEmail : "");
|
|
3863
|
+
const purchaser = normalizeChapterEmail(typeof input.purchaserEmail === "string" ? input.purchaserEmail : "");
|
|
3864
|
+
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");
|
|
3865
|
+
const state = await loadMembershipAuthorityState(db, input);
|
|
3866
|
+
const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
|
|
3867
|
+
requireMembershipScope(member, input);
|
|
3868
|
+
if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
|
|
3869
|
+
const id6 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
3870
|
+
let seat = await membershipLoad(db, "networkSeat", id6);
|
|
3871
|
+
if (!seat) {
|
|
3872
|
+
const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
|
|
3873
|
+
const purchaseEndsAt = seatPurchaseDeadline(offering, member);
|
|
3874
|
+
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) {
|
|
3875
|
+
membershipFail("named_seat_purchase_unavailable");
|
|
3876
|
+
}
|
|
3877
|
+
const attrs = {
|
|
3878
|
+
id: id6,
|
|
3879
|
+
...membershipScope(input),
|
|
3880
|
+
primaryMembershipId: memberId,
|
|
3881
|
+
recipientName: name,
|
|
3882
|
+
recipientEmailNormalized: email,
|
|
3883
|
+
purchaserUserId: input.purchaserUserId,
|
|
3884
|
+
recipientClaimId: claimId,
|
|
3885
|
+
priceId: input.priceId,
|
|
3886
|
+
primaryPriceId: member.providerPriceId,
|
|
3887
|
+
amountCents: offering.seatPriceCents,
|
|
3888
|
+
currency: offering.currency,
|
|
3889
|
+
interval: offering.interval,
|
|
3890
|
+
providerCustomerId: member.providerCustomerId,
|
|
3891
|
+
providerSubscriptionId: member.providerSubscriptionId,
|
|
3892
|
+
status: "payment_pending",
|
|
3893
|
+
preparedAt: now,
|
|
3894
|
+
termEndsAt: member.termEndsAt,
|
|
3895
|
+
seatPurchaseEndsAt: purchaseEndsAt,
|
|
3896
|
+
offeringSnapshot: offering,
|
|
3897
|
+
revision: 1,
|
|
3898
|
+
createdAt: now,
|
|
3899
|
+
updatedAt: now
|
|
3900
|
+
};
|
|
3901
|
+
try {
|
|
3902
|
+
await db.transact([
|
|
3903
|
+
membershipUpdate("networkSeat", id6, attrs),
|
|
3904
|
+
membershipUpdate("networkSeatRecipient", claimId, {
|
|
3905
|
+
id: claimId,
|
|
3906
|
+
appId: input.appId,
|
|
3907
|
+
chapterId: input.chapterId,
|
|
3908
|
+
environment: input.environment,
|
|
3909
|
+
runtime: input.runtime,
|
|
3910
|
+
seatId: id6,
|
|
3911
|
+
recipientEmailNormalized: email,
|
|
3912
|
+
createdAt: now
|
|
3913
|
+
}),
|
|
3914
|
+
membershipUpdate("networkMembership", memberId, { namedSeatId: id6, revision: Number(member.revision) + 1, updatedAt: now })
|
|
3915
|
+
], { mutationId: `named-seat-reserve:${id6}`, guards: [
|
|
3916
|
+
{ ns: "networkSeat", id: id6, exists: false },
|
|
3917
|
+
{ ns: "networkSeatRecipient", id: claimId, exists: false },
|
|
3918
|
+
{ ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
|
|
3919
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
3920
|
+
] });
|
|
3921
|
+
} catch (error) {
|
|
3922
|
+
if (error.code !== "transact_guard_failed") throw error;
|
|
3923
|
+
}
|
|
3924
|
+
seat = await membershipLoad(db, "networkSeat", id6);
|
|
3925
|
+
if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
|
|
3926
|
+
}
|
|
3927
|
+
requireMembershipScope(seat, input);
|
|
3928
|
+
if (seat.primaryMembershipId !== memberId || seat.recipientName !== name || seat.recipientEmailNormalized !== email || seat.priceId !== input.priceId || seat.purchaserUserId !== input.purchaserUserId) membershipFail("named_seat_purchase_conflict");
|
|
3929
|
+
return seat;
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3932
|
+
// src/membership-seat-included.ts
|
|
3933
|
+
var namedSeatIncluded = (seat) => Number(seat.amountCents) === 0;
|
|
3934
|
+
var namedSeatSettled = (seat) => namedSeatIncluded(seat) ? seat.purchasedAt !== void 0 : Boolean(seat.providerPaymentIntentId && seat.providerSubscriptionItemId);
|
|
3935
|
+
function namedSeatRecipientState(seat) {
|
|
3936
|
+
switch (String(seat.status)) {
|
|
3937
|
+
case "renewal_pending":
|
|
3938
|
+
case "pending_acceptance":
|
|
3939
|
+
return "invited";
|
|
3940
|
+
case "pending_approval":
|
|
3941
|
+
case "pending_primary":
|
|
3942
|
+
return "accepted";
|
|
3943
|
+
case "active":
|
|
3944
|
+
return "joined";
|
|
3945
|
+
case "refund_pending":
|
|
3946
|
+
case "refunded":
|
|
3947
|
+
return seat.refundReason === "declined" ? "declined" : "refunded";
|
|
3948
|
+
case "canceled":
|
|
3949
|
+
return "canceled";
|
|
3950
|
+
default:
|
|
3951
|
+
return "payment_pending";
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
var OPEN = ["pending_acceptance", "pending_approval", "pending_primary", "active"];
|
|
3955
|
+
async function grantIncludedNamedSeat(db, input, now) {
|
|
3956
|
+
membershipClock(now);
|
|
3957
|
+
const seat = await reserveNamedMembershipSeat(db, input, now);
|
|
3958
|
+
if (!namedSeatIncluded(seat)) membershipFail("named_seat_not_included");
|
|
3959
|
+
const state = await loadMembershipAuthorityState(db, input);
|
|
3960
|
+
const member = await membershipLoad(db, "networkMembership", seat.primaryMembershipId);
|
|
3961
|
+
requireMembershipScope(member, input);
|
|
3962
|
+
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)) {
|
|
3963
|
+
membershipFail("named_seat_purchase_unavailable");
|
|
3964
|
+
}
|
|
3965
|
+
if (seat.status === "payment_pending") {
|
|
3966
|
+
const entitlementId = `${seat.id}:entitlement`, inviteId = `${seat.id}:invite`, source = `included:${seat.id}`;
|
|
3967
|
+
try {
|
|
3968
|
+
await db.transact([
|
|
3969
|
+
membershipUpdate("networkSeat", seat.id, {
|
|
3970
|
+
status: "pending_acceptance",
|
|
3971
|
+
purchasedAt: now,
|
|
3972
|
+
termStartedAt: now,
|
|
3973
|
+
termEndsAt: member.termEndsAt,
|
|
3974
|
+
acceptanceEndsAt: Number(member.termEndsAt),
|
|
3975
|
+
entitlementId,
|
|
3976
|
+
revision: Number(seat.revision) + 1,
|
|
3977
|
+
updatedAt: now
|
|
3978
|
+
}),
|
|
3979
|
+
membershipUpdate("networkEntitlement", entitlementId, {
|
|
3980
|
+
id: entitlementId,
|
|
3981
|
+
...membershipScope(input),
|
|
3982
|
+
membershipId: member.id,
|
|
3983
|
+
seatId: seat.id,
|
|
3984
|
+
kind: "named_seat",
|
|
3985
|
+
status: "pending_acceptance",
|
|
3986
|
+
termStartedAt: now,
|
|
3987
|
+
termEndsAt: member.termEndsAt,
|
|
3988
|
+
offeringSnapshot: member.offeringSnapshot,
|
|
3989
|
+
sourceEventId: source,
|
|
3990
|
+
revision: 1,
|
|
3991
|
+
createdAt: now,
|
|
3992
|
+
updatedAt: now
|
|
3993
|
+
}),
|
|
3994
|
+
membershipUpdate("membershipEffect", inviteId, {
|
|
3995
|
+
id: inviteId,
|
|
3996
|
+
chapterId: input.chapterId,
|
|
3997
|
+
environment: input.environment,
|
|
3998
|
+
runtime: input.runtime,
|
|
3999
|
+
membershipId: member.id,
|
|
4000
|
+
seatId: seat.id,
|
|
4001
|
+
kind: "invite_named_recipient",
|
|
4002
|
+
status: "pending",
|
|
4003
|
+
attempts: 0,
|
|
4004
|
+
createdAt: now,
|
|
4005
|
+
updatedAt: now
|
|
4006
|
+
})
|
|
4007
|
+
], { mutationId: `named-seat-included:${seat.id}`, guards: [
|
|
4008
|
+
{
|
|
4009
|
+
ns: "networkSeat",
|
|
4010
|
+
id: seat.id,
|
|
4011
|
+
exists: true,
|
|
4012
|
+
equals: { status: "payment_pending", revision: seat.revision, amountCents: 0 },
|
|
4013
|
+
absent: ["purchasedAt", "providerPaymentIntentId"]
|
|
4014
|
+
},
|
|
4015
|
+
{ ns: "networkEntitlement", id: entitlementId, exists: false },
|
|
4016
|
+
{ ns: "networkMembership", id: String(member.id), exists: true, equals: { revision: member.revision, namedSeatId: seat.id } },
|
|
4017
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
4018
|
+
] });
|
|
4019
|
+
} catch (error) {
|
|
4020
|
+
if (error.code !== "transact_guard_failed") throw error;
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
const current = await membershipLoad(db, "networkSeat", seat.id);
|
|
4024
|
+
requireMembershipScope(current, input);
|
|
4025
|
+
if (!namedSeatIncluded(current) || current.purchasedAt === void 0 || !OPEN.includes(String(current.status))) membershipFail("named_seat_purchase_unavailable");
|
|
4026
|
+
return { seatId: seat.id, amountCents: 0, currency: seat.currency, termEndsAt: Number(current.termEndsAt), status: String(current.status), included: true };
|
|
4027
|
+
}
|
|
4028
|
+
|
|
3854
4029
|
// src/membership-seat-refund.ts
|
|
3855
4030
|
async function requestNamedSeatRefund(db, input, now) {
|
|
3856
4031
|
membershipClock(now);
|
|
@@ -3860,7 +4035,7 @@ async function requestNamedSeatRefund(db, input, now) {
|
|
|
3860
4035
|
const member = await membershipLoad(db, "networkMembership", String(seat.primaryMembershipId));
|
|
3861
4036
|
requireMembershipScope(member, input);
|
|
3862
4037
|
if (seat.status === "refunded" || seat.status === "refund_pending") return;
|
|
3863
|
-
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");
|
|
4038
|
+
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");
|
|
3864
4039
|
const entitlement = seat.entitlementId ? await membershipLoad(db, "networkEntitlement", String(seat.entitlementId)) : void 0;
|
|
3865
4040
|
await db.transact([
|
|
3866
4041
|
membershipUpdate("networkSeat", input.seatId, { status: "refund_pending", refundReason: input.reason, revision: Number(seat.revision) + 1, updatedAt: now }),
|
|
@@ -3905,7 +4080,7 @@ async function completeNamedSeatRefund(db, seatId, secretKey, now) {
|
|
|
3905
4080
|
await loadMembershipAuthorityState(db, seat);
|
|
3906
4081
|
if (seat.status === "refunded") return;
|
|
3907
4082
|
if (seat.status !== "refund_pending") membershipFail("named_seat_refund_not_ready");
|
|
3908
|
-
const result = await refundNamedSeatStripePayment(secretKey, seat);
|
|
4083
|
+
const result = namedSeatIncluded(seat) ? { refundedCents: 0, refundIds: [] } : await refundNamedSeatStripePayment(secretKey, seat);
|
|
3909
4084
|
await db.transact([
|
|
3910
4085
|
membershipUpdate("networkSeat", seatId, {
|
|
3911
4086
|
status: "refunded",
|
|
@@ -3927,7 +4102,7 @@ async function refundNamedSeatByOperator(db, input, stripeSecretKey, now) {
|
|
|
3927
4102
|
await requestNamedSeatRefund(db, { ...input, reason: "operator" }, now);
|
|
3928
4103
|
await completeNamedSeatRefund(db, input.seatId, stripeSecretKey, now);
|
|
3929
4104
|
const refunded = await membershipLoad(db, "networkSeat", input.seatId);
|
|
3930
|
-
if (refunded?.status !== "refunded" || !Number.isSafeInteger(refunded.refundedCents) || Number(refunded.refundedCents)
|
|
4105
|
+
if (refunded?.status !== "refunded" || !Number.isSafeInteger(refunded.refundedCents) || Number(refunded.refundedCents) !== Number(seat.amountCents)) {
|
|
3931
4106
|
throw new Error("named seat refund unconfirmed");
|
|
3932
4107
|
}
|
|
3933
4108
|
return { seatId: String(seat.id), status: "refunded", refundedCents: Number(refunded.refundedCents), subscriptionCanceled: false };
|
|
@@ -3959,9 +4134,56 @@ async function liftSeatPurchaseWindow(db, scope2, now, limit = 100) {
|
|
|
3959
4134
|
return { lifted, more: rows5.length > limit };
|
|
3960
4135
|
}
|
|
3961
4136
|
|
|
4137
|
+
// src/membership-seat-reprice.ts
|
|
4138
|
+
async function lowerSeatPrice(db, scope2, now, limit = 100) {
|
|
4139
|
+
membershipClock(now);
|
|
4140
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 200) throw new TypeError("invalid reprice limit");
|
|
4141
|
+
const state = await loadMembershipAuthorityState(db, scope2);
|
|
4142
|
+
const versions = (await db.query({ membershipOfferingVersion: { $: { where: { chapterId: scope2.chapterId }, limit: 200 } } })).membershipOfferingVersion ?? [];
|
|
4143
|
+
if (versions.length >= 200) throw Object.assign(new Error("policy_history_incomplete"), { code: "policy_history_incomplete" });
|
|
4144
|
+
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];
|
|
4145
|
+
if (!current) throw Object.assign(new Error("ambiguous_membership_policy"), { code: "ambiguous_membership_policy" });
|
|
4146
|
+
const target = current.seatPriceCents;
|
|
4147
|
+
const rows5 = (await db.query({ networkMembership: { $: { where: { ...scope2 }, order: { updatedAt: "asc" }, limit: limit + 1 } } })).networkMembership ?? [];
|
|
4148
|
+
const receipt = { seatPriceCents: target, repriced: 0, skipped: [], more: rows5.length > limit };
|
|
4149
|
+
for (const member of rows5.slice(0, limit)) {
|
|
4150
|
+
if (Object.entries(scope2).some(([key3, value]) => member[key3] !== value)) throw new Error("membership reprice scope mismatch");
|
|
4151
|
+
const id6 = String(member.id), skip = (reason) => {
|
|
4152
|
+
receipt.skipped.push({ membershipId: id6, reason });
|
|
4153
|
+
};
|
|
4154
|
+
const offering = membershipOfferingVersion(member.offeringSnapshot);
|
|
4155
|
+
if (offering.seatPriceCents <= target) {
|
|
4156
|
+
skip("seat price already at or below the current offering");
|
|
4157
|
+
continue;
|
|
4158
|
+
}
|
|
4159
|
+
if (!["paid_pending_approval", "active", "cancel_at_term_end", "payment_grace"].includes(String(member.status))) {
|
|
4160
|
+
skip(`membership is ${String(member.status)}`);
|
|
4161
|
+
continue;
|
|
4162
|
+
}
|
|
4163
|
+
const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
|
|
4164
|
+
if (seat && (seat.status !== "payment_pending" || seat.providerPaymentIntentId !== void 0)) {
|
|
4165
|
+
skip(`seat is ${String(seat.status)}`);
|
|
4166
|
+
continue;
|
|
4167
|
+
}
|
|
4168
|
+
const ops = [membershipUpdate("networkMembership", id6, { offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(member.revision) + 1, updatedAt: now })];
|
|
4169
|
+
const guards = [
|
|
4170
|
+
{ ns: "networkMembership", id: id6, exists: true, equals: { revision: member.revision } },
|
|
4171
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative" } }
|
|
4172
|
+
];
|
|
4173
|
+
if (seat) {
|
|
4174
|
+
ops.push(membershipUpdate("networkSeat", String(seat.id), { amountCents: target, offeringSnapshot: { ...offering, seatPriceCents: target }, revision: Number(seat.revision) + 1, updatedAt: now }));
|
|
4175
|
+
guards.push({ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision, status: "payment_pending" } });
|
|
4176
|
+
}
|
|
4177
|
+
await db.transact(ops, { mutationId: `seat-reprice:${id6}:${member.revision}`, guards });
|
|
4178
|
+
receipt.repriced++;
|
|
4179
|
+
}
|
|
4180
|
+
return receipt;
|
|
4181
|
+
}
|
|
4182
|
+
|
|
3962
4183
|
// src/worker-routes-admin-membership-seats.ts
|
|
3963
4184
|
var LIST = "/api/admin/membership-seats";
|
|
3964
4185
|
var LIFT = "/api/admin/membership-seats/lift-purchase-window";
|
|
4186
|
+
var REPRICE = "/api/admin/membership-seats/reprice";
|
|
3965
4187
|
var ONE = /^\/api\/admin\/membership-seats\/([A-Za-z0-9][A-Za-z0-9_.:-]{2,159})\/refund$/;
|
|
3966
4188
|
var SCOPE = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
3967
4189
|
var PUBLIC = [
|
|
@@ -3988,11 +4210,11 @@ var PUBLIC = [
|
|
|
3988
4210
|
];
|
|
3989
4211
|
var project = (row2) => Object.fromEntries(PUBLIC.filter((key3) => row2[key3] !== void 0).map((key3) => [key3, row2[key3]]));
|
|
3990
4212
|
var handleAdminMembershipSeats = async (req, url, env, ctx) => {
|
|
3991
|
-
const one = url.pathname.match(ONE), lift = url.pathname === LIFT;
|
|
3992
|
-
if (url.pathname !== LIST && !one && !lift) return null;
|
|
4213
|
+
const one = url.pathname.match(ONE), lift = url.pathname === LIFT, reprice = url.pathname === REPRICE;
|
|
4214
|
+
if (url.pathname !== LIST && !one && !lift && !reprice) return null;
|
|
3993
4215
|
const operator = ctx.membershipAuthorityOperator;
|
|
3994
4216
|
if (!operator) return json({ error: "not found" }, 404);
|
|
3995
|
-
if (req.method !== (one || lift ? "POST" : "GET")) return json({ error: "method not allowed" }, 405);
|
|
4217
|
+
if (req.method !== (one || lift || reprice ? "POST" : "GET")) return json({ error: "method not allowed" }, 405);
|
|
3996
4218
|
const raw2 = ctx.makeDb(env), user = await ctx.verifyUser(req, env);
|
|
3997
4219
|
if (!user) return json({ error: "unauthorized" }, 401);
|
|
3998
4220
|
if (!await ctx.isSuperAdminEmail(raw2, user.email, user.userId)) return json({ error: "super-admin required", code: "super_admin_required" }, 403);
|
|
@@ -4002,7 +4224,7 @@ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
|
|
|
4002
4224
|
} catch {
|
|
4003
4225
|
return json({ error: "the membership authority is not hosted by this deployment" }, 404);
|
|
4004
4226
|
}
|
|
4005
|
-
if (lift) {
|
|
4227
|
+
if (lift || reprice) {
|
|
4006
4228
|
let body;
|
|
4007
4229
|
try {
|
|
4008
4230
|
body = await req.json();
|
|
@@ -4010,14 +4232,16 @@ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
|
|
|
4010
4232
|
return json({ error: "chapterId and runtime are required" }, 400);
|
|
4011
4233
|
}
|
|
4012
4234
|
const appId = body.appId ?? body.chapterId;
|
|
4013
|
-
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)) {
|
|
4014
|
-
return json({ error: "chapterId and runtime are required" }, 400);
|
|
4235
|
+
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))) {
|
|
4236
|
+
return json({ error: reprice ? "chapterId, runtime and skuId are required" : "chapterId and runtime are required" }, 400);
|
|
4015
4237
|
}
|
|
4238
|
+
const scope2 = { appId, chapterId: body.chapterId, environment: env.ODLA_ENV, runtime: body.runtime };
|
|
4016
4239
|
try {
|
|
4017
|
-
return json({ ok: true, ...await
|
|
4240
|
+
if (reprice) return json({ ok: true, ...await lowerSeatPrice(db, { ...scope2, skuId: String(body.skuId) }, Date.now()) });
|
|
4241
|
+
return json({ ok: true, ...await liftSeatPurchaseWindow(db, scope2, Date.now()) });
|
|
4018
4242
|
} catch (error) {
|
|
4019
4243
|
const code = error.code;
|
|
4020
|
-
return json({ error: "purchase window lift is pending; retry to confirm
|
|
4244
|
+
return json({ error: `${reprice ? "seat reprice" : "purchase window lift"} is pending; retry to confirm`, ...code ? { code } : {} }, code ? 409 : 503);
|
|
4021
4245
|
}
|
|
4022
4246
|
}
|
|
4023
4247
|
if (!one) {
|
|
@@ -4053,8 +4277,8 @@ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
|
|
|
4053
4277
|
var LIMIT = 200;
|
|
4054
4278
|
var text2 = (value) => typeof value === "string" && value.length > 0 && value.length <= 200 ? value : void 0;
|
|
4055
4279
|
var instant = (value) => {
|
|
4056
|
-
const
|
|
4057
|
-
return Number.isSafeInteger(
|
|
4280
|
+
const at2 = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
|
|
4281
|
+
return Number.isSafeInteger(at2) && at2 >= 0 ? at2 : void 0;
|
|
4058
4282
|
};
|
|
4059
4283
|
function provenance(row2) {
|
|
4060
4284
|
const source = text2(row2.source), actor = text2(row2.actor ?? row2.$createdBy), recordedAt = instant(row2.recordedAt ?? row2.createdAt ?? row2.$createdAt);
|
|
@@ -6427,7 +6651,7 @@ var handleMe = async (req, url, env, ctx) => {
|
|
|
6427
6651
|
} catch {
|
|
6428
6652
|
}
|
|
6429
6653
|
}
|
|
6430
|
-
return json({ ...base, memberAccess, membershipRestart, application: member?.application ?? null, namedSeatsEnabled: Boolean(
|
|
6654
|
+
return json({ ...base, memberAccess, membershipRestart, application: member?.application ?? null, namedSeatsEnabled: Boolean(ctx.membershipAuthority?.namedSeats && member?.application.paid && member.source.status !== "submitted") });
|
|
6431
6655
|
};
|
|
6432
6656
|
var handleCrm = async (req, url, env, ctx) => {
|
|
6433
6657
|
const crmBase = ctx.crmBase;
|
|
@@ -9425,6 +9649,7 @@ async function namedSeatOfferDigest(offer) {
|
|
|
9425
9649
|
])));
|
|
9426
9650
|
return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
9427
9651
|
}
|
|
9652
|
+
var at = (value) => Number.isSafeInteger(value) ? Number(value) : void 0;
|
|
9428
9653
|
async function readNamedSeatOffer(db, input, now) {
|
|
9429
9654
|
membershipClock(now);
|
|
9430
9655
|
await loadMembershipAuthorityState(db, input);
|
|
@@ -9447,10 +9672,19 @@ async function readNamedSeatOffer(db, input, now) {
|
|
|
9447
9672
|
seatId: String(seat.id),
|
|
9448
9673
|
status: String(seat.status),
|
|
9449
9674
|
recipientName: String(seat.recipientName),
|
|
9450
|
-
recipientEmail: String(seat.recipientEmailNormalized)
|
|
9675
|
+
recipientEmail: String(seat.recipientEmailNormalized),
|
|
9676
|
+
recipientState: namedSeatRecipientState(seat),
|
|
9677
|
+
...at(seat.purchasedAt) !== void 0 ? { purchasedAt: at(seat.purchasedAt) } : {},
|
|
9678
|
+
...at(seat.acceptedAt) !== void 0 ? { acceptedAt: at(seat.acceptedAt) } : {},
|
|
9679
|
+
...seat.status === "active" && at(seat.journeyApprovedAt) !== void 0 ? { joinedAt: at(seat.journeyApprovedAt) } : {}
|
|
9451
9680
|
} } : {}
|
|
9452
9681
|
};
|
|
9453
|
-
return {
|
|
9682
|
+
return {
|
|
9683
|
+
...offer,
|
|
9684
|
+
digest: await namedSeatOfferDigest(offer),
|
|
9685
|
+
included: offering.seatPriceCents === 0,
|
|
9686
|
+
purchaseWindowed: offering.seatPurchaseWindowCalendarMonths !== void 0
|
|
9687
|
+
};
|
|
9454
9688
|
}
|
|
9455
9689
|
|
|
9456
9690
|
// src/named-seat-request.ts
|
|
@@ -9526,8 +9760,9 @@ var handleNamedSeat = async (req, url, env, ctx) => {
|
|
|
9526
9760
|
if (recipientName.length < 2 || /[\u0000-\u001f\u007f]/.test(body.recipientName) || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(recipientEmail) || recipientEmail === normalizeChapterEmail(user.email)) {
|
|
9527
9761
|
return json({ error: "a valid name and email for someone other than the purchaser are required" }, 400);
|
|
9528
9762
|
}
|
|
9529
|
-
|
|
9530
|
-
if (!
|
|
9763
|
+
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);
|
|
9764
|
+
if (!offer.eligible && !granted || body.quoteDigest !== offer.digest) return json({ error: "seat offer changed or is unavailable; refresh before paying" }, 409);
|
|
9765
|
+
if (!group?.refundPolicyText || !group.merchantDisclosureText || !offer.included && !group.stripePublishableKey) return json({ error: "seat checkout disclosures unavailable" }, 503);
|
|
9531
9766
|
const consentId = `${offer.membershipId}:named-seat-consent`;
|
|
9532
9767
|
const prior = await membershipLoad(db, "namedSeatConsents", consentId);
|
|
9533
9768
|
const attrs = {
|
|
@@ -9566,7 +9801,7 @@ var handleNamedSeat = async (req, url, env, ctx) => {
|
|
|
9566
9801
|
}
|
|
9567
9802
|
return json({
|
|
9568
9803
|
...receipt,
|
|
9569
|
-
publishableKey: String(group.stripePublishableKey),
|
|
9804
|
+
...group.stripePublishableKey ? { publishableKey: String(group.stripePublishableKey) } : {},
|
|
9570
9805
|
lineItems: { standardCents: offer.amountCents, discountCents: 0, dueTodayCents: offer.amountCents }
|
|
9571
9806
|
});
|
|
9572
9807
|
} catch {
|
|
@@ -11368,7 +11603,7 @@ async function networkApplicationAction(opts) {
|
|
|
11368
11603
|
if (app.status !== input.expectedStage || !applicationActionStages(app, env, ctx).includes(input.stage)) {
|
|
11369
11604
|
return json({ error: "application action is no longer available; reload", code: "invalid_application_action" }, 409);
|
|
11370
11605
|
}
|
|
11371
|
-
const
|
|
11606
|
+
const at2 = Date.now();
|
|
11372
11607
|
const meta = { [ACTION_META]: {
|
|
11373
11608
|
version: 1,
|
|
11374
11609
|
state: "pending",
|
|
@@ -11388,7 +11623,7 @@ async function networkApplicationAction(opts) {
|
|
|
11388
11623
|
recordId: record4.id,
|
|
11389
11624
|
kind: "note",
|
|
11390
11625
|
authorId: `network:${sourceId}`,
|
|
11391
|
-
createdAt:
|
|
11626
|
+
createdAt: at2,
|
|
11392
11627
|
body: `Requested ${input.expectedStage} \u2192 ${input.stage} from ${sourceId}`,
|
|
11393
11628
|
meta
|
|
11394
11629
|
} },
|
|
@@ -11397,7 +11632,7 @@ async function networkApplicationAction(opts) {
|
|
|
11397
11632
|
recordId: record4.id,
|
|
11398
11633
|
kind: "note",
|
|
11399
11634
|
authorId: `network:${sourceId}`,
|
|
11400
|
-
createdAt:
|
|
11635
|
+
createdAt: at2,
|
|
11401
11636
|
body: "Application operation reconciliation",
|
|
11402
11637
|
meta: { state: "pending", activityId }
|
|
11403
11638
|
} }
|
|
@@ -12248,9 +12483,9 @@ function normalizeNetworkIdentity(kind, value) {
|
|
|
12248
12483
|
return trimmed;
|
|
12249
12484
|
}
|
|
12250
12485
|
function emailLooksReal(value) {
|
|
12251
|
-
const
|
|
12252
|
-
if (
|
|
12253
|
-
const domain = value.slice(
|
|
12486
|
+
const at2 = value.lastIndexOf("@");
|
|
12487
|
+
if (at2 <= 0 || at2 === value.length - 1) return false;
|
|
12488
|
+
const domain = value.slice(at2 + 1).toLowerCase();
|
|
12254
12489
|
return !domain.endsWith(".invalid") && domain !== "example.com" && domain !== "example.test";
|
|
12255
12490
|
}
|
|
12256
12491
|
function validateNetworkPersonImport(input, allowedChapterIds) {
|
|
@@ -14569,7 +14804,9 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
14569
14804
|
if (start !== member.termEndsAt || start > now + 3e4) membershipFail("membership_renewal_period_gap");
|
|
14570
14805
|
const seat = member.namedSeatId ? await membershipLoad(db, "networkSeat", String(member.namedSeatId)) : void 0;
|
|
14571
14806
|
if (seat) requireMembershipScope(seat, input);
|
|
14572
|
-
const
|
|
14807
|
+
const openSeat = seat && !["refunded", "refund_pending", "canceled"].includes(String(seat.status)) ? seat : void 0;
|
|
14808
|
+
const billedSeat = openSeat && openSeat.providerSubscriptionId === member.providerSubscriptionId && openSeat.providerSubscriptionItemId ? openSeat : void 0;
|
|
14809
|
+
const carriedSeat = billedSeat ?? (openSeat && namedSeatIncluded(openSeat) && openSeat.purchasedAt !== void 0 ? openSeat : void 0);
|
|
14573
14810
|
const prices = [String(member.providerPriceId), ...billedSeat ? [String(billedSeat.priceId)] : []].sort();
|
|
14574
14811
|
const amount = Number(member.renewalAmountCents) + Number(billedSeat?.amountCents ?? 0);
|
|
14575
14812
|
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");
|
|
@@ -14609,19 +14846,19 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
14609
14846
|
evidenceGuard
|
|
14610
14847
|
];
|
|
14611
14848
|
if (seat) guards.push({ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision } });
|
|
14612
|
-
if (
|
|
14613
|
-
const seatEntitlement = await membershipLoad(db, "networkEntitlement", String(
|
|
14849
|
+
if (carriedSeat?.entitlementId) {
|
|
14850
|
+
const seatEntitlement = await membershipLoad(db, "networkEntitlement", String(carriedSeat.entitlementId));
|
|
14614
14851
|
requireMembershipScope(seatEntitlement, input);
|
|
14615
14852
|
guards.push({ ns: "networkEntitlement", id: String(seatEntitlement.id), exists: true, equals: { revision: seatEntitlement.revision } });
|
|
14616
14853
|
ops.push(
|
|
14617
|
-
membershipUpdate("networkSeat", String(
|
|
14854
|
+
membershipUpdate("networkSeat", String(carriedSeat.id), {
|
|
14618
14855
|
...paid ? { termStartedAt: start, termEndsAt: end } : {},
|
|
14619
|
-
revision: Number(
|
|
14856
|
+
revision: Number(carriedSeat.revision) + 1,
|
|
14620
14857
|
updatedAt: now
|
|
14621
14858
|
}),
|
|
14622
14859
|
membershipUpdate("networkEntitlement", String(seatEntitlement.id), {
|
|
14623
14860
|
...paid ? { termStartedAt: start, termEndsAt: end } : {},
|
|
14624
|
-
status:
|
|
14861
|
+
status: carriedSeat.journeyApprovedAt ? entitlementStatus : seatEntitlement.status,
|
|
14625
14862
|
revision: Number(seatEntitlement.revision) + 1,
|
|
14626
14863
|
updatedAt: now
|
|
14627
14864
|
})
|
|
@@ -14731,84 +14968,6 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
|
|
|
14731
14968
|
return membershipFail("membership_billing_retry_required");
|
|
14732
14969
|
}
|
|
14733
14970
|
|
|
14734
|
-
// src/membership-seat-reservation.ts
|
|
14735
|
-
async function recipientKey(input, email) {
|
|
14736
|
-
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([input.appId, input.chapterId, input.environment, input.runtime, email])));
|
|
14737
|
-
return `seat-recipient:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
14738
|
-
}
|
|
14739
|
-
async function reserveNamedMembershipSeat(db, input, now) {
|
|
14740
|
-
membershipClock(now);
|
|
14741
|
-
const name = typeof input.recipientName === "string" ? input.recipientName.trim().replace(/\s+/g, " ") : "";
|
|
14742
|
-
const email = normalizeChapterEmail(typeof input.recipientEmail === "string" ? input.recipientEmail : "");
|
|
14743
|
-
const purchaser = normalizeChapterEmail(typeof input.purchaserEmail === "string" ? input.purchaserEmail : "");
|
|
14744
|
-
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");
|
|
14745
|
-
const state = await loadMembershipAuthorityState(db, input);
|
|
14746
|
-
const memberId = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", memberId);
|
|
14747
|
-
requireMembershipScope(member, input);
|
|
14748
|
-
if (!Number.isSafeInteger(member.revision) || !/^price_[A-Za-z0-9_]+$/.test(String(member.providerPriceId)) || member.providerPriceId === input.priceId) membershipFail("named_seat_purchase_unavailable");
|
|
14749
|
-
const id6 = (await membershipOfferIdForRequest({ ...input, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
14750
|
-
let seat = await membershipLoad(db, "networkSeat", id6);
|
|
14751
|
-
if (!seat) {
|
|
14752
|
-
const offering = membershipOfferingVersion(member.offeringSnapshot), claimId = await recipientKey(input, email);
|
|
14753
|
-
const purchaseEndsAt = seatPurchaseDeadline(offering, member);
|
|
14754
|
-
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) {
|
|
14755
|
-
membershipFail("named_seat_purchase_unavailable");
|
|
14756
|
-
}
|
|
14757
|
-
const attrs = {
|
|
14758
|
-
id: id6,
|
|
14759
|
-
...membershipScope(input),
|
|
14760
|
-
primaryMembershipId: memberId,
|
|
14761
|
-
recipientName: name,
|
|
14762
|
-
recipientEmailNormalized: email,
|
|
14763
|
-
purchaserUserId: input.purchaserUserId,
|
|
14764
|
-
recipientClaimId: claimId,
|
|
14765
|
-
priceId: input.priceId,
|
|
14766
|
-
primaryPriceId: member.providerPriceId,
|
|
14767
|
-
amountCents: offering.seatPriceCents,
|
|
14768
|
-
currency: offering.currency,
|
|
14769
|
-
interval: offering.interval,
|
|
14770
|
-
providerCustomerId: member.providerCustomerId,
|
|
14771
|
-
providerSubscriptionId: member.providerSubscriptionId,
|
|
14772
|
-
status: "payment_pending",
|
|
14773
|
-
preparedAt: now,
|
|
14774
|
-
termEndsAt: member.termEndsAt,
|
|
14775
|
-
seatPurchaseEndsAt: purchaseEndsAt,
|
|
14776
|
-
offeringSnapshot: offering,
|
|
14777
|
-
revision: 1,
|
|
14778
|
-
createdAt: now,
|
|
14779
|
-
updatedAt: now
|
|
14780
|
-
};
|
|
14781
|
-
try {
|
|
14782
|
-
await db.transact([
|
|
14783
|
-
membershipUpdate("networkSeat", id6, attrs),
|
|
14784
|
-
membershipUpdate("networkSeatRecipient", claimId, {
|
|
14785
|
-
id: claimId,
|
|
14786
|
-
appId: input.appId,
|
|
14787
|
-
chapterId: input.chapterId,
|
|
14788
|
-
environment: input.environment,
|
|
14789
|
-
runtime: input.runtime,
|
|
14790
|
-
seatId: id6,
|
|
14791
|
-
recipientEmailNormalized: email,
|
|
14792
|
-
createdAt: now
|
|
14793
|
-
}),
|
|
14794
|
-
membershipUpdate("networkMembership", memberId, { namedSeatId: id6, revision: Number(member.revision) + 1, updatedAt: now })
|
|
14795
|
-
], { mutationId: `named-seat-reserve:${id6}`, guards: [
|
|
14796
|
-
{ ns: "networkSeat", id: id6, exists: false },
|
|
14797
|
-
{ ns: "networkSeatRecipient", id: claimId, exists: false },
|
|
14798
|
-
{ ns: "networkMembership", id: memberId, exists: true, equals: { revision: member.revision }, absent: ["namedSeatId"] },
|
|
14799
|
-
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
14800
|
-
] });
|
|
14801
|
-
} catch (error) {
|
|
14802
|
-
if (error.code !== "transact_guard_failed") throw error;
|
|
14803
|
-
}
|
|
14804
|
-
seat = await membershipLoad(db, "networkSeat", id6);
|
|
14805
|
-
if (!seat && await membershipLoad(db, "networkSeatRecipient", claimId)) membershipFail("named_seat_recipient_unavailable");
|
|
14806
|
-
}
|
|
14807
|
-
requireMembershipScope(seat, input);
|
|
14808
|
-
if (seat.primaryMembershipId !== memberId || seat.recipientName !== name || seat.recipientEmailNormalized !== email || seat.priceId !== input.priceId || seat.purchaserUserId !== input.purchaserUserId) membershipFail("named_seat_purchase_conflict");
|
|
14809
|
-
return seat;
|
|
14810
|
-
}
|
|
14811
|
-
|
|
14812
14971
|
// src/membership-seat-stripe.ts
|
|
14813
14972
|
import { stripeRequest as stripeRequest12 } from "@odla-ai/stripe";
|
|
14814
14973
|
var reject7 = () => {
|
|
@@ -14896,6 +15055,7 @@ async function attachNamedSeatStripeRenewal(sk, seat) {
|
|
|
14896
15055
|
// src/membership-seat-checkout.ts
|
|
14897
15056
|
async function prepareNamedMembershipSeatPayment(db, input, secretKey, now) {
|
|
14898
15057
|
let seat = await reserveNamedMembershipSeat(db, input, now);
|
|
15058
|
+
if (namedSeatIncluded(seat)) membershipFail("named_seat_included");
|
|
14899
15059
|
const state = await loadMembershipAuthorityState(db, input);
|
|
14900
15060
|
const member = await membershipLoad(db, "networkMembership", seat.primaryMembershipId);
|
|
14901
15061
|
requireMembershipScope(member, input);
|
|
@@ -15102,7 +15262,7 @@ async function recordRecipient(db, input, now, approve) {
|
|
|
15102
15262
|
requireMembershipScope(member, input);
|
|
15103
15263
|
const entitlement = await membershipLoad(db, "networkEntitlement", String(seat.entitlementId));
|
|
15104
15264
|
requireMembershipScope(entitlement, input);
|
|
15105
|
-
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
|
|
15265
|
+
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");
|
|
15106
15266
|
if (seat.acceptedAt === void 0) {
|
|
15107
15267
|
if (approve || normalizeChapterEmail(input.verifiedRecipientEmail) !== seat.recipientEmailNormalized) {
|
|
15108
15268
|
membershipFail("named_seat_recipient_verification_required");
|
|
@@ -15289,6 +15449,15 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
|
|
|
15289
15449
|
};
|
|
15290
15450
|
if (action === "seat-accept") return acceptNamedSeatRecipient(db, recipientInput, now);
|
|
15291
15451
|
if (action === "seat-approve") return approveNamedSeatRecipient(db, recipientInput, now);
|
|
15452
|
+
const checkout = {
|
|
15453
|
+
...target,
|
|
15454
|
+
priceId: peer.seatPriceId,
|
|
15455
|
+
recipientName: String(input.recipientName),
|
|
15456
|
+
recipientEmail: String(input.recipientEmail),
|
|
15457
|
+
purchaserEmail: String(input.purchaserEmail),
|
|
15458
|
+
purchaserUserId: String(input.purchaserUserId)
|
|
15459
|
+
};
|
|
15460
|
+
if (action === "seat-checkouts" && namedSeatIncluded(await reserveNamedMembershipSeat(db, checkout, now))) return grantIncludedNamedSeat(db, checkout, now);
|
|
15292
15461
|
const secret = await getVaultSecret(db, stripeSecretName);
|
|
15293
15462
|
if (!secret) throw new Error("network Stripe provider is unavailable");
|
|
15294
15463
|
if (action === "seat-decline") {
|
|
@@ -15304,14 +15473,7 @@ async function handleMembershipSeatAction(action, db, peer, input, now, stripeSe
|
|
|
15304
15473
|
return { seatId: seat.id, status: "refunded", refundedCents: refunded.refundedCents, subscriptionCanceled: false };
|
|
15305
15474
|
}
|
|
15306
15475
|
if (action === "seat-operator-refund") return refundNamedSeatByOperator(db, { ...target, seatId: String(input.seatId) }, secret, now);
|
|
15307
|
-
if (action === "seat-checkouts") return prepareNamedMembershipSeatPayment(db,
|
|
15308
|
-
...target,
|
|
15309
|
-
priceId: peer.seatPriceId,
|
|
15310
|
-
recipientName: String(input.recipientName),
|
|
15311
|
-
recipientEmail: String(input.recipientEmail),
|
|
15312
|
-
purchaserEmail: String(input.purchaserEmail),
|
|
15313
|
-
purchaserUserId: String(input.purchaserUserId)
|
|
15314
|
-
}, secret, now);
|
|
15476
|
+
if (action === "seat-checkouts") return prepareNamedMembershipSeatPayment(db, checkout, secret, now);
|
|
15315
15477
|
const evidence = { ...target, seatId: String(input.seatId), eventId: String(input.eventId) };
|
|
15316
15478
|
if (action === "seat-payments") {
|
|
15317
15479
|
const receipt = await confirmNamedMembershipSeatPayment(db, evidence, now);
|
|
@@ -16167,13 +16329,13 @@ async function requestNamedSeatDecline(options) {
|
|
|
16167
16329
|
verifiedRecipientEmail: options.verifiedRecipientEmail
|
|
16168
16330
|
});
|
|
16169
16331
|
const seat = result.seat;
|
|
16170
|
-
if (!seat || seat.seatId !== options.seatId || seat.status !== "refunded" || seat.subscriptionCanceled !== false || !Number.isSafeInteger(seat.refundedCents) || seat.refundedCents
|
|
16332
|
+
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");
|
|
16171
16333
|
return seat;
|
|
16172
16334
|
}
|
|
16173
16335
|
async function requestNamedSeatOperatorRefund(options) {
|
|
16174
16336
|
const result = await requestAuthority(options, "seat-operator-refund", { applicationId: options.applicationId, skuId: options.skuId, seatId: options.seatId });
|
|
16175
16337
|
const seat = result.seat;
|
|
16176
|
-
if (!seat || seat.seatId !== options.seatId || seat.status !== "refunded" || seat.subscriptionCanceled !== false || !Number.isSafeInteger(seat.refundedCents) || seat.refundedCents
|
|
16338
|
+
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");
|
|
16177
16339
|
return seat;
|
|
16178
16340
|
}
|
|
16179
16341
|
async function requestNamedSeatReservation(options) {
|
|
@@ -16187,13 +16349,13 @@ async function requestNamedSeatReservation(options) {
|
|
|
16187
16349
|
});
|
|
16188
16350
|
const seat = result.seat;
|
|
16189
16351
|
const expected = (await membershipOfferIdForRequest({ ...options, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
16190
|
-
if (!seat || seat.seatId !== expected || !Number.isSafeInteger(seat.amountCents) || seat.amountCents
|
|
16352
|
+
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");
|
|
16191
16353
|
return { seatId: seat.seatId, amountCents: seat.amountCents, currency: seat.currency, termEndsAt: seat.termEndsAt };
|
|
16192
16354
|
}
|
|
16193
16355
|
async function requestNamedSeatOffer(options) {
|
|
16194
16356
|
const result = await requestAuthority(options, "seat-offer", { applicationId: options.applicationId, skuId: options.skuId });
|
|
16195
16357
|
const offer = result.seat;
|
|
16196
|
-
if (!offer || offer.membershipId !== await networkMembershipId(options) || !Number.isSafeInteger(offer.amountCents) || offer.amountCents
|
|
16358
|
+
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");
|
|
16197
16359
|
return offer;
|
|
16198
16360
|
}
|
|
16199
16361
|
async function requestNamedSeatInvitation(options) {
|
|
@@ -16229,7 +16391,8 @@ async function requestNamedSeatCheckout(options) {
|
|
|
16229
16391
|
});
|
|
16230
16392
|
const seat = result.seat;
|
|
16231
16393
|
const expected = (await membershipOfferIdForRequest({ ...options, requestId: "named_seat" })).replace(/^offer_/, "seat_");
|
|
16232
|
-
|
|
16394
|
+
const included = seat?.amountCents === 0;
|
|
16395
|
+
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))) {
|
|
16233
16396
|
throw new Error("invalid named seat checkout receipt");
|
|
16234
16397
|
}
|
|
16235
16398
|
return seat;
|
|
@@ -16720,6 +16883,11 @@ export {
|
|
|
16720
16883
|
deliverMembershipChapterEffect,
|
|
16721
16884
|
deliverSignupControl,
|
|
16722
16885
|
drainMembershipEffects,
|
|
16886
|
+
grantIncludedNamedSeat,
|
|
16887
|
+
lowerSeatPrice,
|
|
16888
|
+
namedSeatIncluded,
|
|
16889
|
+
namedSeatRecipientState,
|
|
16890
|
+
readNamedSeatOffer,
|
|
16723
16891
|
refundNamedSeatByOperator,
|
|
16724
16892
|
renderActiveSignupControlHtml,
|
|
16725
16893
|
resolveNetworkTargetTransport,
|