@odla-ai/chapter 0.58.1 → 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 +66 -0
- package/dist/{chunk-FHKZVF5G.js → chunk-GADLTNJF.js} +4 -2
- package/dist/{chunk-FHKZVF5G.js.map → chunk-GADLTNJF.js.map} +1 -1
- package/dist/{chunk-L2IPS2FQ.js → chunk-P6X3W2XP.js} +147 -56
- package/dist/chunk-P6X3W2XP.js.map +1 -0
- package/dist/{chunk-CKC6X5H2.js → chunk-SDGVPIXF.js} +401 -309
- package/dist/chunk-SDGVPIXF.js.map +1 -0
- package/dist/{copy-context-DE4YMAbz.d.ts → copy-context-BuJBQafl.d.ts} +4 -0
- package/dist/index.cjs +285 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +281 -81
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +40 -3
- package/dist/ui/admin/index.js +6 -2
- package/dist/ui/index.d.ts +3 -3
- package/dist/ui/index.js +15 -3
- package/dist/ui/member/index.d.ts +34 -6
- package/dist/ui/member/index.js +10 -2
- package/dist/worker/index.cjs +694 -184
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +173 -3
- package/dist/worker/index.d.ts +173 -3
- package/dist/worker/index.js +687 -177
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-CKC6X5H2.js.map +0 -1
- package/dist/chunk-L2IPS2FQ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1560,7 +1560,9 @@ var DEFAULT_JOIN_COPY = {
|
|
|
1560
1560
|
failed: "The booking could not be completed.",
|
|
1561
1561
|
loading: "Loading available times\u2026",
|
|
1562
1562
|
book: "Book this time",
|
|
1563
|
-
booking: "Booking\u2026"
|
|
1563
|
+
booking: "Booking\u2026",
|
|
1564
|
+
pickDate: "Pick a date",
|
|
1565
|
+
pickTime: "Pick a time"
|
|
1564
1566
|
},
|
|
1565
1567
|
payment: {
|
|
1566
1568
|
setupFailed: "Payment could not be set up. Please try again.",
|
|
@@ -1712,8 +1714,8 @@ var APPLICATION_LABELS = {
|
|
|
1712
1714
|
state: "State"
|
|
1713
1715
|
};
|
|
1714
1716
|
function humanize(id11) {
|
|
1715
|
-
const
|
|
1716
|
-
return
|
|
1717
|
+
const text8 = id11.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
1718
|
+
return text8 ? `${text8[0].toUpperCase()}${text8.slice(1)}` : id11;
|
|
1717
1719
|
}
|
|
1718
1720
|
function applicationDefs(chapter) {
|
|
1719
1721
|
try {
|
|
@@ -2512,6 +2514,7 @@ function normalizeNetworkCommercialStripeEvent(input, digest) {
|
|
|
2512
2514
|
else if (eventType.startsWith("refund.")) objectKind = "refund";
|
|
2513
2515
|
else if (eventType === "charge.refunded") objectKind = "refund";
|
|
2514
2516
|
else if (eventType.startsWith("payment_intent.")) objectKind = "payment_intent";
|
|
2517
|
+
else if (eventType.startsWith("charge.dispute.")) objectKind = "dispute";
|
|
2515
2518
|
const customerId = refId(object2.customer, "cus_");
|
|
2516
2519
|
const subscriptionId = objectKind === "subscription" ? providerId(objectId, "sub_") : subscriptionRef(object2);
|
|
2517
2520
|
const invoiceId = objectKind === "invoice" ? providerId(objectId, "in_") : refId(object2.invoice, "in_");
|
|
@@ -2521,6 +2524,7 @@ function normalizeNetworkCommercialStripeEvent(input, digest) {
|
|
|
2521
2524
|
const amountCents = integer(eventType === "charge.refunded" ? object2.amount_refunded : object2.amount);
|
|
2522
2525
|
const currency = text(object2.currency)?.toLowerCase();
|
|
2523
2526
|
const status2 = text(object2.status);
|
|
2527
|
+
const disputeReason = objectKind === "dispute" ? text(object2.reason) : void 0;
|
|
2524
2528
|
return {
|
|
2525
2529
|
eventId,
|
|
2526
2530
|
digest,
|
|
@@ -2539,6 +2543,7 @@ function normalizeNetworkCommercialStripeEvent(input, digest) {
|
|
|
2539
2543
|
...invoiceId ? { invoiceId } : {},
|
|
2540
2544
|
...chargeId ? { chargeId } : {},
|
|
2541
2545
|
...paymentIntentId ? { paymentIntentId } : {},
|
|
2546
|
+
...disputeReason ? { disputeReason } : {},
|
|
2542
2547
|
...price,
|
|
2543
2548
|
...objectKind === "subscription" && commercialSeatItems(object2) ? { seatSubscriptionItems: commercialSeatItems(object2) } : {},
|
|
2544
2549
|
...status2 ? { status: status2 } : {},
|
|
@@ -2581,6 +2586,7 @@ function ownRef(fact) {
|
|
|
2581
2586
|
if (fact.objectKind === "subscription") return `subscription:${fact.objectId}`;
|
|
2582
2587
|
if (fact.objectKind === "invoice") return `invoice:${fact.objectId}`;
|
|
2583
2588
|
if (fact.objectKind === "payment_intent") return `payment_intent:${fact.objectId}`;
|
|
2589
|
+
if (fact.objectKind === "dispute") return `dispute:${fact.objectId}`;
|
|
2584
2590
|
if (fact.eventType.startsWith("refund.")) return `refund:${fact.objectId}`;
|
|
2585
2591
|
return fact.eventType === "charge.refunded" ? `charge:${fact.objectId}` : null;
|
|
2586
2592
|
}
|
|
@@ -2636,6 +2642,7 @@ function projectNetworkCommercialEvents(input, sources) {
|
|
|
2636
2642
|
continue;
|
|
2637
2643
|
}
|
|
2638
2644
|
const source = assignments.get(fact.eventId);
|
|
2645
|
+
if (!source && fact.objectKind === "dispute") continue;
|
|
2639
2646
|
if (!source) {
|
|
2640
2647
|
const direct = directSource(fact, sources);
|
|
2641
2648
|
const keys2 = new Set(objectRefs(fact).flatMap((ref) => [...refs.get(ref) ?? []]));
|
|
@@ -2652,6 +2659,7 @@ function projectNetworkCommercialEvents(input, sources) {
|
|
|
2652
2659
|
const subscriptions = rows3.filter((row2) => row2.objectKind === "subscription");
|
|
2653
2660
|
const invoices = rows3.filter((row2) => row2.objectKind === "invoice");
|
|
2654
2661
|
const seatPayments = rows3.filter((row2) => row2.objectKind === "payment_intent" && row2.seatId && row2.membershipId && !row2.invoiceId);
|
|
2662
|
+
const disputes = rows3.filter((row2) => row2.objectKind === "dispute");
|
|
2655
2663
|
const refunds = rows3.filter((row2) => row2.objectKind === "refund" && row2.eventType.startsWith("refund."));
|
|
2656
2664
|
for (const charge of rows3.filter((row2) => row2.eventType === "charge.refunded")) {
|
|
2657
2665
|
if (!refunds.some((refund) => refund.chargeId === charge.objectId)) {
|
|
@@ -2672,6 +2680,7 @@ function projectNetworkCommercialEvents(input, sources) {
|
|
|
2672
2680
|
for (const row2 of refunds) add(row2, "refundedCents", row2.amountCents ?? 0);
|
|
2673
2681
|
return {
|
|
2674
2682
|
...seatPayments.length ? { seatPayments, seats: projectCommercialSeats(seatPayments, subscriptions, refunds) } : {},
|
|
2683
|
+
...disputes.length ? { disputes } : {},
|
|
2675
2684
|
customers,
|
|
2676
2685
|
subscriptions,
|
|
2677
2686
|
invoices,
|
|
@@ -4275,7 +4284,7 @@ async function refundNetworkMembership(db, input, secretKey, now) {
|
|
|
4275
4284
|
}
|
|
4276
4285
|
|
|
4277
4286
|
// src/membership-provider-lifecycle.ts
|
|
4278
|
-
import { stripeRequest as
|
|
4287
|
+
import { stripeRequest as stripeRequest5 } from "@odla-ai/stripe";
|
|
4279
4288
|
|
|
4280
4289
|
// src/membership-seat-reservation.ts
|
|
4281
4290
|
async function namedSeatRecipientClaimId(input, email) {
|
|
@@ -4373,6 +4382,9 @@ function namedSeatRecipientState(seat) {
|
|
|
4373
4382
|
return seat.refundReason === "declined" ? "declined" : "refunded";
|
|
4374
4383
|
case "canceled":
|
|
4375
4384
|
return "canceled";
|
|
4385
|
+
// A card-network dispute on the seat's own payment pauses the recipient until it is won or re-charged.
|
|
4386
|
+
case "payment_disputed":
|
|
4387
|
+
return "disputed";
|
|
4376
4388
|
default:
|
|
4377
4389
|
return "payment_pending";
|
|
4378
4390
|
}
|
|
@@ -4566,12 +4578,174 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
4566
4578
|
return membershipFail("membership_renewal_retry_required");
|
|
4567
4579
|
}
|
|
4568
4580
|
|
|
4581
|
+
// src/membership-dispute-authority.ts
|
|
4582
|
+
import { stripeRequest as stripeRequest4 } from "@odla-ai/stripe";
|
|
4583
|
+
|
|
4584
|
+
// src/membership-dispute-transition.ts
|
|
4585
|
+
var MEMBERSHIP_DISPUTED_STATUS = "payment_disputed";
|
|
4586
|
+
var DISPUTE_WON_STATUSES = ["won", "warning_closed"];
|
|
4587
|
+
var CLOSED_STATUSES = [...DISPUTE_WON_STATUSES, "lost"];
|
|
4588
|
+
function disputeTransition(row2, fact, eventId, now, terminal) {
|
|
4589
|
+
const status2 = String(fact.status ?? "unknown"), closed = CLOSED_STATUSES.includes(status2);
|
|
4590
|
+
const sameDispute = row2.disputeId === fact.objectId;
|
|
4591
|
+
if (sameDispute && Number(row2.disputeClosedAt ?? 0) > 0 && !closed) return null;
|
|
4592
|
+
const won = DISPUTE_WON_STATUSES.includes(status2);
|
|
4593
|
+
const suspended = row2.status === MEMBERSHIP_DISPUTED_STATUS, done = terminal.includes(String(row2.status));
|
|
4594
|
+
const restored = String(row2.disputePriorStatus ?? (row2.journeyApprovedAt ? "active" : "paid_pending_approval"));
|
|
4595
|
+
const next = done ? String(row2.status) : won ? suspended ? restored : String(row2.status) : MEMBERSHIP_DISPUTED_STATUS;
|
|
4596
|
+
return {
|
|
4597
|
+
status: next,
|
|
4598
|
+
disputeId: fact.objectId,
|
|
4599
|
+
disputeStatus: status2,
|
|
4600
|
+
disputeEventId: eventId,
|
|
4601
|
+
disputeClosedAt: closed ? now : 0,
|
|
4602
|
+
...fact.disputeReason ? { disputeReason: fact.disputeReason } : {},
|
|
4603
|
+
...fact.amountCents !== void 0 ? { disputeAmountCents: fact.amountCents } : {},
|
|
4604
|
+
...sameDispute ? {} : { disputedAt: now },
|
|
4605
|
+
...next === MEMBERSHIP_DISPUTED_STATUS && !suspended ? { disputePriorStatus: String(row2.status) } : {},
|
|
4606
|
+
revision: Number(row2.revision) + 1,
|
|
4607
|
+
updatedAt: now
|
|
4608
|
+
};
|
|
4609
|
+
}
|
|
4610
|
+
|
|
4611
|
+
// src/membership-seat-dispute.ts
|
|
4612
|
+
var text3 = (value) => typeof value === "string" && value ? value : void 0;
|
|
4613
|
+
var TERMINAL = ["payment_pending", "payment_processing", "payment_canceling", "refund_pending", "refunded", "canceled"];
|
|
4614
|
+
var namedSeatDisputeStanding = (seat, applied) => ({ seatId: String(seat.id), status: String(seat.status), ...text3(seat.disputeStatus) ? { disputeStatus: text3(seat.disputeStatus) } : {}, applied });
|
|
4615
|
+
function disputedChargeCoversSeat(charge, seat) {
|
|
4616
|
+
return charge.seatId === seat.id && charge.customerId === seat.providerCustomerId && charge.paymentIntentId !== void 0 && (charge.paymentIntentId === seat.providerPaymentIntentId || charge.paymentIntentId === seat.rechargePaymentIntentId);
|
|
4617
|
+
}
|
|
4618
|
+
async function reconcileNamedSeatDispute(db, input, now, charge, evidence) {
|
|
4619
|
+
if (!charge.seatId) membershipFail("membership_dispute_scope_mismatch");
|
|
4620
|
+
const membership = async (applied, seat) => {
|
|
4621
|
+
const id11 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id11);
|
|
4622
|
+
requireMembershipScope(member, input);
|
|
4623
|
+
return {
|
|
4624
|
+
membershipId: id11,
|
|
4625
|
+
status: String(member.status),
|
|
4626
|
+
termEndsAt: Number(member.termEndsAt),
|
|
4627
|
+
graceEndsAt: Number(member.graceEndsAt ?? 0),
|
|
4628
|
+
autoRenew: member.autoRenew === true,
|
|
4629
|
+
applied,
|
|
4630
|
+
...text3(member.disputeStatus) ? { disputeStatus: text3(member.disputeStatus) } : {},
|
|
4631
|
+
...seat ? { seat: namedSeatDisputeStanding(seat, applied) } : {}
|
|
4632
|
+
};
|
|
4633
|
+
};
|
|
4634
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
4635
|
+
const state = await loadMembershipAuthorityState(db, input), seat = await membershipLoad(db, "networkSeat", charge.seatId);
|
|
4636
|
+
if (!seat) return membership(false);
|
|
4637
|
+
requireMembershipScope(seat, input);
|
|
4638
|
+
if (charge.customerId !== seat.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
|
|
4639
|
+
if (!disputedChargeCoversSeat(charge, seat) || seat.disputeEventId === input.eventId) return membership(seat.disputeEventId === input.eventId, seat);
|
|
4640
|
+
const attrs = disputeTransition(seat, evidence.fact, input.eventId, now, TERMINAL);
|
|
4641
|
+
if (!attrs) return membership(false, seat);
|
|
4642
|
+
try {
|
|
4643
|
+
await db.transact([membershipUpdate("networkSeat", String(seat.id), attrs)], { mutationId: `seat-dispute:${seat.id}:${input.eventId}`, guards: [
|
|
4644
|
+
{ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision } },
|
|
4645
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
|
|
4646
|
+
{ ns: "networkCommercialEvents", id: String(evidence.row.id), exists: true, equals: { digest: evidence.fact.digest } },
|
|
4647
|
+
evidence.guard
|
|
4648
|
+
] });
|
|
4649
|
+
} catch (error) {
|
|
4650
|
+
if (error.code !== "transact_guard_failed") throw error;
|
|
4651
|
+
continue;
|
|
4652
|
+
}
|
|
4653
|
+
const saved = await membershipLoad(db, "networkSeat", String(seat.id));
|
|
4654
|
+
requireMembershipScope(saved, input);
|
|
4655
|
+
return membership(true, saved);
|
|
4656
|
+
}
|
|
4657
|
+
return membershipFail("membership_dispute_retry_required");
|
|
4658
|
+
}
|
|
4659
|
+
|
|
4660
|
+
// src/membership-dispute-authority.ts
|
|
4661
|
+
var TERMINAL2 = ["refunded", "refund_pending", "canceled", "declined", "restarting", "superseded"];
|
|
4662
|
+
var text4 = (value) => typeof value === "string" && value ? value : void 0;
|
|
4663
|
+
var refOf = (value) => typeof value === "string" ? text4(value) : text4(value?.id);
|
|
4664
|
+
async function readDisputedCharge(secretKey, chargeId, fetch2) {
|
|
4665
|
+
if (!/^ch_[A-Za-z0-9]+$/.test(chargeId)) membershipFail("invalid_dispute_charge");
|
|
4666
|
+
const config = { secretKey, ...fetch2 ? { fetch: fetch2 } : {} };
|
|
4667
|
+
const charge = await stripeRequest4(config, "GET", `/v1/charges/${chargeId}`);
|
|
4668
|
+
if (!charge.ok || charge.body.id !== chargeId) membershipFail("membership_dispute_provider_unavailable");
|
|
4669
|
+
const customerId = refOf(charge.body.customer), invoiceId = refOf(charge.body.invoice), paymentIntentId = refOf(charge.body.payment_intent);
|
|
4670
|
+
if (!customerId) membershipFail("membership_dispute_provider_unavailable");
|
|
4671
|
+
let subscriptionId;
|
|
4672
|
+
if (invoiceId) {
|
|
4673
|
+
const invoice = await stripeRequest4(config, "GET", `/v1/invoices/${invoiceId}`);
|
|
4674
|
+
if (!invoice.ok || invoice.body.id !== invoiceId) membershipFail("membership_dispute_provider_unavailable");
|
|
4675
|
+
subscriptionId = refOf(invoice.body.subscription) ?? refOf(invoice.body.parent?.subscription_details?.subscription);
|
|
4676
|
+
}
|
|
4677
|
+
const seatId = text4(charge.body.metadata?.seatId);
|
|
4678
|
+
return {
|
|
4679
|
+
chargeId,
|
|
4680
|
+
customerId,
|
|
4681
|
+
...invoiceId ? { invoiceId } : {},
|
|
4682
|
+
...subscriptionId ? { subscriptionId } : {},
|
|
4683
|
+
...paymentIntentId ? { paymentIntentId } : {},
|
|
4684
|
+
...seatId ? { seatId } : {}
|
|
4685
|
+
};
|
|
4686
|
+
}
|
|
4687
|
+
function disputedChargeCoversMembership(charge, member) {
|
|
4688
|
+
return charge.customerId === member.providerCustomerId && !charge.seatId && (charge.invoiceId !== void 0 && charge.invoiceId === member.providerInitialInvoiceId || charge.subscriptionId !== void 0 && charge.subscriptionId === member.providerSubscriptionId || charge.paymentIntentId !== void 0 && charge.paymentIntentId === member.rechargePaymentIntentId);
|
|
4689
|
+
}
|
|
4690
|
+
async function reconcileMembershipDispute(db, input, now, secretKey, resolved) {
|
|
4691
|
+
membershipClock(now);
|
|
4692
|
+
const row2 = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
|
|
4693
|
+
const fact = row2?.fact;
|
|
4694
|
+
const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, row2?.digest, "membership_billing_evidence_pending");
|
|
4695
|
+
if (!fact || fact.objectKind !== "dispute" || fact.eventId !== input.eventId || fact.digest !== row2.digest || !fact.chargeId || !fact.objectId || fact.livemode !== (input.environment === "prod")) membershipFail("membership_billing_evidence_pending");
|
|
4696
|
+
const id11 = await networkMembershipId(input);
|
|
4697
|
+
const receipt = (value, applied = value.disputeEventId === input.eventId) => ({
|
|
4698
|
+
membershipId: id11,
|
|
4699
|
+
status: String(value.status),
|
|
4700
|
+
termEndsAt: Number(value.termEndsAt),
|
|
4701
|
+
graceEndsAt: Number(value.graceEndsAt ?? 0),
|
|
4702
|
+
autoRenew: value.autoRenew === true,
|
|
4703
|
+
...text4(value.disputeStatus) ? { disputeStatus: text4(value.disputeStatus) } : {},
|
|
4704
|
+
applied
|
|
4705
|
+
});
|
|
4706
|
+
const known = await membershipLoad(db, "networkMembership", id11);
|
|
4707
|
+
requireMembershipScope(known, input);
|
|
4708
|
+
if (known.disputeEventId === input.eventId) return receipt(known);
|
|
4709
|
+
const seatKnown = known.namedSeatId ? await membershipLoad(db, "networkSeat", String(known.namedSeatId)) : void 0;
|
|
4710
|
+
if (seatKnown?.disputeEventId === input.eventId) return { ...receipt(known, true), seat: namedSeatDisputeStanding(seatKnown, true) };
|
|
4711
|
+
const charge = resolved && resolved.chargeId === fact.chargeId ? resolved : await readDisputedCharge(secretKey, fact.chargeId);
|
|
4712
|
+
if (charge.customerId !== known.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
|
|
4713
|
+
if (charge.seatId) return reconcileNamedSeatDispute(db, input, now, charge, { row: row2, fact, guard: evidenceGuard });
|
|
4714
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
4715
|
+
const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
|
|
4716
|
+
requireMembershipScope(member, input);
|
|
4717
|
+
if (!disputedChargeCoversMembership(charge, member) || member.disputeEventId === input.eventId) return receipt(member);
|
|
4718
|
+
const attrs = disputeTransition(member, fact, input.eventId, now, TERMINAL2);
|
|
4719
|
+
if (!attrs) return receipt(member, false);
|
|
4720
|
+
try {
|
|
4721
|
+
await db.transact([membershipUpdate("networkMembership", id11, attrs)], { mutationId: `membership-dispute:${id11}:${input.eventId}`, guards: [
|
|
4722
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
|
|
4723
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
|
|
4724
|
+
{ ns: "networkCommercialEvents", id: String(row2.id), exists: true, equals: { digest: fact.digest } },
|
|
4725
|
+
evidenceGuard
|
|
4726
|
+
] });
|
|
4727
|
+
} catch (error) {
|
|
4728
|
+
if (error.code !== "transact_guard_failed") throw error;
|
|
4729
|
+
continue;
|
|
4730
|
+
}
|
|
4731
|
+
const saved = await membershipLoad(db, "networkMembership", id11);
|
|
4732
|
+
requireMembershipScope(saved, input);
|
|
4733
|
+
return receipt(saved, true);
|
|
4734
|
+
}
|
|
4735
|
+
return membershipFail("membership_dispute_retry_required");
|
|
4736
|
+
}
|
|
4737
|
+
|
|
4569
4738
|
// src/membership-provider-lifecycle.ts
|
|
4570
4739
|
async function reconcileMembershipBilling(db, input, now, stripeSecretName = "stripe_secret_key") {
|
|
4571
4740
|
membershipClock(now);
|
|
4572
4741
|
const row2 = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
|
|
4573
4742
|
const fact = row2?.fact;
|
|
4574
4743
|
if (fact?.objectKind === "invoice") return reconcileMembershipRenewal(db, input, now);
|
|
4744
|
+
if (fact?.objectKind === "dispute") {
|
|
4745
|
+
const secret = await getVaultSecret(db, stripeSecretName);
|
|
4746
|
+
if (!secret) membershipFail("membership_billing_provider_unavailable");
|
|
4747
|
+
return reconcileMembershipDispute(db, input, now, secret);
|
|
4748
|
+
}
|
|
4575
4749
|
const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, row2?.digest, "membership_billing_evidence_pending");
|
|
4576
4750
|
if (!fact || fact.eventId !== input.eventId || fact.digest !== row2.digest || !/^[a-f0-9]{64}$/.test(fact.digest) || !["customer.subscription.updated", "customer.subscription.deleted", "charge.refunded"].includes(fact.eventType) || fact.livemode !== (input.environment === "prod") || fact.applicationId !== input.applicationId || !fact.identity || Object.entries({
|
|
4577
4751
|
appId: input.appId,
|
|
@@ -4614,7 +4788,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
|
|
|
4614
4788
|
}
|
|
4615
4789
|
if (fact.subscriptionId !== member.providerSubscriptionId || fact.objectKind !== "subscription") membershipFail("membership_billing_scope_mismatch");
|
|
4616
4790
|
if (["refund_pending", "refunded", "declined", "canceled", "restarting"].includes(String(member.status))) return receipt();
|
|
4617
|
-
const result = await
|
|
4791
|
+
const result = await stripeRequest5({ secretKey: sk }, "GET", `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`);
|
|
4618
4792
|
const sub = result.body;
|
|
4619
4793
|
if (!result.ok || sub.id !== member.providerSubscriptionId || sub.customer !== member.providerCustomerId || sub.livemode !== (input.environment === "prod") || typeof sub.cancel_at_period_end !== "boolean" || !["active", "trialing", "past_due", "unpaid", "canceled"].includes(sub.status) || !Object.entries({ ...stripePartitionMetadata(input), applicationId: input.applicationId }).every(([key3, value]) => sub.metadata?.[key3] === value)) {
|
|
4620
4794
|
membershipFail("membership_billing_provider_mismatch");
|
|
@@ -4671,7 +4845,7 @@ async function expireRestartedMembershipSeat(db, target, member, now) {
|
|
|
4671
4845
|
}
|
|
4672
4846
|
|
|
4673
4847
|
// src/membership-restart-authority.ts
|
|
4674
|
-
import { stripeRequest as
|
|
4848
|
+
import { stripeRequest as stripeRequest6 } from "@odla-ai/stripe";
|
|
4675
4849
|
|
|
4676
4850
|
// src/stripe-event-digest.ts
|
|
4677
4851
|
async function stripePayloadDigest(value) {
|
|
@@ -4686,14 +4860,14 @@ function membershipCanRestart(member, now) {
|
|
|
4686
4860
|
}
|
|
4687
4861
|
async function stopPreviousSubscription(member, target, secret) {
|
|
4688
4862
|
const path = `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`;
|
|
4689
|
-
const result = await
|
|
4863
|
+
const result = await stripeRequest6({ secretKey: secret }, "GET", path);
|
|
4690
4864
|
const sub = result.body;
|
|
4691
4865
|
if (!result.ok || sub.id !== member.providerSubscriptionId || sub.customer !== member.providerCustomerId || sub.livemode !== (target.environment === "prod") || !Object.entries({
|
|
4692
4866
|
...stripePartitionMetadata(target),
|
|
4693
4867
|
applicationId: target.applicationId
|
|
4694
4868
|
}).every(([key3, value]) => sub.metadata?.[key3] === value)) membershipFail("membership_restart_provider_mismatch");
|
|
4695
4869
|
if (sub.status === "canceled") return;
|
|
4696
|
-
const canceled = await
|
|
4870
|
+
const canceled = await stripeRequest6(
|
|
4697
4871
|
{ secretKey: secret },
|
|
4698
4872
|
"DELETE",
|
|
4699
4873
|
path,
|
|
@@ -4794,16 +4968,16 @@ async function verifyMembershipAccessReceipt(value, input, now) {
|
|
|
4794
4968
|
}
|
|
4795
4969
|
|
|
4796
4970
|
// src/membership-seat-stripe.ts
|
|
4797
|
-
import { stripeRequest as
|
|
4971
|
+
import { stripeRequest as stripeRequest8 } from "@odla-ai/stripe";
|
|
4798
4972
|
|
|
4799
4973
|
// src/subscription-provider-contract.ts
|
|
4800
|
-
import { stripeRequest as
|
|
4974
|
+
import { stripeRequest as stripeRequest7 } from "@odla-ai/stripe";
|
|
4801
4975
|
function subscriptionContractMismatch() {
|
|
4802
4976
|
throw Object.assign(new Error("Stripe subscription does not match the checkout contract"), { code: "stripe_contract_mismatch" });
|
|
4803
4977
|
}
|
|
4804
4978
|
async function verifySubscriptionPrice(secretKey, input, resume = false) {
|
|
4805
4979
|
if (!/^price_[A-Za-z0-9]+$/.test(input.priceId) || !Number.isSafeInteger(input.priceCents) || input.priceCents <= 0 || !/^[a-z]{3}$/.test(input.currency) || !["month", "year"].includes(input.interval)) subscriptionContractMismatch();
|
|
4806
|
-
const result = await
|
|
4980
|
+
const result = await stripeRequest7({ secretKey }, "GET", `/v1/prices/${encodeURIComponent(input.priceId)}`);
|
|
4807
4981
|
const price = result.body;
|
|
4808
4982
|
if (!result.ok || !price || price.id !== input.priceId || !resume && price.active !== true || price.unit_amount !== input.priceCents || price.currency !== input.currency || price.type !== "recurring" || price.billing_scheme !== "per_unit" || price.custom_unit_amount || price.transform_quantity || price.recurring?.interval !== input.interval || price.recurring?.interval_count !== 1 || price.recurring?.usage_type !== "licensed" || price.livemode !== (input.environment === "prod")) subscriptionContractMismatch();
|
|
4809
4983
|
return typeof price.product === "string" ? price.product : void 0;
|
|
@@ -4821,7 +4995,7 @@ var metadataFor = (seat) => ({
|
|
|
4821
4995
|
});
|
|
4822
4996
|
var matches = (value, expected) => value && Object.entries(expected).every(([key3, item]) => value[key3] === item);
|
|
4823
4997
|
async function read(sk, path, params) {
|
|
4824
|
-
const response = await
|
|
4998
|
+
const response = await stripeRequest8({ secretKey: sk }, "GET", path, params);
|
|
4825
4999
|
if (!response.ok) return reject3();
|
|
4826
5000
|
return response.body;
|
|
4827
5001
|
}
|
|
@@ -4858,11 +5032,11 @@ async function createNamedSeatStripePayment(sk, seat, now) {
|
|
|
4858
5032
|
}
|
|
4859
5033
|
await subscriptionForSeat(sk, seat);
|
|
4860
5034
|
const metadata = metadataFor(seat);
|
|
4861
|
-
const result = seat.providerPaymentIntentId ? await
|
|
5035
|
+
const result = seat.providerPaymentIntentId ? await stripeRequest8(
|
|
4862
5036
|
{ secretKey: sk },
|
|
4863
5037
|
"GET",
|
|
4864
5038
|
`/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`
|
|
4865
|
-
) : await
|
|
5039
|
+
) : await stripeRequest8({ secretKey: sk }, "POST", "/v1/payment_intents", {
|
|
4866
5040
|
amount: seat.amountCents,
|
|
4867
5041
|
currency: seat.currency,
|
|
4868
5042
|
customer: seat.providerCustomerId,
|
|
@@ -4884,7 +5058,7 @@ async function attachNamedSeatStripeRenewal(sk, seat) {
|
|
|
4884
5058
|
return String(existing.id);
|
|
4885
5059
|
}
|
|
4886
5060
|
if (seat.combinedInvoice) reject3();
|
|
4887
|
-
const result = await
|
|
5061
|
+
const result = await stripeRequest8({ secretKey: sk }, "POST", "/v1/subscription_items", {
|
|
4888
5062
|
subscription: seat.providerSubscriptionId,
|
|
4889
5063
|
price: seat.priceId,
|
|
4890
5064
|
quantity: 1,
|
|
@@ -5159,10 +5333,10 @@ async function recordRecipient(db, input, now, approve) {
|
|
|
5159
5333
|
}
|
|
5160
5334
|
|
|
5161
5335
|
// src/membership-seat-refund-stripe.ts
|
|
5162
|
-
import { stripeRequest as
|
|
5336
|
+
import { stripeRequest as stripeRequest10 } from "@odla-ai/stripe";
|
|
5163
5337
|
|
|
5164
5338
|
// src/membership-checkout-seat-refund.ts
|
|
5165
|
-
import { stripeRequest as
|
|
5339
|
+
import { stripeRequest as stripeRequest9 } from "@odla-ai/stripe";
|
|
5166
5340
|
var fail4 = (code = "named_seat_refund_mismatch") => {
|
|
5167
5341
|
throw Object.assign(new Error(code), { code });
|
|
5168
5342
|
};
|
|
@@ -5171,7 +5345,7 @@ async function refundCheckoutNamedSeat(secretKey, seat) {
|
|
|
5171
5345
|
const invoiceRef = seat.combinedInvoice;
|
|
5172
5346
|
if (!invoiceRef || !/^in_[A-Za-z0-9_]+$/.test(invoiceRef.invoiceId) || !Number.isSafeInteger(invoiceRef.totalCents) || invoiceRef.totalCents <= seat.amountCents || seat.amountCents <= 0 || seat.status !== "refund_pending") fail4();
|
|
5173
5347
|
const call = async (method, path, params, key3) => {
|
|
5174
|
-
const result = await
|
|
5348
|
+
const result = await stripeRequest9({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
|
|
5175
5349
|
if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
|
|
5176
5350
|
return result.body;
|
|
5177
5351
|
};
|
|
@@ -5244,7 +5418,7 @@ async function refundNamedSeatStripePayment(secretKey, seat) {
|
|
|
5244
5418
|
if (seat.combinedInvoice) return refundCheckoutNamedSeat(secretKey, seat);
|
|
5245
5419
|
if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail5();
|
|
5246
5420
|
const call = async (method, path, params, idempotencyKey) => {
|
|
5247
|
-
const result = await
|
|
5421
|
+
const result = await stripeRequest10({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
|
|
5248
5422
|
if (!result.ok || !result.body) return fail5("named_seat_refund_provider_unavailable");
|
|
5249
5423
|
return result.body;
|
|
5250
5424
|
};
|
|
@@ -5946,7 +6120,7 @@ async function readBoundedJson(response, signal, maxBytes, label) {
|
|
|
5946
6120
|
};
|
|
5947
6121
|
signal.addEventListener("abort", cancel, { once: true });
|
|
5948
6122
|
let size = 0;
|
|
5949
|
-
let
|
|
6123
|
+
let text8 = "";
|
|
5950
6124
|
const decoder = new TextDecoder();
|
|
5951
6125
|
try {
|
|
5952
6126
|
while (true) {
|
|
@@ -5956,10 +6130,10 @@ async function readBoundedJson(response, signal, maxBytes, label) {
|
|
|
5956
6130
|
if (done) break;
|
|
5957
6131
|
size += value.byteLength;
|
|
5958
6132
|
if (size > maxBytes) throw new TypeError(`${label} exceeds limit`);
|
|
5959
|
-
|
|
6133
|
+
text8 += decoder.decode(value, { stream: true });
|
|
5960
6134
|
}
|
|
5961
6135
|
try {
|
|
5962
|
-
return JSON.parse(
|
|
6136
|
+
return JSON.parse(text8 + decoder.decode());
|
|
5963
6137
|
} catch {
|
|
5964
6138
|
throw new TypeError(`invalid ${label}`);
|
|
5965
6139
|
}
|
|
@@ -6173,7 +6347,7 @@ async function requestMembershipRefund(options) {
|
|
|
6173
6347
|
async function requestMembershipRenewal(options) {
|
|
6174
6348
|
const result = await requestAuthority(options, "renewals", { applicationId: options.applicationId, skuId: options.skuId, eventId: options.eventId });
|
|
6175
6349
|
const row2 = result.renewal;
|
|
6176
|
-
if (!row2 || row2.membershipId !== await networkMembershipId(options) || !Number.isSafeInteger(row2.termEndsAt) || row2.termEndsAt <= 0 || !Number.isSafeInteger(row2.graceEndsAt) || row2.graceEndsAt < 0 || typeof row2.autoRenew !== "boolean" || !["active", "paid_pending_approval", "cancel_at_term_end", "payment_grace", "lapsed", "refund_pending", "refunded", "canceled", "declined", "restarting"].includes(row2.status)) {
|
|
6350
|
+
if (!row2 || row2.membershipId !== await networkMembershipId(options) || !Number.isSafeInteger(row2.termEndsAt) || row2.termEndsAt <= 0 || !Number.isSafeInteger(row2.graceEndsAt) || row2.graceEndsAt < 0 || typeof row2.autoRenew !== "boolean" || !["active", "paid_pending_approval", "cancel_at_term_end", "payment_grace", "lapsed", "refund_pending", "refunded", "canceled", "declined", "restarting", "payment_disputed"].includes(row2.status)) {
|
|
6177
6351
|
throw new Error("invalid membership renewal receipt");
|
|
6178
6352
|
}
|
|
6179
6353
|
return { membershipId: row2.membershipId, status: row2.status, termEndsAt: row2.termEndsAt, graceEndsAt: row2.graceEndsAt, autoRenew: row2.autoRenew };
|
|
@@ -6233,7 +6407,7 @@ async function requestMembershipCancellation(options) {
|
|
|
6233
6407
|
}
|
|
6234
6408
|
|
|
6235
6409
|
// src/membership-seat-cancel.ts
|
|
6236
|
-
import { initStripe, stripeRequest as
|
|
6410
|
+
import { initStripe, stripeRequest as stripeRequest11 } from "@odla-ai/stripe";
|
|
6237
6411
|
async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
|
|
6238
6412
|
membershipClock(now);
|
|
6239
6413
|
const state = await loadMembershipAuthorityState(db, target);
|
|
@@ -6261,7 +6435,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
|
|
|
6261
6435
|
}
|
|
6262
6436
|
const metadata = { ...stripePartitionMetadata(target), applicationId: target.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
|
|
6263
6437
|
const request = async (method, path, params) => {
|
|
6264
|
-
const result = await
|
|
6438
|
+
const result = await stripeRequest11(
|
|
6265
6439
|
{ secretKey },
|
|
6266
6440
|
method,
|
|
6267
6441
|
path,
|
|
@@ -6446,6 +6620,19 @@ var id10 = () => attr9("string", false, true, true);
|
|
|
6446
6620
|
var key2 = (optional = false) => attr9("string", optional, false, true);
|
|
6447
6621
|
var scope = () => ({ appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), applicationId: key2(), skuId: key2() });
|
|
6448
6622
|
var times = () => ({ revision: attr9("number"), createdAt: attr9("number"), updatedAt: attr9("number") });
|
|
6623
|
+
var dispute = () => ({
|
|
6624
|
+
disputeId: key2(true),
|
|
6625
|
+
disputeStatus: attr9("string", true),
|
|
6626
|
+
disputeReason: attr9("string", true),
|
|
6627
|
+
disputeAmountCents: attr9("number", true),
|
|
6628
|
+
disputedAt: attr9("number", true),
|
|
6629
|
+
disputeClosedAt: attr9("number", true),
|
|
6630
|
+
disputePriorStatus: attr9("string", true),
|
|
6631
|
+
disputeEventId: key2(true),
|
|
6632
|
+
rechargePaymentIntentId: key2(true),
|
|
6633
|
+
rechargedAt: attr9("number", true),
|
|
6634
|
+
rechargeAttempt: attr9("json", true)
|
|
6635
|
+
});
|
|
6449
6636
|
var membershipAuthorityEntities = {
|
|
6450
6637
|
networkMembership: { attrs: {
|
|
6451
6638
|
id: id10(),
|
|
@@ -6485,7 +6672,8 @@ var membershipAuthorityEntities = {
|
|
|
6485
6672
|
graceEndsAt: attr9("number", true),
|
|
6486
6673
|
...times(),
|
|
6487
6674
|
refundRequestedAt: attr9("number", true),
|
|
6488
|
-
restartId: key2(true)
|
|
6675
|
+
restartId: key2(true),
|
|
6676
|
+
...dispute()
|
|
6489
6677
|
} },
|
|
6490
6678
|
membershipRestart: { attrs: {
|
|
6491
6679
|
id: id10(),
|
|
@@ -6561,7 +6749,8 @@ var membershipAuthorityEntities = {
|
|
|
6561
6749
|
refundedCents: attr9("number", true),
|
|
6562
6750
|
providerRefundIds: attr9("json", true),
|
|
6563
6751
|
regiftedAt: attr9("number", true),
|
|
6564
|
-
formerRecipients: attr9("json", true)
|
|
6752
|
+
formerRecipients: attr9("json", true),
|
|
6753
|
+
...dispute()
|
|
6565
6754
|
} },
|
|
6566
6755
|
networkSeatRecipient: { attrs: { id: id10(), appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), seatId: key2(), recipientEmailNormalized: key2(), createdAt: attr9("number") } },
|
|
6567
6756
|
membershipEffect: { attrs: {
|
|
@@ -6921,7 +7110,7 @@ var record2 = (value) => value !== null && typeof value === "object" && !Array.i
|
|
|
6921
7110
|
function unknownKeys2(value, allowed2, path, errors) {
|
|
6922
7111
|
for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
6923
7112
|
}
|
|
6924
|
-
function
|
|
7113
|
+
function text5(value, path, errors, max = MAX_TEXT2, required = true) {
|
|
6925
7114
|
if (value === void 0 && !required) return void 0;
|
|
6926
7115
|
if (typeof value !== "string" || required && !value.trim()) {
|
|
6927
7116
|
errors.push(`${path} must be a non-empty string`);
|
|
@@ -6947,7 +7136,7 @@ function scopeValue(value, path, errors) {
|
|
|
6947
7136
|
return { kind: raw2.kind };
|
|
6948
7137
|
}
|
|
6949
7138
|
if (raw2.kind === "chapter") {
|
|
6950
|
-
const chapterId =
|
|
7139
|
+
const chapterId = text5(raw2.chapterId, `${path}.chapterId`, errors, 63) ?? "";
|
|
6951
7140
|
if (chapterId && !APP_ID.test(chapterId)) errors.push(`${path}.chapterId must be a lowercase app slug`);
|
|
6952
7141
|
return { kind: "chapter", chapterId };
|
|
6953
7142
|
}
|
|
@@ -6961,22 +7150,22 @@ function resourceValue(value, path, errors) {
|
|
|
6961
7150
|
return null;
|
|
6962
7151
|
}
|
|
6963
7152
|
unknownKeys2(raw2, RESOURCE_KEYS, path, errors);
|
|
6964
|
-
const id11 =
|
|
7153
|
+
const id11 = text5(raw2.id, `${path}.id`, errors, 80) ?? "";
|
|
6965
7154
|
if (id11 && !ID2.test(id11)) errors.push(`${path}.id must be lowercase kebab-case`);
|
|
6966
|
-
const kind =
|
|
7155
|
+
const kind = text5(raw2.kind, `${path}.kind`, errors) ?? "";
|
|
6967
7156
|
if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
|
|
6968
|
-
const title =
|
|
6969
|
-
const summary =
|
|
6970
|
-
const status2 =
|
|
7157
|
+
const title = text5(raw2.title, `${path}.title`, errors, 240) ?? "";
|
|
7158
|
+
const summary = text5(raw2.summary, `${path}.summary`, errors, 1e3, false);
|
|
7159
|
+
const status2 = text5(raw2.status, `${path}.status`, errors) ?? "";
|
|
6971
7160
|
if (!STATUSES.has(status2)) errors.push(`${path}.status must be draft, published, or archived`);
|
|
6972
7161
|
const revision = integer3(raw2.revision, `${path}.revision`, errors);
|
|
6973
7162
|
const scope2 = scopeValue(raw2.scope, `${path}.scope`, errors) ?? { kind: "global" };
|
|
6974
|
-
const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) =>
|
|
7163
|
+
const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) => text5(tag, `${path}.tags[${index2}]`, errors, 80) ?? "") : [];
|
|
6975
7164
|
if (!Array.isArray(raw2.tags)) errors.push(`${path}.tags must be an array`);
|
|
6976
7165
|
if (tags.length > MAX_TAGS) errors.push(`${path}.tags may contain at most ${MAX_TAGS} items`);
|
|
6977
7166
|
if (new Set(tags).size !== tags.length) errors.push(`${path}.tags must be unique`);
|
|
6978
|
-
const bodyMarkdown =
|
|
6979
|
-
const url =
|
|
7167
|
+
const bodyMarkdown = text5(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN2, false);
|
|
7168
|
+
const url = text5(raw2.url, `${path}.url`, errors, 2048, false);
|
|
6980
7169
|
if (url) {
|
|
6981
7170
|
try {
|
|
6982
7171
|
if (new URL(url).protocol !== "https:") errors.push(`${path}.url must use https`);
|
|
@@ -6988,9 +7177,9 @@ function resourceValue(value, path, errors) {
|
|
|
6988
7177
|
if (raw2.brandAsset !== void 0 && !asset) errors.push(`${path}.brandAsset must be an object`);
|
|
6989
7178
|
if (asset) unknownKeys2(asset, ASSET_KEYS, `${path}.brandAsset`, errors);
|
|
6990
7179
|
const brandAsset = asset ? {
|
|
6991
|
-
bookId:
|
|
6992
|
-
assetId:
|
|
6993
|
-
...asset.filename === void 0 ? {} : { filename:
|
|
7180
|
+
bookId: text5(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
|
|
7181
|
+
assetId: text5(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
|
|
7182
|
+
...asset.filename === void 0 ? {} : { filename: text5(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
|
|
6994
7183
|
} : void 0;
|
|
6995
7184
|
if ((kind === "document" || kind === "note") && !bodyMarkdown) errors.push(`${path}.bodyMarkdown is required for ${kind}`);
|
|
6996
7185
|
if ((kind === "link" || kind === "inspiration") && !url) errors.push(`${path}.url is required for ${kind}`);
|
|
@@ -7005,17 +7194,17 @@ function resourceValue(value, path, errors) {
|
|
|
7005
7194
|
if (provenanceRaw?.promotedFrom !== void 0 && !promotedRaw) errors.push(`${path}.provenance.promotedFrom must be an object`);
|
|
7006
7195
|
if (promotedRaw) unknownKeys2(promotedRaw, PROMOTION_KEYS, `${path}.provenance.promotedFrom`, errors);
|
|
7007
7196
|
const promotedFrom = promotedRaw ? {
|
|
7008
|
-
chapterId:
|
|
7009
|
-
resourceId:
|
|
7197
|
+
chapterId: text5(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
|
|
7198
|
+
resourceId: text5(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
|
|
7010
7199
|
resourceRevision: integer3(promotedRaw.resourceRevision, `${path}.provenance.promotedFrom.resourceRevision`, errors),
|
|
7011
7200
|
documentRevision: integer3(promotedRaw.documentRevision, `${path}.provenance.promotedFrom.documentRevision`, errors),
|
|
7012
|
-
promotedBy:
|
|
7201
|
+
promotedBy: text5(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
|
|
7013
7202
|
promotedAt: integer3(promotedRaw.promotedAt, `${path}.provenance.promotedFrom.promotedAt`, errors, 0)
|
|
7014
7203
|
} : void 0;
|
|
7015
7204
|
const provenance = {
|
|
7016
|
-
createdBy:
|
|
7205
|
+
createdBy: text5(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
|
|
7017
7206
|
createdAt: integer3(provenanceRaw?.createdAt, `${path}.provenance.createdAt`, errors, 0),
|
|
7018
|
-
updatedBy:
|
|
7207
|
+
updatedBy: text5(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
|
|
7019
7208
|
updatedAt: integer3(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
|
|
7020
7209
|
...promotedFrom ? { promotedFrom } : {}
|
|
7021
7210
|
};
|
|
@@ -7034,7 +7223,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
|
|
|
7034
7223
|
if (sourceRaw) unknownKeys2(sourceRaw, SOURCE_KEYS2, "document.source", errors);
|
|
7035
7224
|
const sourceType = sourceRaw?.type;
|
|
7036
7225
|
if (sourceType !== "chapter" && sourceType !== "built_not_found") errors.push("document.source.type must be chapter or built_not_found");
|
|
7037
|
-
const sourceAppId =
|
|
7226
|
+
const sourceAppId = text5(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
|
|
7038
7227
|
if (sourceAppId && !APP_ID.test(sourceAppId)) errors.push("document.source.appId must be a lowercase app slug");
|
|
7039
7228
|
if (options.sourceType && sourceType !== options.sourceType) errors.push(`document.source.type must be ${options.sourceType}`);
|
|
7040
7229
|
if (options.sourceAppId && sourceAppId !== options.sourceAppId) errors.push(`document.source.appId must be ${options.sourceAppId}`);
|
|
@@ -7042,8 +7231,8 @@ function validateBuilderLibraryDocument(value, options = {}) {
|
|
|
7042
7231
|
if (raw2.target !== void 0 && !targetRaw) errors.push("document.target must be an object");
|
|
7043
7232
|
if (targetRaw) unknownKeys2(targetRaw, TARGET_KEYS2, "document.target", errors);
|
|
7044
7233
|
const target = targetRaw ? {
|
|
7045
|
-
appId:
|
|
7046
|
-
environment:
|
|
7234
|
+
appId: text5(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
|
|
7235
|
+
environment: text5(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
|
|
7047
7236
|
} : void 0;
|
|
7048
7237
|
if (options.targetAppId && target?.appId !== options.targetAppId) errors.push(`document.target.appId must be ${options.targetAppId}`);
|
|
7049
7238
|
if (!Array.isArray(raw2.resources)) errors.push("document.resources must be an array");
|
|
@@ -7138,9 +7327,9 @@ async function createBuilderLibraryEnvelope(document) {
|
|
|
7138
7327
|
}
|
|
7139
7328
|
|
|
7140
7329
|
// src/email-retirement.ts
|
|
7141
|
-
function isRetiredEmailCopy(subject,
|
|
7330
|
+
function isRetiredEmailCopy(subject, text8) {
|
|
7142
7331
|
const subjectCopy = subject.trimStart().replace(/^\[dev\]\s*/i, "");
|
|
7143
|
-
const bodyCopy =
|
|
7332
|
+
const bodyCopy = text8.trimStart().replace(/^\(dev redirect; original recipient: [^\r\n]*\)\r?\n\r?\n/, "");
|
|
7144
7333
|
return /^\(\s*unused\s*:/i.test(subjectCopy) || /^Unused\.\s*Switched off\b/i.test(bodyCopy);
|
|
7145
7334
|
}
|
|
7146
7335
|
|
|
@@ -7218,10 +7407,10 @@ function planDelivery(input) {
|
|
|
7218
7407
|
if (isRetiredEmailCopy(renderedSubject, renderedText)) return { deliver: false, reason: "template-retired" };
|
|
7219
7408
|
if (validateEmailCopy({ subject: renderedSubject, text: renderedText }, { allowedVariables: [] }).length) return { deliver: false, reason: "template-invalid" };
|
|
7220
7409
|
const subject = (redirect ? "[dev] " : "") + renderedSubject;
|
|
7221
|
-
const
|
|
7410
|
+
const text8 = redirect ? `(dev redirect; original recipient: ${input.to})
|
|
7222
7411
|
|
|
7223
7412
|
` + renderedText : renderedText;
|
|
7224
|
-
return { deliver: true, transport, to, subject, text:
|
|
7413
|
+
return { deliver: true, transport, to, subject, text: text8, redirected: redirect };
|
|
7225
7414
|
}
|
|
7226
7415
|
|
|
7227
7416
|
// src/email-readiness.ts
|
|
@@ -7556,6 +7745,17 @@ function findGiftRef(obj) {
|
|
|
7556
7745
|
}
|
|
7557
7746
|
function normalizeWebhookEvent(event2, options = {}) {
|
|
7558
7747
|
const obj = event2.data?.object ?? {};
|
|
7748
|
+
if (event2.type.startsWith("charge.dispute.")) {
|
|
7749
|
+
const chargeId = typeof obj.charge === "string" ? obj.charge : String(obj.charge?.id ?? "");
|
|
7750
|
+
if (!/^ch_[A-Za-z0-9]+$/.test(chargeId) || typeof obj.id !== "string" || !obj.id) return { kind: "ignored", type: event2.type };
|
|
7751
|
+
return {
|
|
7752
|
+
kind: "disputed",
|
|
7753
|
+
chargeId,
|
|
7754
|
+
disputeId: obj.id,
|
|
7755
|
+
disputeStatus: typeof obj.status === "string" ? obj.status : "unknown",
|
|
7756
|
+
...typeof obj.payment_intent === "string" ? { paymentIntentId: obj.payment_intent } : {}
|
|
7757
|
+
};
|
|
7758
|
+
}
|
|
7559
7759
|
if (event2.type === "charge.refunded" && (!Number.isSafeInteger(obj.amount) || Number(obj.amount) <= 0 || obj.amount_refunded !== obj.amount || obj.refunded !== true)) return { kind: "ignored", type: event2.type };
|
|
7560
7760
|
const gift = findGiftRef(obj);
|
|
7561
7761
|
if (gift.giftIntentId) {
|
|
@@ -7866,7 +8066,7 @@ var ID4 = /^[a-z][a-z0-9-]{1,79}$/;
|
|
|
7866
8066
|
var PRICE = /^price_[A-Za-z0-9]{12,}$/;
|
|
7867
8067
|
var CURRENCY = /^[a-z]{3}$/;
|
|
7868
8068
|
var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
7869
|
-
var
|
|
8069
|
+
var text6 = (value, path, errors, max) => {
|
|
7870
8070
|
if (typeof value !== "string" || !value.trim()) {
|
|
7871
8071
|
errors.push(`${path} is required`);
|
|
7872
8072
|
return "";
|
|
@@ -7894,23 +8094,23 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
7894
8094
|
}
|
|
7895
8095
|
const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
|
|
7896
8096
|
for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
7897
|
-
const id11 =
|
|
8097
|
+
const id11 = text6(item.id, `${path}.id`, errors, 80);
|
|
7898
8098
|
if (id11 && !ID4.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
7899
8099
|
if (ids2.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
|
|
7900
8100
|
ids2.add(id11);
|
|
7901
8101
|
const targetRaw = record3(item.target);
|
|
7902
8102
|
let target = { type: "entitlement", entitlementKey: "invalid" };
|
|
7903
8103
|
if (targetRaw?.type === "tier") {
|
|
7904
|
-
const tierId =
|
|
8104
|
+
const tierId = text6(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
|
|
7905
8105
|
if (!tierIds.has(tierId)) errors.push(`${path}.target.tierId must reference a declared tier`);
|
|
7906
8106
|
target = { type: "tier", tierId };
|
|
7907
8107
|
} else if (targetRaw?.type === "entitlement") {
|
|
7908
|
-
const entitlementKey =
|
|
8108
|
+
const entitlementKey = text6(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
|
|
7909
8109
|
if (entitlementKey && !ID4.test(entitlementKey)) errors.push(`${path}.target.entitlementKey must be a lowercase kebab-case id`);
|
|
7910
8110
|
target = { type: "entitlement", entitlementKey };
|
|
7911
8111
|
} else errors.push(`${path}.target.type must be tier or entitlement`);
|
|
7912
8112
|
if (item.billingMode !== "one_time" && item.billingMode !== "subscription") errors.push(`${path}.billingMode must be one_time or subscription`);
|
|
7913
|
-
const stripePriceId =
|
|
8113
|
+
const stripePriceId = text6(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
|
|
7914
8114
|
if (stripePriceId && !PRICE.test(stripePriceId)) errors.push(`${path}.stripePriceId must be a real Stripe Price id`);
|
|
7915
8115
|
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`);
|
|
7916
8116
|
const currency = typeof item.currency === "string" ? item.currency.trim().toLowerCase() : "";
|
|
@@ -7932,9 +8132,9 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
7932
8132
|
redemptionLifetimeHours: lifetime,
|
|
7933
8133
|
...activeFrom !== void 0 ? { activeFrom } : {},
|
|
7934
8134
|
...activeUntil !== void 0 ? { activeUntil } : {},
|
|
7935
|
-
title:
|
|
7936
|
-
description:
|
|
7937
|
-
ctaLabel:
|
|
8135
|
+
title: text6(item.title, `${path}.title`, errors, 160),
|
|
8136
|
+
description: text6(item.description, `${path}.description`, errors, 2e3),
|
|
8137
|
+
ctaLabel: text6(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
|
|
7938
8138
|
});
|
|
7939
8139
|
}
|
|
7940
8140
|
return errors.length ? { ok: false, errors } : { ok: true, value: { schemaVersion: 1, offers } };
|
|
@@ -8398,9 +8598,9 @@ function subAnnualCents(sub) {
|
|
|
8398
8598
|
}
|
|
8399
8599
|
|
|
8400
8600
|
// src/payments-stripe.ts
|
|
8401
|
-
import { stripeRequest as
|
|
8601
|
+
import { stripeRequest as stripeRequest12, verifyStripeSignature as verifyStripeSignature2 } from "@odla-ai/stripe";
|
|
8402
8602
|
async function stripeCall(sk, method, path, params, idempotencyKey) {
|
|
8403
|
-
return
|
|
8603
|
+
return stripeRequest12(
|
|
8404
8604
|
{ secretKey: sk },
|
|
8405
8605
|
method,
|
|
8406
8606
|
path,
|
|
@@ -9022,10 +9222,10 @@ function boundedString(value, path, errors, max, required = false) {
|
|
|
9022
9222
|
errors.push(`${path} must be a string`);
|
|
9023
9223
|
return void 0;
|
|
9024
9224
|
}
|
|
9025
|
-
const
|
|
9026
|
-
if (required && !
|
|
9027
|
-
if (
|
|
9028
|
-
return
|
|
9225
|
+
const text8 = value.trim();
|
|
9226
|
+
if (required && !text8) errors.push(`${path} is required`);
|
|
9227
|
+
if (text8.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
|
|
9228
|
+
return text8;
|
|
9029
9229
|
}
|
|
9030
9230
|
function validateMembershipPageDocument(input, options = {}) {
|
|
9031
9231
|
const errors = [];
|
|
@@ -9074,8 +9274,8 @@ function validateMembershipPageDocument(input, options = {}) {
|
|
|
9074
9274
|
else {
|
|
9075
9275
|
if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);
|
|
9076
9276
|
benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {
|
|
9077
|
-
const
|
|
9078
|
-
return
|
|
9277
|
+
const text8 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
|
|
9278
|
+
return text8 ? [text8] : [];
|
|
9079
9279
|
});
|
|
9080
9280
|
}
|
|
9081
9281
|
}
|
|
@@ -9174,7 +9374,7 @@ var recordOf = (value) => value !== null && typeof value === "object" && !Array.
|
|
|
9174
9374
|
function unknownKeys3(value, allowed2, path, errors) {
|
|
9175
9375
|
for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
9176
9376
|
}
|
|
9177
|
-
function
|
|
9377
|
+
function text7(value, path, errors, max = 65536) {
|
|
9178
9378
|
if (typeof value !== "string" || !value.trim()) {
|
|
9179
9379
|
errors.push(`${path} is required`);
|
|
9180
9380
|
return "";
|
|
@@ -9197,11 +9397,11 @@ function tiersOf(value, errors) {
|
|
|
9197
9397
|
return [];
|
|
9198
9398
|
}
|
|
9199
9399
|
unknownKeys3(item, TIER_KEYS2, path, errors);
|
|
9200
|
-
const id11 =
|
|
9400
|
+
const id11 = text7(item.id, `${path}.id`, errors, 80);
|
|
9201
9401
|
if (id11 && !TIER.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
9202
9402
|
if (seen.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
|
|
9203
9403
|
seen.add(id11);
|
|
9204
|
-
const name =
|
|
9404
|
+
const name = text7(item.name, `${path}.name`, errors, 160);
|
|
9205
9405
|
if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
|
|
9206
9406
|
if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
|
|
9207
9407
|
if (!Number.isInteger(item.priceCents) || Number(item.priceCents) < 0 || Number(item.priceCents) > 1e8) {
|
|
@@ -9219,7 +9419,7 @@ function tiersOf(value, errors) {
|
|
|
9219
9419
|
if (item.interval !== void 0 && item.interval !== "year" && item.interval !== "month") {
|
|
9220
9420
|
errors.push(`${path}.interval must be year or month`);
|
|
9221
9421
|
}
|
|
9222
|
-
const blurb = item.blurb === void 0 ? void 0 :
|
|
9422
|
+
const blurb = item.blurb === void 0 ? void 0 : text7(item.blurb, `${path}.blurb`, errors, 4e3);
|
|
9223
9423
|
return [{
|
|
9224
9424
|
id: id11,
|
|
9225
9425
|
name,
|
|
@@ -9277,7 +9477,7 @@ function policyOf(value, errors, tiers2) {
|
|
|
9277
9477
|
const result = {};
|
|
9278
9478
|
for (const key3 of allowed2) {
|
|
9279
9479
|
if (input[key3] === void 0 && key3 !== "disclaimerText" && key3 !== "refundPolicyText") continue;
|
|
9280
|
-
result[key3] =
|
|
9480
|
+
result[key3] = text7(input[key3], `document.policy.${key3}`, errors);
|
|
9281
9481
|
}
|
|
9282
9482
|
const tierPolicies = {};
|
|
9283
9483
|
if (input.tierPolicies !== void 0) {
|
|
@@ -9292,8 +9492,8 @@ function policyOf(value, errors, tiers2) {
|
|
|
9292
9492
|
}
|
|
9293
9493
|
unknownKeys3(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
|
|
9294
9494
|
tierPolicies[id11] = {
|
|
9295
|
-
refundPolicyText:
|
|
9296
|
-
...policy2.trustCopy !== void 0 ? { trustCopy:
|
|
9495
|
+
refundPolicyText: text7(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
|
|
9496
|
+
...policy2.trustCopy !== void 0 ? { trustCopy: text7(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
|
|
9297
9497
|
};
|
|
9298
9498
|
}
|
|
9299
9499
|
}
|
|
@@ -9333,9 +9533,9 @@ function validateSignupControlDocument(input) {
|
|
|
9333
9533
|
if (!Number.isSafeInteger(root.generatedAt) || Number(root.generatedAt) < 1) errors.push("document.generatedAt must be epoch milliseconds");
|
|
9334
9534
|
const source = recordOf(root.source);
|
|
9335
9535
|
const target = recordOf(root.target);
|
|
9336
|
-
const appId =
|
|
9337
|
-
const chapterId =
|
|
9338
|
-
const environment =
|
|
9536
|
+
const appId = text7(source?.appId, "document.source.appId", errors, 63);
|
|
9537
|
+
const chapterId = text7(target?.chapterId, "document.target.chapterId", errors, 63);
|
|
9538
|
+
const environment = text7(target?.environment, "document.target.environment", errors, 63);
|
|
9339
9539
|
if (appId && !SLUG9.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
|
|
9340
9540
|
if (chapterId && !SLUG9.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
|
|
9341
9541
|
if (environment && !ENVIRONMENT.test(environment)) errors.push("document.target.environment is invalid");
|
|
@@ -9512,9 +9712,9 @@ function markdownBenefits(lines, path, errors) {
|
|
|
9512
9712
|
errors.push(`${path} line ${index2 + 1} must be a Markdown bullet`);
|
|
9513
9713
|
continue;
|
|
9514
9714
|
}
|
|
9515
|
-
const
|
|
9516
|
-
if (
|
|
9517
|
-
if (
|
|
9715
|
+
const text8 = (match[1] ?? "").trim();
|
|
9716
|
+
if (text8.length > 2e3) errors.push(`${path} line ${index2 + 1} must be at most 2,000 characters`);
|
|
9717
|
+
if (text8) benefits.push(text8);
|
|
9518
9718
|
}
|
|
9519
9719
|
if (benefits.length > MAX_BENEFITS2) errors.push(`${path} supports at most ${MAX_BENEFITS2} entries`);
|
|
9520
9720
|
return benefits.slice(0, MAX_BENEFITS2);
|