@odla-ai/chapter 0.59.0 → 0.59.1
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 +14 -2
- package/dist/{chunk-WFTWT3V7.js → chunk-P6X3W2XP.js} +11 -5
- package/dist/{chunk-WFTWT3V7.js.map → chunk-P6X3W2XP.js.map} +1 -1
- package/dist/{chunk-Y2H4VURI.js → chunk-SDGVPIXF.js} +3 -2
- package/dist/chunk-SDGVPIXF.js.map +1 -0
- package/dist/index.cjs +161 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +161 -95
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +4 -0
- package/dist/ui/admin/index.js +1 -1
- package/dist/ui/index.js +2 -2
- package/dist/ui/member/index.d.ts +1 -1
- package/dist/ui/member/index.js +1 -1
- package/dist/worker/index.cjs +264 -181
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +122 -14
- package/dist/worker/index.d.ts +122 -14
- package/dist/worker/index.js +264 -181
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-Y2H4VURI.js.map +0 -1
package/dist/worker/index.js
CHANGED
|
@@ -1633,9 +1633,9 @@ async function backfillCrm(deps) {
|
|
|
1633
1633
|
}
|
|
1634
1634
|
|
|
1635
1635
|
// src/email-retirement.ts
|
|
1636
|
-
function isRetiredEmailCopy(subject,
|
|
1636
|
+
function isRetiredEmailCopy(subject, text10) {
|
|
1637
1637
|
const subjectCopy = subject.trimStart().replace(/^\[dev\]\s*/i, "");
|
|
1638
|
-
const bodyCopy =
|
|
1638
|
+
const bodyCopy = text10.trimStart().replace(/^\(dev redirect; original recipient: [^\r\n]*\)\r?\n\r?\n/, "");
|
|
1639
1639
|
return /^\(\s*unused\s*:/i.test(subjectCopy) || /^Unused\.\s*Switched off\b/i.test(bodyCopy);
|
|
1640
1640
|
}
|
|
1641
1641
|
|
|
@@ -1713,10 +1713,10 @@ function planDelivery(input) {
|
|
|
1713
1713
|
if (isRetiredEmailCopy(renderedSubject, renderedText)) return { deliver: false, reason: "template-retired" };
|
|
1714
1714
|
if (validateEmailCopy({ subject: renderedSubject, text: renderedText }, { allowedVariables: [] }).length) return { deliver: false, reason: "template-invalid" };
|
|
1715
1715
|
const subject = (redirect ? "[dev] " : "") + renderedSubject;
|
|
1716
|
-
const
|
|
1716
|
+
const text10 = redirect ? `(dev redirect; original recipient: ${input.to})
|
|
1717
1717
|
|
|
1718
1718
|
` + renderedText : renderedText;
|
|
1719
|
-
return { deliver: true, transport, to, subject, text:
|
|
1719
|
+
return { deliver: true, transport, to, subject, text: text10, redirected: redirect };
|
|
1720
1720
|
}
|
|
1721
1721
|
|
|
1722
1722
|
// src/notify.ts
|
|
@@ -1864,8 +1864,8 @@ function formatInstant(at2, timeZone) {
|
|
|
1864
1864
|
var adminUrlFor = (origin) => `${origin}/admin/`;
|
|
1865
1865
|
var membersUrlFor = (origin) => `${origin}/members/`;
|
|
1866
1866
|
function applicantVariables(app) {
|
|
1867
|
-
const
|
|
1868
|
-
return { firstName:
|
|
1867
|
+
const text10 = (v) => typeof v === "string" ? v : "";
|
|
1868
|
+
return { firstName: text10(app.firstName), lastName: text10(app.lastName), email: text10(app.email), phone: text10(app.phone), state: text10(app.state) };
|
|
1869
1869
|
}
|
|
1870
1870
|
function sampleVariables(origin, timeZone, now = Date.now()) {
|
|
1871
1871
|
const startAt = formatInstant(now + 864e5, timeZone);
|
|
@@ -3599,12 +3599,87 @@ function paymentLineItems(group, tier) {
|
|
|
3599
3599
|
|
|
3600
3600
|
// src/membership-dispute-authority.ts
|
|
3601
3601
|
import { stripeRequest as stripeRequest4 } from "@odla-ai/stripe";
|
|
3602
|
+
|
|
3603
|
+
// src/membership-dispute-transition.ts
|
|
3602
3604
|
var MEMBERSHIP_DISPUTED_STATUS = "payment_disputed";
|
|
3603
3605
|
var DISPUTE_WON_STATUSES = ["won", "warning_closed"];
|
|
3604
3606
|
var CLOSED_STATUSES = [...DISPUTE_WON_STATUSES, "lost"];
|
|
3605
|
-
|
|
3607
|
+
function disputeTransition(row2, fact, eventId, now, terminal) {
|
|
3608
|
+
const status2 = String(fact.status ?? "unknown"), closed = CLOSED_STATUSES.includes(status2);
|
|
3609
|
+
const sameDispute = row2.disputeId === fact.objectId;
|
|
3610
|
+
if (sameDispute && Number(row2.disputeClosedAt ?? 0) > 0 && !closed) return null;
|
|
3611
|
+
const won = DISPUTE_WON_STATUSES.includes(status2);
|
|
3612
|
+
const suspended = row2.status === MEMBERSHIP_DISPUTED_STATUS, done = terminal.includes(String(row2.status));
|
|
3613
|
+
const restored = String(row2.disputePriorStatus ?? (row2.journeyApprovedAt ? "active" : "paid_pending_approval"));
|
|
3614
|
+
const next = done ? String(row2.status) : won ? suspended ? restored : String(row2.status) : MEMBERSHIP_DISPUTED_STATUS;
|
|
3615
|
+
return {
|
|
3616
|
+
status: next,
|
|
3617
|
+
disputeId: fact.objectId,
|
|
3618
|
+
disputeStatus: status2,
|
|
3619
|
+
disputeEventId: eventId,
|
|
3620
|
+
disputeClosedAt: closed ? now : 0,
|
|
3621
|
+
...fact.disputeReason ? { disputeReason: fact.disputeReason } : {},
|
|
3622
|
+
...fact.amountCents !== void 0 ? { disputeAmountCents: fact.amountCents } : {},
|
|
3623
|
+
...sameDispute ? {} : { disputedAt: now },
|
|
3624
|
+
...next === MEMBERSHIP_DISPUTED_STATUS && !suspended ? { disputePriorStatus: String(row2.status) } : {},
|
|
3625
|
+
revision: Number(row2.revision) + 1,
|
|
3626
|
+
updatedAt: now
|
|
3627
|
+
};
|
|
3628
|
+
}
|
|
3629
|
+
|
|
3630
|
+
// src/membership-seat-dispute.ts
|
|
3606
3631
|
var text2 = (value) => typeof value === "string" && value ? value : void 0;
|
|
3607
|
-
var
|
|
3632
|
+
var TERMINAL = ["payment_pending", "payment_processing", "payment_canceling", "refund_pending", "refunded", "canceled"];
|
|
3633
|
+
var namedSeatDisputeStanding = (seat, applied) => ({ seatId: String(seat.id), status: String(seat.status), ...text2(seat.disputeStatus) ? { disputeStatus: text2(seat.disputeStatus) } : {}, applied });
|
|
3634
|
+
function disputedChargeCoversSeat(charge, seat) {
|
|
3635
|
+
return charge.seatId === seat.id && charge.customerId === seat.providerCustomerId && charge.paymentIntentId !== void 0 && (charge.paymentIntentId === seat.providerPaymentIntentId || charge.paymentIntentId === seat.rechargePaymentIntentId);
|
|
3636
|
+
}
|
|
3637
|
+
async function reconcileNamedSeatDispute(db, input, now, charge, evidence) {
|
|
3638
|
+
if (!charge.seatId) membershipFail("membership_dispute_scope_mismatch");
|
|
3639
|
+
const membership = async (applied, seat) => {
|
|
3640
|
+
const id7 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id7);
|
|
3641
|
+
requireMembershipScope(member, input);
|
|
3642
|
+
return {
|
|
3643
|
+
membershipId: id7,
|
|
3644
|
+
status: String(member.status),
|
|
3645
|
+
termEndsAt: Number(member.termEndsAt),
|
|
3646
|
+
graceEndsAt: Number(member.graceEndsAt ?? 0),
|
|
3647
|
+
autoRenew: member.autoRenew === true,
|
|
3648
|
+
applied,
|
|
3649
|
+
...text2(member.disputeStatus) ? { disputeStatus: text2(member.disputeStatus) } : {},
|
|
3650
|
+
...seat ? { seat: namedSeatDisputeStanding(seat, applied) } : {}
|
|
3651
|
+
};
|
|
3652
|
+
};
|
|
3653
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
3654
|
+
const state = await loadMembershipAuthorityState(db, input), seat = await membershipLoad(db, "networkSeat", charge.seatId);
|
|
3655
|
+
if (!seat) return membership(false);
|
|
3656
|
+
requireMembershipScope(seat, input);
|
|
3657
|
+
if (charge.customerId !== seat.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
|
|
3658
|
+
if (!disputedChargeCoversSeat(charge, seat) || seat.disputeEventId === input.eventId) return membership(seat.disputeEventId === input.eventId, seat);
|
|
3659
|
+
const attrs = disputeTransition(seat, evidence.fact, input.eventId, now, TERMINAL);
|
|
3660
|
+
if (!attrs) return membership(false, seat);
|
|
3661
|
+
try {
|
|
3662
|
+
await db.transact([membershipUpdate("networkSeat", String(seat.id), attrs)], { mutationId: `seat-dispute:${seat.id}:${input.eventId}`, guards: [
|
|
3663
|
+
{ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision } },
|
|
3664
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
|
|
3665
|
+
{ ns: "networkCommercialEvents", id: String(evidence.row.id), exists: true, equals: { digest: evidence.fact.digest } },
|
|
3666
|
+
evidence.guard
|
|
3667
|
+
] });
|
|
3668
|
+
} catch (error) {
|
|
3669
|
+
if (error.code !== "transact_guard_failed") throw error;
|
|
3670
|
+
continue;
|
|
3671
|
+
}
|
|
3672
|
+
const saved = await membershipLoad(db, "networkSeat", String(seat.id));
|
|
3673
|
+
requireMembershipScope(saved, input);
|
|
3674
|
+
return membership(true, saved);
|
|
3675
|
+
}
|
|
3676
|
+
return membershipFail("membership_dispute_retry_required");
|
|
3677
|
+
}
|
|
3678
|
+
|
|
3679
|
+
// src/membership-dispute-authority.ts
|
|
3680
|
+
var TERMINAL2 = ["refunded", "refund_pending", "canceled", "declined", "restarting", "superseded"];
|
|
3681
|
+
var text3 = (value) => typeof value === "string" && value ? value : void 0;
|
|
3682
|
+
var refOf = (value) => typeof value === "string" ? text3(value) : text3(value?.id);
|
|
3608
3683
|
async function readDisputedCharge(secretKey, chargeId, fetch2) {
|
|
3609
3684
|
if (!/^ch_[A-Za-z0-9]+$/.test(chargeId)) membershipFail("invalid_dispute_charge");
|
|
3610
3685
|
const config = { secretKey, ...fetch2 ? { fetch: fetch2 } : {} };
|
|
@@ -3618,7 +3693,7 @@ async function readDisputedCharge(secretKey, chargeId, fetch2) {
|
|
|
3618
3693
|
if (!invoice.ok || invoice.body.id !== invoiceId) membershipFail("membership_dispute_provider_unavailable");
|
|
3619
3694
|
subscriptionId = refOf(invoice.body.subscription) ?? refOf(invoice.body.parent?.subscription_details?.subscription);
|
|
3620
3695
|
}
|
|
3621
|
-
const seatId =
|
|
3696
|
+
const seatId = text3(charge.body.metadata?.seatId);
|
|
3622
3697
|
return {
|
|
3623
3698
|
chargeId,
|
|
3624
3699
|
customerId,
|
|
@@ -3644,37 +3719,23 @@ async function reconcileMembershipDispute(db, input, now, secretKey, resolved) {
|
|
|
3644
3719
|
termEndsAt: Number(value.termEndsAt),
|
|
3645
3720
|
graceEndsAt: Number(value.graceEndsAt ?? 0),
|
|
3646
3721
|
autoRenew: value.autoRenew === true,
|
|
3647
|
-
...
|
|
3722
|
+
...text3(value.disputeStatus) ? { disputeStatus: text3(value.disputeStatus) } : {},
|
|
3648
3723
|
applied
|
|
3649
3724
|
});
|
|
3650
3725
|
const known = await membershipLoad(db, "networkMembership", id7);
|
|
3651
3726
|
requireMembershipScope(known, input);
|
|
3652
3727
|
if (known.disputeEventId === input.eventId) return receipt(known);
|
|
3728
|
+
const seatKnown = known.namedSeatId ? await membershipLoad(db, "networkSeat", String(known.namedSeatId)) : void 0;
|
|
3729
|
+
if (seatKnown?.disputeEventId === input.eventId) return { ...receipt(known, true), seat: namedSeatDisputeStanding(seatKnown, true) };
|
|
3653
3730
|
const charge = resolved && resolved.chargeId === fact.chargeId ? resolved : await readDisputedCharge(secretKey, fact.chargeId);
|
|
3731
|
+
if (charge.customerId !== known.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
|
|
3732
|
+
if (charge.seatId) return reconcileNamedSeatDispute(db, input, now, charge, { row: row2, fact, guard: evidenceGuard });
|
|
3654
3733
|
for (let attempt = 0; attempt < 8; attempt++) {
|
|
3655
3734
|
const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id7);
|
|
3656
3735
|
requireMembershipScope(member, input);
|
|
3657
|
-
if (charge.customerId !== member.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
|
|
3658
3736
|
if (!disputedChargeCoversMembership(charge, member) || member.disputeEventId === input.eventId) return receipt(member);
|
|
3659
|
-
const
|
|
3660
|
-
|
|
3661
|
-
if (sameDispute && Number(member.disputeClosedAt ?? 0) > 0 && !closed) return receipt(member, false);
|
|
3662
|
-
const won = DISPUTE_WON_STATUSES.includes(status2);
|
|
3663
|
-
const suspended = member.status === MEMBERSHIP_DISPUTED_STATUS, terminal = TERMINAL.includes(String(member.status));
|
|
3664
|
-
const next = terminal ? String(member.status) : won ? suspended ? String(member.disputePriorStatus ?? (member.journeyApprovedAt ? "active" : "paid_pending_approval")) : String(member.status) : MEMBERSHIP_DISPUTED_STATUS;
|
|
3665
|
-
const attrs = {
|
|
3666
|
-
status: next,
|
|
3667
|
-
disputeId: fact.objectId,
|
|
3668
|
-
disputeStatus: status2,
|
|
3669
|
-
disputeEventId: input.eventId,
|
|
3670
|
-
disputeClosedAt: closed ? now : 0,
|
|
3671
|
-
...fact.disputeReason ? { disputeReason: fact.disputeReason } : {},
|
|
3672
|
-
...fact.amountCents !== void 0 ? { disputeAmountCents: fact.amountCents } : {},
|
|
3673
|
-
...sameDispute ? {} : { disputedAt: now },
|
|
3674
|
-
...next === MEMBERSHIP_DISPUTED_STATUS && !suspended ? { disputePriorStatus: String(member.status) } : {},
|
|
3675
|
-
revision: Number(member.revision) + 1,
|
|
3676
|
-
updatedAt: now
|
|
3677
|
-
};
|
|
3737
|
+
const attrs = disputeTransition(member, fact, input.eventId, now, TERMINAL2);
|
|
3738
|
+
if (!attrs) return receipt(member, false);
|
|
3678
3739
|
try {
|
|
3679
3740
|
await db.transact([membershipUpdate("networkMembership", id7, attrs)], { mutationId: `membership-dispute:${id7}:${input.eventId}`, guards: [
|
|
3680
3741
|
{ ns: "networkMembership", id: id7, exists: true, equals: { revision: member.revision } },
|
|
@@ -4796,6 +4857,9 @@ function namedSeatRecipientState(seat) {
|
|
|
4796
4857
|
return seat.refundReason === "declined" ? "declined" : "refunded";
|
|
4797
4858
|
case "canceled":
|
|
4798
4859
|
return "canceled";
|
|
4860
|
+
// A card-network dispute on the seat's own payment pauses the recipient until it is won or re-charged.
|
|
4861
|
+
case "payment_disputed":
|
|
4862
|
+
return "disputed";
|
|
4799
4863
|
default:
|
|
4800
4864
|
return "payment_pending";
|
|
4801
4865
|
}
|
|
@@ -5124,6 +5188,7 @@ var handleAdminMembershipSeats = async (req, url, env, ctx) => {
|
|
|
5124
5188
|
|
|
5125
5189
|
// src/membership-dispute-recharge.ts
|
|
5126
5190
|
import { stripeRequest as stripeRequest7 } from "@odla-ai/stripe";
|
|
5191
|
+
var NS = { membership: "networkMembership", seat: "networkSeat" };
|
|
5127
5192
|
var fail6 = (code, extra = {}) => {
|
|
5128
5193
|
throw Object.assign(new Error(code), { code, ...extra });
|
|
5129
5194
|
};
|
|
@@ -5137,65 +5202,64 @@ async function defaultPaymentMethod(config, subscriptionId, customerId) {
|
|
|
5137
5202
|
if (!customer.ok) fail6("membership_dispute_provider_unavailable");
|
|
5138
5203
|
return refOf2(customer.body.invoice_settings?.default_payment_method);
|
|
5139
5204
|
}
|
|
5140
|
-
async function
|
|
5205
|
+
async function rechargeDisputed(db, kind, id7, secretKey, now) {
|
|
5141
5206
|
membershipClock(now);
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
const
|
|
5207
|
+
const ns = NS[kind];
|
|
5208
|
+
let row2 = await membershipLoad(db, ns, id7);
|
|
5209
|
+
if (!row2) fail6("membership_not_found");
|
|
5210
|
+
const scope2 = { chapterId: String(row2.chapterId), environment: String(row2.environment) };
|
|
5211
|
+
const membershipId = kind === "membership" ? id7 : String(row2.primaryMembershipId);
|
|
5212
|
+
const amountCents = Number(row2.disputeAmountCents ?? row2.amountCents), currency = String(row2.currency);
|
|
5146
5213
|
const done = (value, replayed) => ({
|
|
5147
5214
|
membershipId,
|
|
5215
|
+
...kind === "seat" ? { seatId: id7 } : {},
|
|
5148
5216
|
status: String(value.status),
|
|
5149
5217
|
paymentIntentId: String(value.rechargePaymentIntentId),
|
|
5150
5218
|
amountCents,
|
|
5151
5219
|
currency,
|
|
5152
5220
|
...replayed ? { replayed } : {}
|
|
5153
5221
|
});
|
|
5154
|
-
if (
|
|
5155
|
-
if (
|
|
5222
|
+
if (row2.status !== MEMBERSHIP_DISPUTED_STATUS) {
|
|
5223
|
+
if (row2.disputeStatus === "recharged" && row2.rechargePaymentIntentId) return done(row2, true);
|
|
5156
5224
|
fail6("membership_not_disputed");
|
|
5157
5225
|
}
|
|
5158
|
-
if (
|
|
5226
|
+
if (row2.disputeStatus !== "lost") fail6("membership_dispute_open");
|
|
5159
5227
|
if (!Number.isSafeInteger(amountCents) || amountCents <= 0 || !/^[a-z]{3}$/.test(currency)) fail6("membership_dispute_amount_invalid");
|
|
5160
|
-
let attempt =
|
|
5228
|
+
let attempt = row2.rechargeAttempt;
|
|
5161
5229
|
if (!attempt || attempt.finishedAt !== void 0) {
|
|
5162
|
-
attempt = { key:
|
|
5230
|
+
attempt = { key: `${kind}-recharge:${id7}:${String(row2.disputeId)}:${now}`, startedAt: now };
|
|
5163
5231
|
await db.transact(
|
|
5164
|
-
[membershipUpdate(
|
|
5165
|
-
{ mutationId: `membership-recharge-attempt:${attempt.key}`, guards: [{
|
|
5166
|
-
ns: "networkMembership",
|
|
5167
|
-
id: membershipId,
|
|
5168
|
-
exists: true,
|
|
5169
|
-
equals: { revision: member.revision, status: MEMBERSHIP_DISPUTED_STATUS }
|
|
5170
|
-
}] }
|
|
5232
|
+
[membershipUpdate(ns, id7, { rechargeAttempt: attempt, revision: Number(row2.revision) + 1, updatedAt: now })],
|
|
5233
|
+
{ mutationId: `membership-recharge-attempt:${attempt.key}`, guards: [{ ns, id: id7, exists: true, equals: { revision: row2.revision, status: MEMBERSHIP_DISPUTED_STATUS } }] }
|
|
5171
5234
|
);
|
|
5172
|
-
|
|
5173
|
-
if (!
|
|
5235
|
+
row2 = await membershipLoad(db, ns, id7);
|
|
5236
|
+
if (!row2 || row2.status !== MEMBERSHIP_DISPUTED_STATUS) fail6("membership_dispute_changed");
|
|
5174
5237
|
}
|
|
5175
5238
|
const config = { secretKey };
|
|
5176
|
-
const paymentMethod = await defaultPaymentMethod(config, String(
|
|
5239
|
+
const paymentMethod = await defaultPaymentMethod(config, String(row2.providerSubscriptionId), String(row2.providerCustomerId));
|
|
5177
5240
|
if (!paymentMethod) fail6("recharge_payment_method_unavailable");
|
|
5178
5241
|
const intent = await stripeRequest7(config, "POST", "/v1/payment_intents", {
|
|
5179
5242
|
amount: amountCents,
|
|
5180
5243
|
currency,
|
|
5181
|
-
customer: String(
|
|
5244
|
+
customer: String(row2.providerCustomerId),
|
|
5182
5245
|
payment_method: paymentMethod,
|
|
5183
5246
|
off_session: "true",
|
|
5184
5247
|
confirm: "true",
|
|
5185
|
-
description:
|
|
5248
|
+
description: `${kind === "seat" ? "Named seat" : "Membership"} payment reissued after dispute ${String(row2.disputeId)}`,
|
|
5186
5249
|
metadata: {
|
|
5187
|
-
...stripePartitionMetadata({ appId: String(
|
|
5188
|
-
applicationId: String(
|
|
5250
|
+
...stripePartitionMetadata({ appId: String(row2.appId), chapterId: scope2.chapterId, environment: scope2.environment, runtime: String(row2.runtime) }),
|
|
5251
|
+
applicationId: String(row2.applicationId),
|
|
5189
5252
|
membershipId,
|
|
5190
|
-
|
|
5253
|
+
...kind === "seat" ? { seatId: id7 } : {},
|
|
5254
|
+
disputeId: String(row2.disputeId),
|
|
5191
5255
|
membershipRecharge: "true"
|
|
5192
5256
|
}
|
|
5193
5257
|
}, { idempotencyKey: attempt.key });
|
|
5194
5258
|
const body = intent.body, error = body.error ?? {}, paymentStatus = String(body.status ?? "");
|
|
5195
5259
|
const finish = async (outcome) => {
|
|
5196
|
-
const current = await membershipLoad(db,
|
|
5260
|
+
const current = await membershipLoad(db, ns, id7);
|
|
5197
5261
|
if (!current) return;
|
|
5198
|
-
await db.transact([membershipUpdate(
|
|
5262
|
+
await db.transact([membershipUpdate(ns, id7, {
|
|
5199
5263
|
rechargeAttempt: { ...attempt, finishedAt: now, outcome },
|
|
5200
5264
|
revision: Number(current.revision) + 1,
|
|
5201
5265
|
updatedAt: now
|
|
@@ -5213,15 +5277,15 @@ async function rechargeDisputedMembership(db, membershipId, secretKey, now) {
|
|
|
5213
5277
|
fail6("recharge_declined", { declineCode: String(body.last_payment_error?.decline_code ?? paymentStatus) });
|
|
5214
5278
|
}
|
|
5215
5279
|
if (paymentStatus !== "succeeded") fail6("recharge_pending", { paymentStatus });
|
|
5216
|
-
if (refOf2(body.customer) !==
|
|
5280
|
+
if (refOf2(body.customer) !== row2.providerCustomerId || body.amount !== amountCents) fail6("recharge_mismatch");
|
|
5217
5281
|
for (let retry = 0; retry < 8; retry++) {
|
|
5218
|
-
const state = await loadMembershipAuthorityState(db, scope2), current = await membershipLoad(db,
|
|
5282
|
+
const state = await loadMembershipAuthorityState(db, scope2), current = await membershipLoad(db, ns, id7);
|
|
5219
5283
|
if (!current) fail6("membership_not_found");
|
|
5220
5284
|
if (current.rechargePaymentIntentId === body.id && current.status !== MEMBERSHIP_DISPUTED_STATUS) return done(current, true);
|
|
5221
|
-
const status2 = String(current.disputePriorStatus ?? (current.journeyApprovedAt ? "active" : "paid_pending_approval"));
|
|
5285
|
+
const status2 = String(current.disputePriorStatus ?? (kind === "seat" ? "active" : current.journeyApprovedAt ? "active" : "paid_pending_approval"));
|
|
5222
5286
|
try {
|
|
5223
5287
|
await db.transact(
|
|
5224
|
-
[membershipUpdate(
|
|
5288
|
+
[membershipUpdate(ns, id7, {
|
|
5225
5289
|
status: status2,
|
|
5226
5290
|
disputeStatus: "recharged",
|
|
5227
5291
|
rechargePaymentIntentId: String(body.id),
|
|
@@ -5231,7 +5295,7 @@ async function rechargeDisputedMembership(db, membershipId, secretKey, now) {
|
|
|
5231
5295
|
updatedAt: now
|
|
5232
5296
|
})],
|
|
5233
5297
|
{ mutationId: `membership-recharged:${attempt.key}`, guards: [
|
|
5234
|
-
{ ns
|
|
5298
|
+
{ ns, id: id7, exists: true, equals: { revision: current.revision, status: MEMBERSHIP_DISPUTED_STATUS } },
|
|
5235
5299
|
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } }
|
|
5236
5300
|
] }
|
|
5237
5301
|
);
|
|
@@ -5239,10 +5303,12 @@ async function rechargeDisputedMembership(db, membershipId, secretKey, now) {
|
|
|
5239
5303
|
if (cause.code !== "transact_guard_failed") throw cause;
|
|
5240
5304
|
continue;
|
|
5241
5305
|
}
|
|
5242
|
-
return done(await membershipLoad(db,
|
|
5306
|
+
return done(await membershipLoad(db, ns, id7));
|
|
5243
5307
|
}
|
|
5244
5308
|
return fail6("membership_dispute_retry_required");
|
|
5245
5309
|
}
|
|
5310
|
+
var rechargeDisputedMembership = (db, membershipId, secretKey, now) => rechargeDisputed(db, "membership", membershipId, secretKey, now);
|
|
5311
|
+
var rechargeDisputedSeat = (db, seatId, secretKey, now) => rechargeDisputed(db, "seat", seatId, secretKey, now);
|
|
5246
5312
|
|
|
5247
5313
|
// src/worker-routes-admin-membership-disputes.ts
|
|
5248
5314
|
var LIST2 = "/api/admin/membership-disputes";
|
|
@@ -5259,6 +5325,9 @@ var PUBLIC2 = [
|
|
|
5259
5325
|
"memberNumber",
|
|
5260
5326
|
"amountCents",
|
|
5261
5327
|
"currency",
|
|
5328
|
+
"recipientName",
|
|
5329
|
+
"primaryMembershipId",
|
|
5330
|
+
"purchasedAt",
|
|
5262
5331
|
"disputeId",
|
|
5263
5332
|
"disputeStatus",
|
|
5264
5333
|
"disputeReason",
|
|
@@ -5306,14 +5375,17 @@ var handleAdminMembershipDisputes = async (req, url, env, ctx) => {
|
|
|
5306
5375
|
return json({ error: "the membership authority is not hosted by this deployment" }, 404);
|
|
5307
5376
|
}
|
|
5308
5377
|
if (!one) {
|
|
5309
|
-
const
|
|
5378
|
+
const page = { $: { where: { environment: env.ODLA_ENV }, order: { updatedAt: "desc" }, limit: 500 } };
|
|
5379
|
+
const found = await db.query({ networkMembership: page, networkSeat: page });
|
|
5380
|
+
const memberships = found.networkMembership ?? [], seats = found.networkSeat ?? [];
|
|
5381
|
+
const rows5 = [...memberships.map((row2) => ({ ...project2(row2), kind: "membership" })), ...seats.map((row2) => ({ ...project2(row2), kind: "seat" }))];
|
|
5310
5382
|
const disputes = rows5.filter((row2) => typeof row2.disputeId === "string").sort((a, b) => Number(b.disputedAt ?? 0) - Number(a.disputedAt ?? 0));
|
|
5311
|
-
return json({ disputes
|
|
5383
|
+
return json({ disputes, truncated: memberships.length >= 500 || seats.length >= 500 });
|
|
5312
5384
|
}
|
|
5313
5385
|
const secret = await getVaultSecret(db, operator.stripeSecretName ?? "stripe_secret_key");
|
|
5314
5386
|
if (!secret) return json({ error: "network payment provider unavailable" }, 503);
|
|
5315
5387
|
try {
|
|
5316
|
-
const receipt = await rechargeDisputedMembership(db, one[1], secret, Date.now());
|
|
5388
|
+
const receipt = await (one[1].startsWith("seat_") ? rechargeDisputedSeat : rechargeDisputedMembership)(db, one[1], secret, Date.now());
|
|
5317
5389
|
return json({ ok: true, ...receipt });
|
|
5318
5390
|
} catch (error) {
|
|
5319
5391
|
const failure = error;
|
|
@@ -5333,13 +5405,13 @@ var handleAdminMembershipDisputes = async (req, url, env, ctx) => {
|
|
|
5333
5405
|
|
|
5334
5406
|
// src/membership-policy-versions.ts
|
|
5335
5407
|
var LIMIT = 200;
|
|
5336
|
-
var
|
|
5408
|
+
var text4 = (value) => typeof value === "string" && value.length > 0 && value.length <= 200 ? value : void 0;
|
|
5337
5409
|
var instant = (value) => {
|
|
5338
5410
|
const at2 = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
|
|
5339
5411
|
return Number.isSafeInteger(at2) && at2 >= 0 ? at2 : void 0;
|
|
5340
5412
|
};
|
|
5341
5413
|
function provenance(row2) {
|
|
5342
|
-
const source =
|
|
5414
|
+
const source = text4(row2.source), actor = text4(row2.actor ?? row2.$createdBy), recordedAt = instant(row2.recordedAt ?? row2.createdAt ?? row2.$createdAt);
|
|
5343
5415
|
return { ...source ? { source } : {}, ...actor ? { actor } : {}, ...recordedAt !== void 0 ? { recordedAt } : {} };
|
|
5344
5416
|
}
|
|
5345
5417
|
var live = (w, now) => w.from <= now && (w.until === void 0 || now < w.until);
|
|
@@ -6340,8 +6412,8 @@ var APPLICATION_LABELS = {
|
|
|
6340
6412
|
state: "State"
|
|
6341
6413
|
};
|
|
6342
6414
|
function humanize(id7) {
|
|
6343
|
-
const
|
|
6344
|
-
return
|
|
6415
|
+
const text10 = id7.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
6416
|
+
return text10 ? `${text10[0].toUpperCase()}${text10.slice(1)}` : id7;
|
|
6345
6417
|
}
|
|
6346
6418
|
function applicationDefs(chapter) {
|
|
6347
6419
|
try {
|
|
@@ -6408,7 +6480,7 @@ var ID3 = /^[a-z][a-z0-9-]{1,79}$/;
|
|
|
6408
6480
|
var PRICE = /^price_[A-Za-z0-9]{12,}$/;
|
|
6409
6481
|
var CURRENCY = /^[a-z]{3}$/;
|
|
6410
6482
|
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
6411
|
-
var
|
|
6483
|
+
var text5 = (value, path, errors, max) => {
|
|
6412
6484
|
if (typeof value !== "string" || !value.trim()) {
|
|
6413
6485
|
errors.push(`${path} is required`);
|
|
6414
6486
|
return "";
|
|
@@ -6436,23 +6508,23 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
6436
6508
|
}
|
|
6437
6509
|
const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
|
|
6438
6510
|
for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
6439
|
-
const id7 =
|
|
6511
|
+
const id7 = text5(item.id, `${path}.id`, errors, 80);
|
|
6440
6512
|
if (id7 && !ID3.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
6441
6513
|
if (ids2.has(id7)) errors.push(`${path}.id duplicates "${id7}"`);
|
|
6442
6514
|
ids2.add(id7);
|
|
6443
6515
|
const targetRaw = record(item.target);
|
|
6444
6516
|
let target = { type: "entitlement", entitlementKey: "invalid" };
|
|
6445
6517
|
if (targetRaw?.type === "tier") {
|
|
6446
|
-
const tierId =
|
|
6518
|
+
const tierId = text5(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
|
|
6447
6519
|
if (!tierIds.has(tierId)) errors.push(`${path}.target.tierId must reference a declared tier`);
|
|
6448
6520
|
target = { type: "tier", tierId };
|
|
6449
6521
|
} else if (targetRaw?.type === "entitlement") {
|
|
6450
|
-
const entitlementKey =
|
|
6522
|
+
const entitlementKey = text5(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
|
|
6451
6523
|
if (entitlementKey && !ID3.test(entitlementKey)) errors.push(`${path}.target.entitlementKey must be a lowercase kebab-case id`);
|
|
6452
6524
|
target = { type: "entitlement", entitlementKey };
|
|
6453
6525
|
} else errors.push(`${path}.target.type must be tier or entitlement`);
|
|
6454
6526
|
if (item.billingMode !== "one_time" && item.billingMode !== "subscription") errors.push(`${path}.billingMode must be one_time or subscription`);
|
|
6455
|
-
const stripePriceId =
|
|
6527
|
+
const stripePriceId = text5(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
|
|
6456
6528
|
if (stripePriceId && !PRICE.test(stripePriceId)) errors.push(`${path}.stripePriceId must be a real Stripe Price id`);
|
|
6457
6529
|
if (!Number.isSafeInteger(item.priceCents) || Number(item.priceCents) < 1 || Number(item.priceCents) > 1e8) errors.push(`${path}.priceCents must be an integer from 1 to 100,000,000`);
|
|
6458
6530
|
const currency = typeof item.currency === "string" ? item.currency.trim().toLowerCase() : "";
|
|
@@ -6474,9 +6546,9 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
6474
6546
|
redemptionLifetimeHours: lifetime,
|
|
6475
6547
|
...activeFrom !== void 0 ? { activeFrom } : {},
|
|
6476
6548
|
...activeUntil !== void 0 ? { activeUntil } : {},
|
|
6477
|
-
title:
|
|
6478
|
-
description:
|
|
6479
|
-
ctaLabel:
|
|
6549
|
+
title: text5(item.title, `${path}.title`, errors, 160),
|
|
6550
|
+
description: text5(item.description, `${path}.description`, errors, 2e3),
|
|
6551
|
+
ctaLabel: text5(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
|
|
6480
6552
|
});
|
|
6481
6553
|
}
|
|
6482
6554
|
return errors.length ? { ok: false, errors } : { ok: true, value: { schemaVersion: 1, offers } };
|
|
@@ -6892,17 +6964,17 @@ async function readBoundedRequestText(req, maxBytes) {
|
|
|
6892
6964
|
if (!reader) return "";
|
|
6893
6965
|
const decoder = new TextDecoder();
|
|
6894
6966
|
let bytes = 0;
|
|
6895
|
-
let
|
|
6967
|
+
let text10 = "";
|
|
6896
6968
|
for (; ; ) {
|
|
6897
6969
|
const { done, value } = await reader.read();
|
|
6898
6970
|
if (req.signal.aborted) return invalid2();
|
|
6899
6971
|
if (done) {
|
|
6900
6972
|
complete = true;
|
|
6901
|
-
return
|
|
6973
|
+
return text10 + decoder.decode();
|
|
6902
6974
|
}
|
|
6903
6975
|
bytes += value.byteLength;
|
|
6904
6976
|
if (bytes > maxBytes) return json({ error: "request body too large" }, 413);
|
|
6905
|
-
|
|
6977
|
+
text10 += decoder.decode(value, { stream: true });
|
|
6906
6978
|
}
|
|
6907
6979
|
} catch {
|
|
6908
6980
|
return invalid2();
|
|
@@ -7006,9 +7078,9 @@ async function paymentQuoteRoute(url, env, ctx, prepare = false, request) {
|
|
|
7006
7078
|
let selection;
|
|
7007
7079
|
if (request?.body) {
|
|
7008
7080
|
try {
|
|
7009
|
-
const
|
|
7010
|
-
if (
|
|
7011
|
-
const body = JSON.parse(
|
|
7081
|
+
const text10 = await readBoundedRequestText(request, 2048);
|
|
7082
|
+
if (text10 instanceof Response) return text10;
|
|
7083
|
+
const body = JSON.parse(text10);
|
|
7012
7084
|
if (!body || typeof body !== "object" || Array.isArray(body) || Object.keys(body).some((key3) => key3 !== "namedSeat")) throw new Error("invalid body");
|
|
7013
7085
|
selection = body.namedSeat;
|
|
7014
7086
|
} catch {
|
|
@@ -7890,10 +7962,10 @@ function boundedString(value, path, errors, max, required = false) {
|
|
|
7890
7962
|
errors.push(`${path} must be a string`);
|
|
7891
7963
|
return void 0;
|
|
7892
7964
|
}
|
|
7893
|
-
const
|
|
7894
|
-
if (required && !
|
|
7895
|
-
if (
|
|
7896
|
-
return
|
|
7965
|
+
const text10 = value.trim();
|
|
7966
|
+
if (required && !text10) errors.push(`${path} is required`);
|
|
7967
|
+
if (text10.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
|
|
7968
|
+
return text10;
|
|
7897
7969
|
}
|
|
7898
7970
|
function validateMembershipPageDocument(input, options = {}) {
|
|
7899
7971
|
const errors = [];
|
|
@@ -7942,8 +8014,8 @@ function validateMembershipPageDocument(input, options = {}) {
|
|
|
7942
8014
|
else {
|
|
7943
8015
|
if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);
|
|
7944
8016
|
benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {
|
|
7945
|
-
const
|
|
7946
|
-
return
|
|
8017
|
+
const text10 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
|
|
8018
|
+
return text10 ? [text10] : [];
|
|
7947
8019
|
});
|
|
7948
8020
|
}
|
|
7949
8021
|
}
|
|
@@ -8037,7 +8109,7 @@ var recordOf = (value) => value !== null && typeof value === "object" && !Array.
|
|
|
8037
8109
|
function unknownKeys(value, allowed2, path, errors) {
|
|
8038
8110
|
for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
8039
8111
|
}
|
|
8040
|
-
function
|
|
8112
|
+
function text6(value, path, errors, max = 65536) {
|
|
8041
8113
|
if (typeof value !== "string" || !value.trim()) {
|
|
8042
8114
|
errors.push(`${path} is required`);
|
|
8043
8115
|
return "";
|
|
@@ -8060,11 +8132,11 @@ function tiersOf(value, errors) {
|
|
|
8060
8132
|
return [];
|
|
8061
8133
|
}
|
|
8062
8134
|
unknownKeys(item, TIER_KEYS2, path, errors);
|
|
8063
|
-
const id7 =
|
|
8135
|
+
const id7 = text6(item.id, `${path}.id`, errors, 80);
|
|
8064
8136
|
if (id7 && !TIER.test(id7)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
8065
8137
|
if (seen.has(id7)) errors.push(`${path}.id duplicates "${id7}"`);
|
|
8066
8138
|
seen.add(id7);
|
|
8067
|
-
const name =
|
|
8139
|
+
const name = text6(item.name, `${path}.name`, errors, 160);
|
|
8068
8140
|
if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
|
|
8069
8141
|
if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
|
|
8070
8142
|
if (!Number.isInteger(item.priceCents) || Number(item.priceCents) < 0 || Number(item.priceCents) > 1e8) {
|
|
@@ -8082,7 +8154,7 @@ function tiersOf(value, errors) {
|
|
|
8082
8154
|
if (item.interval !== void 0 && item.interval !== "year" && item.interval !== "month") {
|
|
8083
8155
|
errors.push(`${path}.interval must be year or month`);
|
|
8084
8156
|
}
|
|
8085
|
-
const blurb = item.blurb === void 0 ? void 0 :
|
|
8157
|
+
const blurb = item.blurb === void 0 ? void 0 : text6(item.blurb, `${path}.blurb`, errors, 4e3);
|
|
8086
8158
|
return [{
|
|
8087
8159
|
id: id7,
|
|
8088
8160
|
name,
|
|
@@ -8140,7 +8212,7 @@ function policyOf(value, errors, tiers) {
|
|
|
8140
8212
|
const result = {};
|
|
8141
8213
|
for (const key3 of allowed2) {
|
|
8142
8214
|
if (input[key3] === void 0 && key3 !== "disclaimerText" && key3 !== "refundPolicyText") continue;
|
|
8143
|
-
result[key3] =
|
|
8215
|
+
result[key3] = text6(input[key3], `document.policy.${key3}`, errors);
|
|
8144
8216
|
}
|
|
8145
8217
|
const tierPolicies = {};
|
|
8146
8218
|
if (input.tierPolicies !== void 0) {
|
|
@@ -8155,8 +8227,8 @@ function policyOf(value, errors, tiers) {
|
|
|
8155
8227
|
}
|
|
8156
8228
|
unknownKeys(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
|
|
8157
8229
|
tierPolicies[id7] = {
|
|
8158
|
-
refundPolicyText:
|
|
8159
|
-
...policy2.trustCopy !== void 0 ? { trustCopy:
|
|
8230
|
+
refundPolicyText: text6(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
|
|
8231
|
+
...policy2.trustCopy !== void 0 ? { trustCopy: text6(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
|
|
8160
8232
|
};
|
|
8161
8233
|
}
|
|
8162
8234
|
}
|
|
@@ -8196,9 +8268,9 @@ function validateSignupControlDocument(input) {
|
|
|
8196
8268
|
if (!Number.isSafeInteger(root.generatedAt) || Number(root.generatedAt) < 1) errors.push("document.generatedAt must be epoch milliseconds");
|
|
8197
8269
|
const source = recordOf(root.source);
|
|
8198
8270
|
const target = recordOf(root.target);
|
|
8199
|
-
const appId =
|
|
8200
|
-
const chapterId =
|
|
8201
|
-
const environment =
|
|
8271
|
+
const appId = text6(source?.appId, "document.source.appId", errors, 63);
|
|
8272
|
+
const chapterId = text6(target?.chapterId, "document.target.chapterId", errors, 63);
|
|
8273
|
+
const environment = text6(target?.environment, "document.target.environment", errors, 63);
|
|
8202
8274
|
if (appId && !SLUG.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
|
|
8203
8275
|
if (chapterId && !SLUG.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
|
|
8204
8276
|
if (environment && !ENVIRONMENT.test(environment)) errors.push("document.target.environment is invalid");
|
|
@@ -10469,7 +10541,7 @@ async function startSubscription(req, env, ctx) {
|
|
|
10469
10541
|
|
|
10470
10542
|
// src/worker-payment-receipt.ts
|
|
10471
10543
|
var json2 = (value, status2 = 200) => Response.json(value, { status: status2, headers: { "cache-control": "no-store", "referrer-policy": "no-referrer" } });
|
|
10472
|
-
var escape = (
|
|
10544
|
+
var escape = (text10) => text10.replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]);
|
|
10473
10545
|
async function paymentReceiptRoute(req, url, env, ctx) {
|
|
10474
10546
|
const id7 = url.searchParams.get("application");
|
|
10475
10547
|
if (!id7 || id7.length > 160) return json2({ error: "application required" }, 400);
|
|
@@ -10480,7 +10552,7 @@ async function paymentReceiptRoute(req, url, env, ctx) {
|
|
|
10480
10552
|
if (!app || app.groupId !== ctx.chapter.id || !intent || intent.applicationId !== id7 || intent.appId !== env.ODLA_APP_ID || intent.chapterId !== ctx.chapter.id || intent.environment !== env.ODLA_ENV || intent.runtime !== stripeRuntimeIdentity(env.ODLA_RUNTIME) || app.stripeRuntime !== intent.runtime || app.stripeSubscriptionId !== intent.providerSubscriptionId) return json2({ error: "receipt not found" }, 404);
|
|
10481
10553
|
const receipt = intent.paymentReceipt;
|
|
10482
10554
|
if (!receipt) return json2({ pending: true }, 202);
|
|
10483
|
-
const
|
|
10555
|
+
const text10 = paymentReceiptText(receipt, ctx.chapter.name);
|
|
10484
10556
|
if (req.method === "POST") {
|
|
10485
10557
|
if (!env.SEND_EMAIL || !env.EMAIL_FROM) return json2({ error: "Email is unavailable. You can print or save your receipt." }, 503);
|
|
10486
10558
|
const group = (await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups?.[0];
|
|
@@ -10498,7 +10570,7 @@ async function paymentReceiptRoute(req, url, env, ctx) {
|
|
|
10498
10570
|
template: "paymentReceipt",
|
|
10499
10571
|
to: String(intent.purchaserEmailNormalized),
|
|
10500
10572
|
applicationId: id7,
|
|
10501
|
-
vars: { chapterName: ctx.chapter.name, receiptText:
|
|
10573
|
+
vars: { chapterName: ctx.chapter.name, receiptText: text10 },
|
|
10502
10574
|
dedupeKey: `requested-payment-receipt:${receipt.invoiceId}`,
|
|
10503
10575
|
event: { label: "Payment receipt", trigger: "Purchaser requests their receipt", audience: "Purchaser", variables: ["chapterName", "receiptText"] }
|
|
10504
10576
|
});
|
|
@@ -10508,7 +10580,7 @@ async function paymentReceiptRoute(req, url, env, ctx) {
|
|
|
10508
10580
|
const nonce = crypto.randomUUID();
|
|
10509
10581
|
return new Response(`<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Payment receipt</title>
|
|
10510
10582
|
<style>body{font:16px/1.6 system-ui;max-width:680px;margin:48px auto;padding:24px;color:#17252d}pre{font:inherit;white-space:pre-wrap}button{font:inherit;padding:10px 16px}@media print{button{display:none}body{margin:0}}</style>
|
|
10511
|
-
<h1>Payment receipt</h1><pre>${escape(
|
|
10583
|
+
<h1>Payment receipt</h1><pre>${escape(text10)}</pre><button id="print">Print / save PDF</button>
|
|
10512
10584
|
<script nonce="${nonce}">document.getElementById('print').addEventListener('click',()=>window.print())</script></html>`, {
|
|
10513
10585
|
headers: {
|
|
10514
10586
|
"content-type": "text/html; charset=utf-8",
|
|
@@ -10978,16 +11050,16 @@ var handleNamedSeat = async (req, url, env, ctx) => {
|
|
|
10978
11050
|
};
|
|
10979
11051
|
|
|
10980
11052
|
// src/named-seat-accepted-notice.ts
|
|
10981
|
-
var
|
|
11053
|
+
var text7 = (value) => typeof value === "string" ? value : "";
|
|
10982
11054
|
async function notifyNamedSeatAccepted(db, env, ctx, input) {
|
|
10983
11055
|
const purchaser = await membershipLoad(db, "applications", input.primaryApplicationId);
|
|
10984
|
-
if (!purchaser || !chapterBillingApplication(purchaser, ctx.chapter.id, env.ODLA_RUNTIME) || !
|
|
11056
|
+
if (!purchaser || !chapterBillingApplication(purchaser, ctx.chapter.id, env.ODLA_RUNTIME) || !text7(purchaser.email)) {
|
|
10985
11057
|
return { sent: false, reason: "purchaser-missing" };
|
|
10986
11058
|
}
|
|
10987
11059
|
const group = await membershipLoad(db, "groups", ctx.chapter.id);
|
|
10988
11060
|
if (!group) return { sent: false, reason: "group-missing" };
|
|
10989
11061
|
const config = emailGroupFrom(group);
|
|
10990
|
-
const purchaserName = [purchaser.firstName, purchaser.lastName].map(
|
|
11062
|
+
const purchaserName = [purchaser.firstName, purchaser.lastName].map(text7).join(" ").replace(/\s+/g, " ").trim();
|
|
10991
11063
|
return sendTemplated({
|
|
10992
11064
|
db,
|
|
10993
11065
|
envName: env.ODLA_ENV,
|
|
@@ -10999,12 +11071,12 @@ async function notifyNamedSeatAccepted(db, env, ctx, input) {
|
|
|
10999
11071
|
}, {
|
|
11000
11072
|
group: config,
|
|
11001
11073
|
template: "seatAccepted",
|
|
11002
|
-
to:
|
|
11074
|
+
to: text7(purchaser.email),
|
|
11003
11075
|
dedupeKey: `named-seat-accepted:${input.seatId}`,
|
|
11004
11076
|
applicationId: input.primaryApplicationId,
|
|
11005
11077
|
tierId: input.skuId,
|
|
11006
11078
|
vars: {
|
|
11007
|
-
firstName:
|
|
11079
|
+
firstName: text7(purchaser.firstName),
|
|
11008
11080
|
purchaserName,
|
|
11009
11081
|
recipientName: input.recipientName,
|
|
11010
11082
|
tierName: await tierName(db, group, input.skuId),
|
|
@@ -12471,12 +12543,12 @@ async function updateEmailSettings(db, ctx, env, body) {
|
|
|
12471
12543
|
if (!value || typeof value !== "object" || Array.isArray(value)) return json({ error: `Invalid template: ${key3}` }, 400);
|
|
12472
12544
|
const t = value;
|
|
12473
12545
|
if (typeof t.enabled !== "boolean") return json({ error: `Choose whether ${key3} is enabled.` }, 400);
|
|
12474
|
-
const subject = str2(t.subject).trim(),
|
|
12475
|
-
const issues = validateEmailCopy4({ subject, text:
|
|
12546
|
+
const subject = str2(t.subject).trim(), text10 = str2(t.text);
|
|
12547
|
+
const issues = validateEmailCopy4({ subject, text: text10 }, { allowedVariables: events[key3].variables });
|
|
12476
12548
|
if (t.enabled && issues.length || issues.some((issue) => ["subject-invalid", "body-invalid"].includes(issue.code))) {
|
|
12477
12549
|
return json({ error: `${events[key3].label} is incomplete.`, errors: issues.map((issue) => issue.message) }, 400);
|
|
12478
12550
|
}
|
|
12479
|
-
clean[key3] = { subject, text:
|
|
12551
|
+
clean[key3] = { subject, text: text10, enabled: t.enabled };
|
|
12480
12552
|
}
|
|
12481
12553
|
const notificationEmail = str2(body.notificationEmail).trim(), replyTo = str2(body.replyTo).trim(), debugEmail = str2(body.debugEmail).trim();
|
|
12482
12554
|
const emailish = /^[^\s@,;<>]+@[^\s@,;<>]+\.[^\s@,;<>]+$/;
|
|
@@ -14689,9 +14761,9 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
|
|
|
14689
14761
|
if (req.method === "POST") {
|
|
14690
14762
|
let body;
|
|
14691
14763
|
try {
|
|
14692
|
-
const
|
|
14693
|
-
if (new TextEncoder().encode(
|
|
14694
|
-
const parsed = JSON.parse(
|
|
14764
|
+
const text10 = await req.text();
|
|
14765
|
+
if (new TextEncoder().encode(text10).byteLength > MAX_BODY2) return json({ error: "request is too large" }, 413);
|
|
14766
|
+
const parsed = JSON.parse(text10);
|
|
14695
14767
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("invalid");
|
|
14696
14768
|
body = parsed;
|
|
14697
14769
|
} catch {
|
|
@@ -14860,7 +14932,7 @@ import { signFederatedRequest as signFederatedRequest7, verifyFederatedRequest a
|
|
|
14860
14932
|
// src/network-commercial-parity.ts
|
|
14861
14933
|
var DIGEST = /^[0-9a-f]{64}$/;
|
|
14862
14934
|
var MAX_RECORDS = 500;
|
|
14863
|
-
var
|
|
14935
|
+
var text8 = (value, max = 255) => typeof value === "string" && value.length > 0 && value.length <= max ? value : null;
|
|
14864
14936
|
function parityMessage(partition, kind, value) {
|
|
14865
14937
|
return [
|
|
14866
14938
|
"chapter-commercial-parity",
|
|
@@ -14892,18 +14964,18 @@ async function digestWithKey(key3, partition, kind, value) {
|
|
|
14892
14964
|
async function buildChapterCommercialParitySnapshot(input) {
|
|
14893
14965
|
const requestedMax = input.maxRecords ?? MAX_RECORDS;
|
|
14894
14966
|
const maxRecords = Number.isSafeInteger(requestedMax) ? Math.max(0, Math.min(requestedMax, MAX_RECORDS)) : MAX_RECORDS;
|
|
14895
|
-
const eligible = input.rows.filter((row2) =>
|
|
14967
|
+
const eligible = input.rows.filter((row2) => text8(row2.id) && (text8(row2.stripeCustomerId) || text8(row2.stripeSubscriptionId)));
|
|
14896
14968
|
const selected2 = eligible.slice(0, maxRecords);
|
|
14897
14969
|
const key3 = await parityKey(input.secret);
|
|
14898
14970
|
const records = await Promise.all(selected2.map(async (row2) => {
|
|
14899
|
-
const applicationId =
|
|
14900
|
-
const customerId =
|
|
14901
|
-
const subscriptionId =
|
|
14971
|
+
const applicationId = text8(row2.id);
|
|
14972
|
+
const customerId = text8(row2.stripeCustomerId);
|
|
14973
|
+
const subscriptionId = text8(row2.stripeSubscriptionId);
|
|
14902
14974
|
return {
|
|
14903
14975
|
applicationDigest: await digestWithKey(key3, input.partition, "application", applicationId),
|
|
14904
14976
|
customerDigest: customerId ? await digestWithKey(key3, input.partition, "customer", customerId) : null,
|
|
14905
14977
|
subscriptionDigest: subscriptionId ? await digestWithKey(key3, input.partition, "subscription", subscriptionId) : null,
|
|
14906
|
-
status:
|
|
14978
|
+
status: text8(row2.status, 64),
|
|
14907
14979
|
canceled: row2.canceled === true
|
|
14908
14980
|
};
|
|
14909
14981
|
}));
|
|
@@ -14929,7 +15001,7 @@ function validSnapshot(snapshot, source) {
|
|
|
14929
15001
|
if (snapshot?.version !== 1 || snapshot.site?.id !== source.chapterId || !partition || !Array.isArray(records) || records.length > MAX_RECORDS || partition.appId !== source.appId || partition.chapterId !== source.chapterId || partition.environment !== source.environment || partition.dataEnvironment !== source.dataEnvironment || partition.runtime !== source.runtime || typeof snapshot.complete !== "boolean" || typeof snapshot.truncated !== "boolean" || !snapshot.counts) return false;
|
|
14930
15002
|
const seen = /* @__PURE__ */ new Set();
|
|
14931
15003
|
for (const record4 of records) {
|
|
14932
|
-
if (!DIGEST.test(record4.applicationDigest) || record4.customerDigest !== null && !DIGEST.test(record4.customerDigest) || record4.subscriptionDigest !== null && !DIGEST.test(record4.subscriptionDigest) || record4.status !== null &&
|
|
15004
|
+
if (!DIGEST.test(record4.applicationDigest) || record4.customerDigest !== null && !DIGEST.test(record4.customerDigest) || record4.subscriptionDigest !== null && !DIGEST.test(record4.subscriptionDigest) || record4.status !== null && text8(record4.status, 64) === null || typeof record4.canceled !== "boolean") return false;
|
|
14933
15005
|
const key3 = recordKey(record4);
|
|
14934
15006
|
if (seen.has(key3)) return false;
|
|
14935
15007
|
seen.add(key3);
|
|
@@ -14940,16 +15012,16 @@ async function reconcileCommercialParitySnapshot(input) {
|
|
|
14940
15012
|
if (!validSnapshot(input.snapshot, input.source)) throw new Error("invalid chapter commercial parity snapshot");
|
|
14941
15013
|
const sourceKey = networkCommercialSourceKey(input.source);
|
|
14942
15014
|
const key3 = await parityKey(input.secret);
|
|
14943
|
-
const shadow = await Promise.all(input.projection.subscriptions.filter((row2) => row2.sourceKey === sourceKey &&
|
|
14944
|
-
const customerId =
|
|
14945
|
-
const subscriptionId =
|
|
15015
|
+
const shadow = await Promise.all(input.projection.subscriptions.filter((row2) => row2.sourceKey === sourceKey && text8(row2.applicationId) && text8(row2.objectId)).map(async (row2) => {
|
|
15016
|
+
const customerId = text8(row2.customerId);
|
|
15017
|
+
const subscriptionId = text8(row2.subscriptionId) ?? text8(row2.objectId);
|
|
14946
15018
|
return {
|
|
14947
15019
|
applicationDigest: await digestWithKey(key3, input.snapshot.partition, "application", row2.applicationId),
|
|
14948
15020
|
customerDigest: customerId ? await digestWithKey(key3, input.snapshot.partition, "customer", customerId) : null,
|
|
14949
15021
|
subscriptionDigest: subscriptionId ? await digestWithKey(key3, input.snapshot.partition, "subscription", subscriptionId) : null,
|
|
14950
15022
|
status: null,
|
|
14951
15023
|
canceled: false,
|
|
14952
|
-
stripeStatus:
|
|
15024
|
+
stripeStatus: text8(row2.status, 64),
|
|
14953
15025
|
stripeCanceled: row2.status === "canceled"
|
|
14954
15026
|
};
|
|
14955
15027
|
}));
|
|
@@ -15159,18 +15231,24 @@ async function applyHostedMembershipDispute(env, ctx, fact, now) {
|
|
|
15159
15231
|
const secret = await getVaultSecret(db, operator.stripeSecretName ?? "stripe_secret_key");
|
|
15160
15232
|
if (!secret) throw Object.assign(new Error("membership_dispute_provider_unavailable"), { code: "membership_dispute_provider_unavailable" });
|
|
15161
15233
|
const charge = await readDisputedCharge(secret, fact.chargeId);
|
|
15234
|
+
const scopeOf = (row2) => ({
|
|
15235
|
+
appId: String(row2.appId),
|
|
15236
|
+
chapterId: String(row2.chapterId),
|
|
15237
|
+
environment: String(row2.environment),
|
|
15238
|
+
runtime: String(row2.runtime),
|
|
15239
|
+
applicationId: String(row2.applicationId),
|
|
15240
|
+
skuId: String(row2.skuId)
|
|
15241
|
+
});
|
|
15242
|
+
if (charge.seatId) {
|
|
15243
|
+
const seat = await membershipLoad(db, "networkSeat", charge.seatId);
|
|
15244
|
+
if (!seat || seat.environment !== env.ODLA_ENV || !disputedChargeCoversSeat(charge, seat)) return { disposition: "not_applicable" };
|
|
15245
|
+
const receipt2 = await reconcileMembershipDispute(db, { ...scopeOf(seat), eventId: fact.eventId }, now, secret, charge);
|
|
15246
|
+
return { disposition: "applied", membershipId: receipt2.membershipId, seatId: charge.seatId };
|
|
15247
|
+
}
|
|
15162
15248
|
const rows5 = (await db.query({ networkMembership: { $: { where: { providerCustomerId: charge.customerId, environment: env.ODLA_ENV }, limit: 5 } } })).networkMembership ?? [];
|
|
15163
15249
|
const member = rows5.find((row2) => disputedChargeCoversMembership(charge, row2));
|
|
15164
15250
|
if (!member) return { disposition: "not_applicable" };
|
|
15165
|
-
const
|
|
15166
|
-
appId: String(member.appId),
|
|
15167
|
-
chapterId: String(member.chapterId),
|
|
15168
|
-
environment: String(member.environment),
|
|
15169
|
-
runtime: String(member.runtime),
|
|
15170
|
-
applicationId: String(member.applicationId),
|
|
15171
|
-
skuId: String(member.skuId)
|
|
15172
|
-
};
|
|
15173
|
-
const receipt = await reconcileMembershipDispute(db, { ...target, eventId: fact.eventId }, now, secret, charge);
|
|
15251
|
+
const receipt = await reconcileMembershipDispute(db, { ...scopeOf(member), eventId: fact.eventId }, now, secret, charge);
|
|
15174
15252
|
return { disposition: "applied", membershipId: receipt.membershipId };
|
|
15175
15253
|
}
|
|
15176
15254
|
|
|
@@ -15238,11 +15316,11 @@ function safeProjection(projection) {
|
|
|
15238
15316
|
amountReceivedCents: seat.amountReceivedCents,
|
|
15239
15317
|
refundedCents: seat.refundedCents ?? 0
|
|
15240
15318
|
})),
|
|
15241
|
-
disputes: (projection.disputes ?? []).map((
|
|
15242
|
-
...row2(
|
|
15243
|
-
chargeId:
|
|
15244
|
-
paymentIntentId:
|
|
15245
|
-
reason:
|
|
15319
|
+
disputes: (projection.disputes ?? []).map((dispute2) => ({
|
|
15320
|
+
...row2(dispute2),
|
|
15321
|
+
chargeId: dispute2.chargeId ?? null,
|
|
15322
|
+
paymentIntentId: dispute2.paymentIntentId ?? null,
|
|
15323
|
+
reason: dispute2.disputeReason ?? null
|
|
15246
15324
|
})),
|
|
15247
15325
|
exceptions: projection.exceptions
|
|
15248
15326
|
};
|
|
@@ -15276,10 +15354,10 @@ var handleNetworkCommercialStripeWebhook = async (req, url, env, ctx) => {
|
|
|
15276
15354
|
if (result.status === "collision") {
|
|
15277
15355
|
console.error("network commercial event collision", JSON.stringify({ eventId: fact.eventId, eventType: fact.eventType }));
|
|
15278
15356
|
}
|
|
15279
|
-
let
|
|
15357
|
+
let dispute2;
|
|
15280
15358
|
if (fact.objectKind === "dispute" && result.status !== "collision") {
|
|
15281
15359
|
try {
|
|
15282
|
-
|
|
15360
|
+
dispute2 = await applyHostedMembershipDispute(env, ctx, fact, Date.now());
|
|
15283
15361
|
} catch (error) {
|
|
15284
15362
|
console.warn("membership dispute pending", JSON.stringify({ eventId: fact.eventId, code: error.code ?? "unknown" }));
|
|
15285
15363
|
return json({ error: "membership dispute reconciliation pending", eventId: fact.eventId }, 503);
|
|
@@ -15289,7 +15367,7 @@ var handleNetworkCommercialStripeWebhook = async (req, url, env, ctx) => {
|
|
|
15289
15367
|
ok: true,
|
|
15290
15368
|
status: result.status,
|
|
15291
15369
|
eventId: fact.eventId,
|
|
15292
|
-
...
|
|
15370
|
+
...dispute2 ? { dispute: dispute2.disposition } : {},
|
|
15293
15371
|
...result.status === "replayed" && result.resolvedCollision ? { resolvedCollision: true } : {}
|
|
15294
15372
|
});
|
|
15295
15373
|
} catch (error) {
|
|
@@ -15487,7 +15565,7 @@ var record3 = (value) => value !== null && typeof value === "object" && !Array.i
|
|
|
15487
15565
|
function unknownKeys3(value, allowed2, path, errors) {
|
|
15488
15566
|
for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
15489
15567
|
}
|
|
15490
|
-
function
|
|
15568
|
+
function text9(value, path, errors, max = MAX_TEXT2, required = true) {
|
|
15491
15569
|
if (value === void 0 && !required) return void 0;
|
|
15492
15570
|
if (typeof value !== "string" || required && !value.trim()) {
|
|
15493
15571
|
errors.push(`${path} must be a non-empty string`);
|
|
@@ -15513,7 +15591,7 @@ function scopeValue(value, path, errors) {
|
|
|
15513
15591
|
return { kind: raw2.kind };
|
|
15514
15592
|
}
|
|
15515
15593
|
if (raw2.kind === "chapter") {
|
|
15516
|
-
const chapterId =
|
|
15594
|
+
const chapterId = text9(raw2.chapterId, `${path}.chapterId`, errors, 63) ?? "";
|
|
15517
15595
|
if (chapterId && !APP_ID.test(chapterId)) errors.push(`${path}.chapterId must be a lowercase app slug`);
|
|
15518
15596
|
return { kind: "chapter", chapterId };
|
|
15519
15597
|
}
|
|
@@ -15527,22 +15605,22 @@ function resourceValue(value, path, errors) {
|
|
|
15527
15605
|
return null;
|
|
15528
15606
|
}
|
|
15529
15607
|
unknownKeys3(raw2, RESOURCE_KEYS, path, errors);
|
|
15530
|
-
const id7 =
|
|
15608
|
+
const id7 = text9(raw2.id, `${path}.id`, errors, 80) ?? "";
|
|
15531
15609
|
if (id7 && !ID5.test(id7)) errors.push(`${path}.id must be lowercase kebab-case`);
|
|
15532
|
-
const kind =
|
|
15610
|
+
const kind = text9(raw2.kind, `${path}.kind`, errors) ?? "";
|
|
15533
15611
|
if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
|
|
15534
|
-
const title =
|
|
15535
|
-
const summary =
|
|
15536
|
-
const status2 =
|
|
15612
|
+
const title = text9(raw2.title, `${path}.title`, errors, 240) ?? "";
|
|
15613
|
+
const summary = text9(raw2.summary, `${path}.summary`, errors, 1e3, false);
|
|
15614
|
+
const status2 = text9(raw2.status, `${path}.status`, errors) ?? "";
|
|
15537
15615
|
if (!STATUSES.has(status2)) errors.push(`${path}.status must be draft, published, or archived`);
|
|
15538
15616
|
const revision = integer4(raw2.revision, `${path}.revision`, errors);
|
|
15539
15617
|
const scope2 = scopeValue(raw2.scope, `${path}.scope`, errors) ?? { kind: "global" };
|
|
15540
|
-
const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) =>
|
|
15618
|
+
const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) => text9(tag, `${path}.tags[${index2}]`, errors, 80) ?? "") : [];
|
|
15541
15619
|
if (!Array.isArray(raw2.tags)) errors.push(`${path}.tags must be an array`);
|
|
15542
15620
|
if (tags.length > MAX_TAGS) errors.push(`${path}.tags may contain at most ${MAX_TAGS} items`);
|
|
15543
15621
|
if (new Set(tags).size !== tags.length) errors.push(`${path}.tags must be unique`);
|
|
15544
|
-
const bodyMarkdown =
|
|
15545
|
-
const url =
|
|
15622
|
+
const bodyMarkdown = text9(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN3, false);
|
|
15623
|
+
const url = text9(raw2.url, `${path}.url`, errors, 2048, false);
|
|
15546
15624
|
if (url) {
|
|
15547
15625
|
try {
|
|
15548
15626
|
if (new URL(url).protocol !== "https:") errors.push(`${path}.url must use https`);
|
|
@@ -15554,9 +15632,9 @@ function resourceValue(value, path, errors) {
|
|
|
15554
15632
|
if (raw2.brandAsset !== void 0 && !asset) errors.push(`${path}.brandAsset must be an object`);
|
|
15555
15633
|
if (asset) unknownKeys3(asset, ASSET_KEYS, `${path}.brandAsset`, errors);
|
|
15556
15634
|
const brandAsset = asset ? {
|
|
15557
|
-
bookId:
|
|
15558
|
-
assetId:
|
|
15559
|
-
...asset.filename === void 0 ? {} : { filename:
|
|
15635
|
+
bookId: text9(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
|
|
15636
|
+
assetId: text9(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
|
|
15637
|
+
...asset.filename === void 0 ? {} : { filename: text9(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
|
|
15560
15638
|
} : void 0;
|
|
15561
15639
|
if ((kind === "document" || kind === "note") && !bodyMarkdown) errors.push(`${path}.bodyMarkdown is required for ${kind}`);
|
|
15562
15640
|
if ((kind === "link" || kind === "inspiration") && !url) errors.push(`${path}.url is required for ${kind}`);
|
|
@@ -15571,17 +15649,17 @@ function resourceValue(value, path, errors) {
|
|
|
15571
15649
|
if (provenanceRaw?.promotedFrom !== void 0 && !promotedRaw) errors.push(`${path}.provenance.promotedFrom must be an object`);
|
|
15572
15650
|
if (promotedRaw) unknownKeys3(promotedRaw, PROMOTION_KEYS, `${path}.provenance.promotedFrom`, errors);
|
|
15573
15651
|
const promotedFrom = promotedRaw ? {
|
|
15574
|
-
chapterId:
|
|
15575
|
-
resourceId:
|
|
15652
|
+
chapterId: text9(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
|
|
15653
|
+
resourceId: text9(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
|
|
15576
15654
|
resourceRevision: integer4(promotedRaw.resourceRevision, `${path}.provenance.promotedFrom.resourceRevision`, errors),
|
|
15577
15655
|
documentRevision: integer4(promotedRaw.documentRevision, `${path}.provenance.promotedFrom.documentRevision`, errors),
|
|
15578
|
-
promotedBy:
|
|
15656
|
+
promotedBy: text9(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
|
|
15579
15657
|
promotedAt: integer4(promotedRaw.promotedAt, `${path}.provenance.promotedFrom.promotedAt`, errors, 0)
|
|
15580
15658
|
} : void 0;
|
|
15581
15659
|
const provenance2 = {
|
|
15582
|
-
createdBy:
|
|
15660
|
+
createdBy: text9(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
|
|
15583
15661
|
createdAt: integer4(provenanceRaw?.createdAt, `${path}.provenance.createdAt`, errors, 0),
|
|
15584
|
-
updatedBy:
|
|
15662
|
+
updatedBy: text9(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
|
|
15585
15663
|
updatedAt: integer4(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
|
|
15586
15664
|
...promotedFrom ? { promotedFrom } : {}
|
|
15587
15665
|
};
|
|
@@ -15600,7 +15678,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
|
|
|
15600
15678
|
if (sourceRaw) unknownKeys3(sourceRaw, SOURCE_KEYS2, "document.source", errors);
|
|
15601
15679
|
const sourceType = sourceRaw?.type;
|
|
15602
15680
|
if (sourceType !== "chapter" && sourceType !== "built_not_found") errors.push("document.source.type must be chapter or built_not_found");
|
|
15603
|
-
const sourceAppId =
|
|
15681
|
+
const sourceAppId = text9(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
|
|
15604
15682
|
if (sourceAppId && !APP_ID.test(sourceAppId)) errors.push("document.source.appId must be a lowercase app slug");
|
|
15605
15683
|
if (options.sourceType && sourceType !== options.sourceType) errors.push(`document.source.type must be ${options.sourceType}`);
|
|
15606
15684
|
if (options.sourceAppId && sourceAppId !== options.sourceAppId) errors.push(`document.source.appId must be ${options.sourceAppId}`);
|
|
@@ -15608,8 +15686,8 @@ function validateBuilderLibraryDocument(value, options = {}) {
|
|
|
15608
15686
|
if (raw2.target !== void 0 && !targetRaw) errors.push("document.target must be an object");
|
|
15609
15687
|
if (targetRaw) unknownKeys3(targetRaw, TARGET_KEYS2, "document.target", errors);
|
|
15610
15688
|
const target = targetRaw ? {
|
|
15611
|
-
appId:
|
|
15612
|
-
environment:
|
|
15689
|
+
appId: text9(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
|
|
15690
|
+
environment: text9(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
|
|
15613
15691
|
} : void 0;
|
|
15614
15692
|
if (options.targetAppId && target?.appId !== options.targetAppId) errors.push(`document.target.appId must be ${options.targetAppId}`);
|
|
15615
15693
|
if (!Array.isArray(raw2.resources)) errors.push("document.resources must be an array");
|
|
@@ -17134,6 +17212,19 @@ var id5 = () => attr("string", false, true, true);
|
|
|
17134
17212
|
var key = (optional = false) => attr("string", optional, false, true);
|
|
17135
17213
|
var scope = () => ({ appId: key(), chapterId: key(), environment: key(), runtime: key(), applicationId: key(), skuId: key() });
|
|
17136
17214
|
var times = () => ({ revision: attr("number"), createdAt: attr("number"), updatedAt: attr("number") });
|
|
17215
|
+
var dispute = () => ({
|
|
17216
|
+
disputeId: key(true),
|
|
17217
|
+
disputeStatus: attr("string", true),
|
|
17218
|
+
disputeReason: attr("string", true),
|
|
17219
|
+
disputeAmountCents: attr("number", true),
|
|
17220
|
+
disputedAt: attr("number", true),
|
|
17221
|
+
disputeClosedAt: attr("number", true),
|
|
17222
|
+
disputePriorStatus: attr("string", true),
|
|
17223
|
+
disputeEventId: key(true),
|
|
17224
|
+
rechargePaymentIntentId: key(true),
|
|
17225
|
+
rechargedAt: attr("number", true),
|
|
17226
|
+
rechargeAttempt: attr("json", true)
|
|
17227
|
+
});
|
|
17137
17228
|
var membershipAuthorityEntities = {
|
|
17138
17229
|
networkMembership: { attrs: {
|
|
17139
17230
|
id: id5(),
|
|
@@ -17174,19 +17265,7 @@ var membershipAuthorityEntities = {
|
|
|
17174
17265
|
...times(),
|
|
17175
17266
|
refundRequestedAt: attr("number", true),
|
|
17176
17267
|
restartId: key(true),
|
|
17177
|
-
|
|
17178
|
-
// the status the membership held before it was suspended, and the re-issued charge once an operator collected again.
|
|
17179
|
-
disputeId: key(true),
|
|
17180
|
-
disputeStatus: attr("string", true),
|
|
17181
|
-
disputeReason: attr("string", true),
|
|
17182
|
-
disputeAmountCents: attr("number", true),
|
|
17183
|
-
disputedAt: attr("number", true),
|
|
17184
|
-
disputeClosedAt: attr("number", true),
|
|
17185
|
-
disputePriorStatus: attr("string", true),
|
|
17186
|
-
disputeEventId: key(true),
|
|
17187
|
-
rechargePaymentIntentId: key(true),
|
|
17188
|
-
rechargedAt: attr("number", true),
|
|
17189
|
-
rechargeAttempt: attr("json", true)
|
|
17268
|
+
...dispute()
|
|
17190
17269
|
} },
|
|
17191
17270
|
membershipRestart: { attrs: {
|
|
17192
17271
|
id: id5(),
|
|
@@ -17262,7 +17341,8 @@ var membershipAuthorityEntities = {
|
|
|
17262
17341
|
refundedCents: attr("number", true),
|
|
17263
17342
|
providerRefundIds: attr("json", true),
|
|
17264
17343
|
regiftedAt: attr("number", true),
|
|
17265
|
-
formerRecipients: attr("json", true)
|
|
17344
|
+
formerRecipients: attr("json", true),
|
|
17345
|
+
...dispute()
|
|
17266
17346
|
} },
|
|
17267
17347
|
networkSeatRecipient: { attrs: { id: id5(), appId: key(), chapterId: key(), environment: key(), runtime: key(), seatId: key(), recipientEmailNormalized: key(), createdAt: attr("number") } },
|
|
17268
17348
|
membershipEffect: { attrs: {
|
|
@@ -17599,7 +17679,7 @@ async function readBoundedJson(response, signal, maxBytes, label) {
|
|
|
17599
17679
|
};
|
|
17600
17680
|
signal.addEventListener("abort", cancel, { once: true });
|
|
17601
17681
|
let size = 0;
|
|
17602
|
-
let
|
|
17682
|
+
let text10 = "";
|
|
17603
17683
|
const decoder = new TextDecoder();
|
|
17604
17684
|
try {
|
|
17605
17685
|
while (true) {
|
|
@@ -17609,10 +17689,10 @@ async function readBoundedJson(response, signal, maxBytes, label) {
|
|
|
17609
17689
|
if (done) break;
|
|
17610
17690
|
size += value.byteLength;
|
|
17611
17691
|
if (size > maxBytes) throw new TypeError(`${label} exceeds limit`);
|
|
17612
|
-
|
|
17692
|
+
text10 += decoder.decode(value, { stream: true });
|
|
17613
17693
|
}
|
|
17614
17694
|
try {
|
|
17615
|
-
return JSON.parse(
|
|
17695
|
+
return JSON.parse(text10 + decoder.decode());
|
|
17616
17696
|
} catch {
|
|
17617
17697
|
throw new TypeError(`invalid ${label}`);
|
|
17618
17698
|
}
|
|
@@ -18390,6 +18470,7 @@ export {
|
|
|
18390
18470
|
deliverMemberContent,
|
|
18391
18471
|
deliverMembershipChapterEffect,
|
|
18392
18472
|
deliverSignupControl,
|
|
18473
|
+
disputedChargeCoversSeat,
|
|
18393
18474
|
drainMembershipEffects,
|
|
18394
18475
|
grantIncludedNamedSeat,
|
|
18395
18476
|
liftSeatPurchaseWindow,
|
|
@@ -18401,7 +18482,9 @@ export {
|
|
|
18401
18482
|
readDisputedCharge,
|
|
18402
18483
|
readNamedSeatOffer,
|
|
18403
18484
|
rechargeDisputedMembership,
|
|
18485
|
+
rechargeDisputedSeat,
|
|
18404
18486
|
reconcileMembershipDispute,
|
|
18487
|
+
reconcileNamedSeatDispute,
|
|
18405
18488
|
refundNamedSeatByOperator,
|
|
18406
18489
|
regiftNamedSeat,
|
|
18407
18490
|
releaseNamedSeatRecipientApplications,
|