@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.cjs
CHANGED
|
@@ -254,7 +254,7 @@ __export(src_exports, {
|
|
|
254
254
|
slotWindow: () => slotWindow,
|
|
255
255
|
stageIndex: () => stageIndex,
|
|
256
256
|
stripeCall: () => stripeCall,
|
|
257
|
-
stripeForm: () =>
|
|
257
|
+
stripeForm: () => import_stripe12.stripeForm,
|
|
258
258
|
subAnnualCents: () => subAnnualCents,
|
|
259
259
|
submitApplication: () => submitApplication,
|
|
260
260
|
subscriptionIdempotencyKey: () => subscriptionIdempotencyKey,
|
|
@@ -276,7 +276,7 @@ __export(src_exports, {
|
|
|
276
276
|
verifyMembershipAccessReceipt: () => verifyMembershipAccessReceipt,
|
|
277
277
|
verifyMembershipCheckoutBinding: () => verifyMembershipCheckoutBinding,
|
|
278
278
|
verifyMembershipPaymentReceipt: () => verifyMembershipPaymentReceipt,
|
|
279
|
-
verifyStripeSignature: () =>
|
|
279
|
+
verifyStripeSignature: () => import_stripe12.verifyStripeSignature,
|
|
280
280
|
webhookMutationId: () => webhookMutationId
|
|
281
281
|
});
|
|
282
282
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -1843,7 +1843,9 @@ var DEFAULT_JOIN_COPY = {
|
|
|
1843
1843
|
failed: "The booking could not be completed.",
|
|
1844
1844
|
loading: "Loading available times\u2026",
|
|
1845
1845
|
book: "Book this time",
|
|
1846
|
-
booking: "Booking\u2026"
|
|
1846
|
+
booking: "Booking\u2026",
|
|
1847
|
+
pickDate: "Pick a date",
|
|
1848
|
+
pickTime: "Pick a time"
|
|
1847
1849
|
},
|
|
1848
1850
|
payment: {
|
|
1849
1851
|
setupFailed: "Payment could not be set up. Please try again.",
|
|
@@ -1995,8 +1997,8 @@ var APPLICATION_LABELS = {
|
|
|
1995
1997
|
state: "State"
|
|
1996
1998
|
};
|
|
1997
1999
|
function humanize(id11) {
|
|
1998
|
-
const
|
|
1999
|
-
return
|
|
2000
|
+
const text8 = id11.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
2001
|
+
return text8 ? `${text8[0].toUpperCase()}${text8.slice(1)}` : id11;
|
|
2000
2002
|
}
|
|
2001
2003
|
function applicationDefs(chapter) {
|
|
2002
2004
|
try {
|
|
@@ -2795,6 +2797,7 @@ function normalizeNetworkCommercialStripeEvent(input, digest) {
|
|
|
2795
2797
|
else if (eventType.startsWith("refund.")) objectKind = "refund";
|
|
2796
2798
|
else if (eventType === "charge.refunded") objectKind = "refund";
|
|
2797
2799
|
else if (eventType.startsWith("payment_intent.")) objectKind = "payment_intent";
|
|
2800
|
+
else if (eventType.startsWith("charge.dispute.")) objectKind = "dispute";
|
|
2798
2801
|
const customerId = refId(object2.customer, "cus_");
|
|
2799
2802
|
const subscriptionId = objectKind === "subscription" ? providerId(objectId, "sub_") : subscriptionRef(object2);
|
|
2800
2803
|
const invoiceId = objectKind === "invoice" ? providerId(objectId, "in_") : refId(object2.invoice, "in_");
|
|
@@ -2804,6 +2807,7 @@ function normalizeNetworkCommercialStripeEvent(input, digest) {
|
|
|
2804
2807
|
const amountCents = integer(eventType === "charge.refunded" ? object2.amount_refunded : object2.amount);
|
|
2805
2808
|
const currency = text(object2.currency)?.toLowerCase();
|
|
2806
2809
|
const status2 = text(object2.status);
|
|
2810
|
+
const disputeReason = objectKind === "dispute" ? text(object2.reason) : void 0;
|
|
2807
2811
|
return {
|
|
2808
2812
|
eventId,
|
|
2809
2813
|
digest,
|
|
@@ -2822,6 +2826,7 @@ function normalizeNetworkCommercialStripeEvent(input, digest) {
|
|
|
2822
2826
|
...invoiceId ? { invoiceId } : {},
|
|
2823
2827
|
...chargeId ? { chargeId } : {},
|
|
2824
2828
|
...paymentIntentId ? { paymentIntentId } : {},
|
|
2829
|
+
...disputeReason ? { disputeReason } : {},
|
|
2825
2830
|
...price,
|
|
2826
2831
|
...objectKind === "subscription" && commercialSeatItems(object2) ? { seatSubscriptionItems: commercialSeatItems(object2) } : {},
|
|
2827
2832
|
...status2 ? { status: status2 } : {},
|
|
@@ -2864,6 +2869,7 @@ function ownRef(fact) {
|
|
|
2864
2869
|
if (fact.objectKind === "subscription") return `subscription:${fact.objectId}`;
|
|
2865
2870
|
if (fact.objectKind === "invoice") return `invoice:${fact.objectId}`;
|
|
2866
2871
|
if (fact.objectKind === "payment_intent") return `payment_intent:${fact.objectId}`;
|
|
2872
|
+
if (fact.objectKind === "dispute") return `dispute:${fact.objectId}`;
|
|
2867
2873
|
if (fact.eventType.startsWith("refund.")) return `refund:${fact.objectId}`;
|
|
2868
2874
|
return fact.eventType === "charge.refunded" ? `charge:${fact.objectId}` : null;
|
|
2869
2875
|
}
|
|
@@ -2919,6 +2925,7 @@ function projectNetworkCommercialEvents(input, sources) {
|
|
|
2919
2925
|
continue;
|
|
2920
2926
|
}
|
|
2921
2927
|
const source = assignments.get(fact.eventId);
|
|
2928
|
+
if (!source && fact.objectKind === "dispute") continue;
|
|
2922
2929
|
if (!source) {
|
|
2923
2930
|
const direct = directSource(fact, sources);
|
|
2924
2931
|
const keys2 = new Set(objectRefs(fact).flatMap((ref) => [...refs.get(ref) ?? []]));
|
|
@@ -2935,6 +2942,7 @@ function projectNetworkCommercialEvents(input, sources) {
|
|
|
2935
2942
|
const subscriptions = rows3.filter((row2) => row2.objectKind === "subscription");
|
|
2936
2943
|
const invoices = rows3.filter((row2) => row2.objectKind === "invoice");
|
|
2937
2944
|
const seatPayments = rows3.filter((row2) => row2.objectKind === "payment_intent" && row2.seatId && row2.membershipId && !row2.invoiceId);
|
|
2945
|
+
const disputes = rows3.filter((row2) => row2.objectKind === "dispute");
|
|
2938
2946
|
const refunds = rows3.filter((row2) => row2.objectKind === "refund" && row2.eventType.startsWith("refund."));
|
|
2939
2947
|
for (const charge of rows3.filter((row2) => row2.eventType === "charge.refunded")) {
|
|
2940
2948
|
if (!refunds.some((refund) => refund.chargeId === charge.objectId)) {
|
|
@@ -2955,6 +2963,7 @@ function projectNetworkCommercialEvents(input, sources) {
|
|
|
2955
2963
|
for (const row2 of refunds) add(row2, "refundedCents", row2.amountCents ?? 0);
|
|
2956
2964
|
return {
|
|
2957
2965
|
...seatPayments.length ? { seatPayments, seats: projectCommercialSeats(seatPayments, subscriptions, refunds) } : {},
|
|
2966
|
+
...disputes.length ? { disputes } : {},
|
|
2958
2967
|
customers,
|
|
2959
2968
|
subscriptions,
|
|
2960
2969
|
invoices,
|
|
@@ -4558,7 +4567,7 @@ async function refundNetworkMembership(db, input, secretKey, now) {
|
|
|
4558
4567
|
}
|
|
4559
4568
|
|
|
4560
4569
|
// src/membership-provider-lifecycle.ts
|
|
4561
|
-
var
|
|
4570
|
+
var import_stripe5 = require("@odla-ai/stripe");
|
|
4562
4571
|
|
|
4563
4572
|
// src/membership-seat-reservation.ts
|
|
4564
4573
|
async function namedSeatRecipientClaimId(input, email) {
|
|
@@ -4656,6 +4665,9 @@ function namedSeatRecipientState(seat) {
|
|
|
4656
4665
|
return seat.refundReason === "declined" ? "declined" : "refunded";
|
|
4657
4666
|
case "canceled":
|
|
4658
4667
|
return "canceled";
|
|
4668
|
+
// A card-network dispute on the seat's own payment pauses the recipient until it is won or re-charged.
|
|
4669
|
+
case "payment_disputed":
|
|
4670
|
+
return "disputed";
|
|
4659
4671
|
default:
|
|
4660
4672
|
return "payment_pending";
|
|
4661
4673
|
}
|
|
@@ -4849,12 +4861,174 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
4849
4861
|
return membershipFail("membership_renewal_retry_required");
|
|
4850
4862
|
}
|
|
4851
4863
|
|
|
4864
|
+
// src/membership-dispute-authority.ts
|
|
4865
|
+
var import_stripe4 = require("@odla-ai/stripe");
|
|
4866
|
+
|
|
4867
|
+
// src/membership-dispute-transition.ts
|
|
4868
|
+
var MEMBERSHIP_DISPUTED_STATUS = "payment_disputed";
|
|
4869
|
+
var DISPUTE_WON_STATUSES = ["won", "warning_closed"];
|
|
4870
|
+
var CLOSED_STATUSES = [...DISPUTE_WON_STATUSES, "lost"];
|
|
4871
|
+
function disputeTransition(row2, fact, eventId, now, terminal) {
|
|
4872
|
+
const status2 = String(fact.status ?? "unknown"), closed = CLOSED_STATUSES.includes(status2);
|
|
4873
|
+
const sameDispute = row2.disputeId === fact.objectId;
|
|
4874
|
+
if (sameDispute && Number(row2.disputeClosedAt ?? 0) > 0 && !closed) return null;
|
|
4875
|
+
const won = DISPUTE_WON_STATUSES.includes(status2);
|
|
4876
|
+
const suspended = row2.status === MEMBERSHIP_DISPUTED_STATUS, done = terminal.includes(String(row2.status));
|
|
4877
|
+
const restored = String(row2.disputePriorStatus ?? (row2.journeyApprovedAt ? "active" : "paid_pending_approval"));
|
|
4878
|
+
const next = done ? String(row2.status) : won ? suspended ? restored : String(row2.status) : MEMBERSHIP_DISPUTED_STATUS;
|
|
4879
|
+
return {
|
|
4880
|
+
status: next,
|
|
4881
|
+
disputeId: fact.objectId,
|
|
4882
|
+
disputeStatus: status2,
|
|
4883
|
+
disputeEventId: eventId,
|
|
4884
|
+
disputeClosedAt: closed ? now : 0,
|
|
4885
|
+
...fact.disputeReason ? { disputeReason: fact.disputeReason } : {},
|
|
4886
|
+
...fact.amountCents !== void 0 ? { disputeAmountCents: fact.amountCents } : {},
|
|
4887
|
+
...sameDispute ? {} : { disputedAt: now },
|
|
4888
|
+
...next === MEMBERSHIP_DISPUTED_STATUS && !suspended ? { disputePriorStatus: String(row2.status) } : {},
|
|
4889
|
+
revision: Number(row2.revision) + 1,
|
|
4890
|
+
updatedAt: now
|
|
4891
|
+
};
|
|
4892
|
+
}
|
|
4893
|
+
|
|
4894
|
+
// src/membership-seat-dispute.ts
|
|
4895
|
+
var text3 = (value) => typeof value === "string" && value ? value : void 0;
|
|
4896
|
+
var TERMINAL = ["payment_pending", "payment_processing", "payment_canceling", "refund_pending", "refunded", "canceled"];
|
|
4897
|
+
var namedSeatDisputeStanding = (seat, applied) => ({ seatId: String(seat.id), status: String(seat.status), ...text3(seat.disputeStatus) ? { disputeStatus: text3(seat.disputeStatus) } : {}, applied });
|
|
4898
|
+
function disputedChargeCoversSeat(charge, seat) {
|
|
4899
|
+
return charge.seatId === seat.id && charge.customerId === seat.providerCustomerId && charge.paymentIntentId !== void 0 && (charge.paymentIntentId === seat.providerPaymentIntentId || charge.paymentIntentId === seat.rechargePaymentIntentId);
|
|
4900
|
+
}
|
|
4901
|
+
async function reconcileNamedSeatDispute(db, input, now, charge, evidence) {
|
|
4902
|
+
if (!charge.seatId) membershipFail("membership_dispute_scope_mismatch");
|
|
4903
|
+
const membership = async (applied, seat) => {
|
|
4904
|
+
const id11 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id11);
|
|
4905
|
+
requireMembershipScope(member, input);
|
|
4906
|
+
return {
|
|
4907
|
+
membershipId: id11,
|
|
4908
|
+
status: String(member.status),
|
|
4909
|
+
termEndsAt: Number(member.termEndsAt),
|
|
4910
|
+
graceEndsAt: Number(member.graceEndsAt ?? 0),
|
|
4911
|
+
autoRenew: member.autoRenew === true,
|
|
4912
|
+
applied,
|
|
4913
|
+
...text3(member.disputeStatus) ? { disputeStatus: text3(member.disputeStatus) } : {},
|
|
4914
|
+
...seat ? { seat: namedSeatDisputeStanding(seat, applied) } : {}
|
|
4915
|
+
};
|
|
4916
|
+
};
|
|
4917
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
4918
|
+
const state = await loadMembershipAuthorityState(db, input), seat = await membershipLoad(db, "networkSeat", charge.seatId);
|
|
4919
|
+
if (!seat) return membership(false);
|
|
4920
|
+
requireMembershipScope(seat, input);
|
|
4921
|
+
if (charge.customerId !== seat.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
|
|
4922
|
+
if (!disputedChargeCoversSeat(charge, seat) || seat.disputeEventId === input.eventId) return membership(seat.disputeEventId === input.eventId, seat);
|
|
4923
|
+
const attrs = disputeTransition(seat, evidence.fact, input.eventId, now, TERMINAL);
|
|
4924
|
+
if (!attrs) return membership(false, seat);
|
|
4925
|
+
try {
|
|
4926
|
+
await db.transact([membershipUpdate("networkSeat", String(seat.id), attrs)], { mutationId: `seat-dispute:${seat.id}:${input.eventId}`, guards: [
|
|
4927
|
+
{ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision } },
|
|
4928
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
|
|
4929
|
+
{ ns: "networkCommercialEvents", id: String(evidence.row.id), exists: true, equals: { digest: evidence.fact.digest } },
|
|
4930
|
+
evidence.guard
|
|
4931
|
+
] });
|
|
4932
|
+
} catch (error) {
|
|
4933
|
+
if (error.code !== "transact_guard_failed") throw error;
|
|
4934
|
+
continue;
|
|
4935
|
+
}
|
|
4936
|
+
const saved = await membershipLoad(db, "networkSeat", String(seat.id));
|
|
4937
|
+
requireMembershipScope(saved, input);
|
|
4938
|
+
return membership(true, saved);
|
|
4939
|
+
}
|
|
4940
|
+
return membershipFail("membership_dispute_retry_required");
|
|
4941
|
+
}
|
|
4942
|
+
|
|
4943
|
+
// src/membership-dispute-authority.ts
|
|
4944
|
+
var TERMINAL2 = ["refunded", "refund_pending", "canceled", "declined", "restarting", "superseded"];
|
|
4945
|
+
var text4 = (value) => typeof value === "string" && value ? value : void 0;
|
|
4946
|
+
var refOf = (value) => typeof value === "string" ? text4(value) : text4(value?.id);
|
|
4947
|
+
async function readDisputedCharge(secretKey, chargeId, fetch2) {
|
|
4948
|
+
if (!/^ch_[A-Za-z0-9]+$/.test(chargeId)) membershipFail("invalid_dispute_charge");
|
|
4949
|
+
const config = { secretKey, ...fetch2 ? { fetch: fetch2 } : {} };
|
|
4950
|
+
const charge = await (0, import_stripe4.stripeRequest)(config, "GET", `/v1/charges/${chargeId}`);
|
|
4951
|
+
if (!charge.ok || charge.body.id !== chargeId) membershipFail("membership_dispute_provider_unavailable");
|
|
4952
|
+
const customerId = refOf(charge.body.customer), invoiceId = refOf(charge.body.invoice), paymentIntentId = refOf(charge.body.payment_intent);
|
|
4953
|
+
if (!customerId) membershipFail("membership_dispute_provider_unavailable");
|
|
4954
|
+
let subscriptionId;
|
|
4955
|
+
if (invoiceId) {
|
|
4956
|
+
const invoice = await (0, import_stripe4.stripeRequest)(config, "GET", `/v1/invoices/${invoiceId}`);
|
|
4957
|
+
if (!invoice.ok || invoice.body.id !== invoiceId) membershipFail("membership_dispute_provider_unavailable");
|
|
4958
|
+
subscriptionId = refOf(invoice.body.subscription) ?? refOf(invoice.body.parent?.subscription_details?.subscription);
|
|
4959
|
+
}
|
|
4960
|
+
const seatId = text4(charge.body.metadata?.seatId);
|
|
4961
|
+
return {
|
|
4962
|
+
chargeId,
|
|
4963
|
+
customerId,
|
|
4964
|
+
...invoiceId ? { invoiceId } : {},
|
|
4965
|
+
...subscriptionId ? { subscriptionId } : {},
|
|
4966
|
+
...paymentIntentId ? { paymentIntentId } : {},
|
|
4967
|
+
...seatId ? { seatId } : {}
|
|
4968
|
+
};
|
|
4969
|
+
}
|
|
4970
|
+
function disputedChargeCoversMembership(charge, member) {
|
|
4971
|
+
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);
|
|
4972
|
+
}
|
|
4973
|
+
async function reconcileMembershipDispute(db, input, now, secretKey, resolved) {
|
|
4974
|
+
membershipClock(now);
|
|
4975
|
+
const row2 = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
|
|
4976
|
+
const fact = row2?.fact;
|
|
4977
|
+
const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, row2?.digest, "membership_billing_evidence_pending");
|
|
4978
|
+
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");
|
|
4979
|
+
const id11 = await networkMembershipId(input);
|
|
4980
|
+
const receipt = (value, applied = value.disputeEventId === input.eventId) => ({
|
|
4981
|
+
membershipId: id11,
|
|
4982
|
+
status: String(value.status),
|
|
4983
|
+
termEndsAt: Number(value.termEndsAt),
|
|
4984
|
+
graceEndsAt: Number(value.graceEndsAt ?? 0),
|
|
4985
|
+
autoRenew: value.autoRenew === true,
|
|
4986
|
+
...text4(value.disputeStatus) ? { disputeStatus: text4(value.disputeStatus) } : {},
|
|
4987
|
+
applied
|
|
4988
|
+
});
|
|
4989
|
+
const known = await membershipLoad(db, "networkMembership", id11);
|
|
4990
|
+
requireMembershipScope(known, input);
|
|
4991
|
+
if (known.disputeEventId === input.eventId) return receipt(known);
|
|
4992
|
+
const seatKnown = known.namedSeatId ? await membershipLoad(db, "networkSeat", String(known.namedSeatId)) : void 0;
|
|
4993
|
+
if (seatKnown?.disputeEventId === input.eventId) return { ...receipt(known, true), seat: namedSeatDisputeStanding(seatKnown, true) };
|
|
4994
|
+
const charge = resolved && resolved.chargeId === fact.chargeId ? resolved : await readDisputedCharge(secretKey, fact.chargeId);
|
|
4995
|
+
if (charge.customerId !== known.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
|
|
4996
|
+
if (charge.seatId) return reconcileNamedSeatDispute(db, input, now, charge, { row: row2, fact, guard: evidenceGuard });
|
|
4997
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
4998
|
+
const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
|
|
4999
|
+
requireMembershipScope(member, input);
|
|
5000
|
+
if (!disputedChargeCoversMembership(charge, member) || member.disputeEventId === input.eventId) return receipt(member);
|
|
5001
|
+
const attrs = disputeTransition(member, fact, input.eventId, now, TERMINAL2);
|
|
5002
|
+
if (!attrs) return receipt(member, false);
|
|
5003
|
+
try {
|
|
5004
|
+
await db.transact([membershipUpdate("networkMembership", id11, attrs)], { mutationId: `membership-dispute:${id11}:${input.eventId}`, guards: [
|
|
5005
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
|
|
5006
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
|
|
5007
|
+
{ ns: "networkCommercialEvents", id: String(row2.id), exists: true, equals: { digest: fact.digest } },
|
|
5008
|
+
evidenceGuard
|
|
5009
|
+
] });
|
|
5010
|
+
} catch (error) {
|
|
5011
|
+
if (error.code !== "transact_guard_failed") throw error;
|
|
5012
|
+
continue;
|
|
5013
|
+
}
|
|
5014
|
+
const saved = await membershipLoad(db, "networkMembership", id11);
|
|
5015
|
+
requireMembershipScope(saved, input);
|
|
5016
|
+
return receipt(saved, true);
|
|
5017
|
+
}
|
|
5018
|
+
return membershipFail("membership_dispute_retry_required");
|
|
5019
|
+
}
|
|
5020
|
+
|
|
4852
5021
|
// src/membership-provider-lifecycle.ts
|
|
4853
5022
|
async function reconcileMembershipBilling(db, input, now, stripeSecretName = "stripe_secret_key") {
|
|
4854
5023
|
membershipClock(now);
|
|
4855
5024
|
const row2 = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
|
|
4856
5025
|
const fact = row2?.fact;
|
|
4857
5026
|
if (fact?.objectKind === "invoice") return reconcileMembershipRenewal(db, input, now);
|
|
5027
|
+
if (fact?.objectKind === "dispute") {
|
|
5028
|
+
const secret = await getVaultSecret(db, stripeSecretName);
|
|
5029
|
+
if (!secret) membershipFail("membership_billing_provider_unavailable");
|
|
5030
|
+
return reconcileMembershipDispute(db, input, now, secret);
|
|
5031
|
+
}
|
|
4858
5032
|
const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, row2?.digest, "membership_billing_evidence_pending");
|
|
4859
5033
|
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({
|
|
4860
5034
|
appId: input.appId,
|
|
@@ -4897,7 +5071,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
|
|
|
4897
5071
|
}
|
|
4898
5072
|
if (fact.subscriptionId !== member.providerSubscriptionId || fact.objectKind !== "subscription") membershipFail("membership_billing_scope_mismatch");
|
|
4899
5073
|
if (["refund_pending", "refunded", "declined", "canceled", "restarting"].includes(String(member.status))) return receipt();
|
|
4900
|
-
const result = await (0,
|
|
5074
|
+
const result = await (0, import_stripe5.stripeRequest)({ secretKey: sk }, "GET", `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`);
|
|
4901
5075
|
const sub = result.body;
|
|
4902
5076
|
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)) {
|
|
4903
5077
|
membershipFail("membership_billing_provider_mismatch");
|
|
@@ -4954,7 +5128,7 @@ async function expireRestartedMembershipSeat(db, target, member, now) {
|
|
|
4954
5128
|
}
|
|
4955
5129
|
|
|
4956
5130
|
// src/membership-restart-authority.ts
|
|
4957
|
-
var
|
|
5131
|
+
var import_stripe6 = require("@odla-ai/stripe");
|
|
4958
5132
|
|
|
4959
5133
|
// src/stripe-event-digest.ts
|
|
4960
5134
|
async function stripePayloadDigest(value) {
|
|
@@ -4969,14 +5143,14 @@ function membershipCanRestart(member, now) {
|
|
|
4969
5143
|
}
|
|
4970
5144
|
async function stopPreviousSubscription(member, target, secret) {
|
|
4971
5145
|
const path = `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`;
|
|
4972
|
-
const result = await (0,
|
|
5146
|
+
const result = await (0, import_stripe6.stripeRequest)({ secretKey: secret }, "GET", path);
|
|
4973
5147
|
const sub = result.body;
|
|
4974
5148
|
if (!result.ok || sub.id !== member.providerSubscriptionId || sub.customer !== member.providerCustomerId || sub.livemode !== (target.environment === "prod") || !Object.entries({
|
|
4975
5149
|
...stripePartitionMetadata(target),
|
|
4976
5150
|
applicationId: target.applicationId
|
|
4977
5151
|
}).every(([key3, value]) => sub.metadata?.[key3] === value)) membershipFail("membership_restart_provider_mismatch");
|
|
4978
5152
|
if (sub.status === "canceled") return;
|
|
4979
|
-
const canceled = await (0,
|
|
5153
|
+
const canceled = await (0, import_stripe6.stripeRequest)(
|
|
4980
5154
|
{ secretKey: secret },
|
|
4981
5155
|
"DELETE",
|
|
4982
5156
|
path,
|
|
@@ -5077,16 +5251,16 @@ async function verifyMembershipAccessReceipt(value, input, now) {
|
|
|
5077
5251
|
}
|
|
5078
5252
|
|
|
5079
5253
|
// src/membership-seat-stripe.ts
|
|
5080
|
-
var
|
|
5254
|
+
var import_stripe8 = require("@odla-ai/stripe");
|
|
5081
5255
|
|
|
5082
5256
|
// src/subscription-provider-contract.ts
|
|
5083
|
-
var
|
|
5257
|
+
var import_stripe7 = require("@odla-ai/stripe");
|
|
5084
5258
|
function subscriptionContractMismatch() {
|
|
5085
5259
|
throw Object.assign(new Error("Stripe subscription does not match the checkout contract"), { code: "stripe_contract_mismatch" });
|
|
5086
5260
|
}
|
|
5087
5261
|
async function verifySubscriptionPrice(secretKey, input, resume = false) {
|
|
5088
5262
|
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();
|
|
5089
|
-
const result = await (0,
|
|
5263
|
+
const result = await (0, import_stripe7.stripeRequest)({ secretKey }, "GET", `/v1/prices/${encodeURIComponent(input.priceId)}`);
|
|
5090
5264
|
const price = result.body;
|
|
5091
5265
|
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();
|
|
5092
5266
|
return typeof price.product === "string" ? price.product : void 0;
|
|
@@ -5104,7 +5278,7 @@ var metadataFor = (seat) => ({
|
|
|
5104
5278
|
});
|
|
5105
5279
|
var matches = (value, expected) => value && Object.entries(expected).every(([key3, item]) => value[key3] === item);
|
|
5106
5280
|
async function read(sk, path, params) {
|
|
5107
|
-
const response = await (0,
|
|
5281
|
+
const response = await (0, import_stripe8.stripeRequest)({ secretKey: sk }, "GET", path, params);
|
|
5108
5282
|
if (!response.ok) return reject3();
|
|
5109
5283
|
return response.body;
|
|
5110
5284
|
}
|
|
@@ -5141,11 +5315,11 @@ async function createNamedSeatStripePayment(sk, seat, now) {
|
|
|
5141
5315
|
}
|
|
5142
5316
|
await subscriptionForSeat(sk, seat);
|
|
5143
5317
|
const metadata = metadataFor(seat);
|
|
5144
|
-
const result = seat.providerPaymentIntentId ? await (0,
|
|
5318
|
+
const result = seat.providerPaymentIntentId ? await (0, import_stripe8.stripeRequest)(
|
|
5145
5319
|
{ secretKey: sk },
|
|
5146
5320
|
"GET",
|
|
5147
5321
|
`/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`
|
|
5148
|
-
) : await (0,
|
|
5322
|
+
) : await (0, import_stripe8.stripeRequest)({ secretKey: sk }, "POST", "/v1/payment_intents", {
|
|
5149
5323
|
amount: seat.amountCents,
|
|
5150
5324
|
currency: seat.currency,
|
|
5151
5325
|
customer: seat.providerCustomerId,
|
|
@@ -5167,7 +5341,7 @@ async function attachNamedSeatStripeRenewal(sk, seat) {
|
|
|
5167
5341
|
return String(existing.id);
|
|
5168
5342
|
}
|
|
5169
5343
|
if (seat.combinedInvoice) reject3();
|
|
5170
|
-
const result = await (0,
|
|
5344
|
+
const result = await (0, import_stripe8.stripeRequest)({ secretKey: sk }, "POST", "/v1/subscription_items", {
|
|
5171
5345
|
subscription: seat.providerSubscriptionId,
|
|
5172
5346
|
price: seat.priceId,
|
|
5173
5347
|
quantity: 1,
|
|
@@ -5442,10 +5616,10 @@ async function recordRecipient(db, input, now, approve) {
|
|
|
5442
5616
|
}
|
|
5443
5617
|
|
|
5444
5618
|
// src/membership-seat-refund-stripe.ts
|
|
5445
|
-
var
|
|
5619
|
+
var import_stripe10 = require("@odla-ai/stripe");
|
|
5446
5620
|
|
|
5447
5621
|
// src/membership-checkout-seat-refund.ts
|
|
5448
|
-
var
|
|
5622
|
+
var import_stripe9 = require("@odla-ai/stripe");
|
|
5449
5623
|
var fail4 = (code = "named_seat_refund_mismatch") => {
|
|
5450
5624
|
throw Object.assign(new Error(code), { code });
|
|
5451
5625
|
};
|
|
@@ -5454,7 +5628,7 @@ async function refundCheckoutNamedSeat(secretKey, seat) {
|
|
|
5454
5628
|
const invoiceRef = seat.combinedInvoice;
|
|
5455
5629
|
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();
|
|
5456
5630
|
const call = async (method, path, params, key3) => {
|
|
5457
|
-
const result = await (0,
|
|
5631
|
+
const result = await (0, import_stripe9.stripeRequest)({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
|
|
5458
5632
|
if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
|
|
5459
5633
|
return result.body;
|
|
5460
5634
|
};
|
|
@@ -5527,7 +5701,7 @@ async function refundNamedSeatStripePayment(secretKey, seat) {
|
|
|
5527
5701
|
if (seat.combinedInvoice) return refundCheckoutNamedSeat(secretKey, seat);
|
|
5528
5702
|
if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail5();
|
|
5529
5703
|
const call = async (method, path, params, idempotencyKey) => {
|
|
5530
|
-
const result = await (0,
|
|
5704
|
+
const result = await (0, import_stripe10.stripeRequest)({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
|
|
5531
5705
|
if (!result.ok || !result.body) return fail5("named_seat_refund_provider_unavailable");
|
|
5532
5706
|
return result.body;
|
|
5533
5707
|
};
|
|
@@ -6229,7 +6403,7 @@ async function readBoundedJson(response, signal, maxBytes, label) {
|
|
|
6229
6403
|
};
|
|
6230
6404
|
signal.addEventListener("abort", cancel, { once: true });
|
|
6231
6405
|
let size = 0;
|
|
6232
|
-
let
|
|
6406
|
+
let text8 = "";
|
|
6233
6407
|
const decoder = new TextDecoder();
|
|
6234
6408
|
try {
|
|
6235
6409
|
while (true) {
|
|
@@ -6239,10 +6413,10 @@ async function readBoundedJson(response, signal, maxBytes, label) {
|
|
|
6239
6413
|
if (done) break;
|
|
6240
6414
|
size += value.byteLength;
|
|
6241
6415
|
if (size > maxBytes) throw new TypeError(`${label} exceeds limit`);
|
|
6242
|
-
|
|
6416
|
+
text8 += decoder.decode(value, { stream: true });
|
|
6243
6417
|
}
|
|
6244
6418
|
try {
|
|
6245
|
-
return JSON.parse(
|
|
6419
|
+
return JSON.parse(text8 + decoder.decode());
|
|
6246
6420
|
} catch {
|
|
6247
6421
|
throw new TypeError(`invalid ${label}`);
|
|
6248
6422
|
}
|
|
@@ -6456,7 +6630,7 @@ async function requestMembershipRefund(options) {
|
|
|
6456
6630
|
async function requestMembershipRenewal(options) {
|
|
6457
6631
|
const result = await requestAuthority(options, "renewals", { applicationId: options.applicationId, skuId: options.skuId, eventId: options.eventId });
|
|
6458
6632
|
const row2 = result.renewal;
|
|
6459
|
-
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)) {
|
|
6633
|
+
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)) {
|
|
6460
6634
|
throw new Error("invalid membership renewal receipt");
|
|
6461
6635
|
}
|
|
6462
6636
|
return { membershipId: row2.membershipId, status: row2.status, termEndsAt: row2.termEndsAt, graceEndsAt: row2.graceEndsAt, autoRenew: row2.autoRenew };
|
|
@@ -6516,7 +6690,7 @@ async function requestMembershipCancellation(options) {
|
|
|
6516
6690
|
}
|
|
6517
6691
|
|
|
6518
6692
|
// src/membership-seat-cancel.ts
|
|
6519
|
-
var
|
|
6693
|
+
var import_stripe11 = require("@odla-ai/stripe");
|
|
6520
6694
|
async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
|
|
6521
6695
|
membershipClock(now);
|
|
6522
6696
|
const state = await loadMembershipAuthorityState(db, target);
|
|
@@ -6544,7 +6718,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
|
|
|
6544
6718
|
}
|
|
6545
6719
|
const metadata = { ...stripePartitionMetadata(target), applicationId: target.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
|
|
6546
6720
|
const request = async (method, path, params) => {
|
|
6547
|
-
const result = await (0,
|
|
6721
|
+
const result = await (0, import_stripe11.stripeRequest)(
|
|
6548
6722
|
{ secretKey },
|
|
6549
6723
|
method,
|
|
6550
6724
|
path,
|
|
@@ -6557,7 +6731,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
|
|
|
6557
6731
|
let payment;
|
|
6558
6732
|
if (seat.providerPaymentIntentId) payment = await request("GET", `/v1/payment_intents/${encodeURIComponent(String(seat.providerPaymentIntentId))}`);
|
|
6559
6733
|
else {
|
|
6560
|
-
const snapshot = await listStripeObjects((0,
|
|
6734
|
+
const snapshot = await listStripeObjects((0, import_stripe11.initStripe)({ secretKey }), "/v1/payment_intents", { customer: String(seat.providerCustomerId) });
|
|
6561
6735
|
const matches2 = snapshot.objects.filter((row2) => row2.metadata?.seatId === target.seatId);
|
|
6562
6736
|
if (!snapshot.complete || matches2.length !== 1) throw new Error("seat payment creation outcome unresolved");
|
|
6563
6737
|
payment = matches2[0];
|
|
@@ -6729,6 +6903,19 @@ var id10 = () => attr9("string", false, true, true);
|
|
|
6729
6903
|
var key2 = (optional = false) => attr9("string", optional, false, true);
|
|
6730
6904
|
var scope = () => ({ appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), applicationId: key2(), skuId: key2() });
|
|
6731
6905
|
var times = () => ({ revision: attr9("number"), createdAt: attr9("number"), updatedAt: attr9("number") });
|
|
6906
|
+
var dispute = () => ({
|
|
6907
|
+
disputeId: key2(true),
|
|
6908
|
+
disputeStatus: attr9("string", true),
|
|
6909
|
+
disputeReason: attr9("string", true),
|
|
6910
|
+
disputeAmountCents: attr9("number", true),
|
|
6911
|
+
disputedAt: attr9("number", true),
|
|
6912
|
+
disputeClosedAt: attr9("number", true),
|
|
6913
|
+
disputePriorStatus: attr9("string", true),
|
|
6914
|
+
disputeEventId: key2(true),
|
|
6915
|
+
rechargePaymentIntentId: key2(true),
|
|
6916
|
+
rechargedAt: attr9("number", true),
|
|
6917
|
+
rechargeAttempt: attr9("json", true)
|
|
6918
|
+
});
|
|
6732
6919
|
var membershipAuthorityEntities = {
|
|
6733
6920
|
networkMembership: { attrs: {
|
|
6734
6921
|
id: id10(),
|
|
@@ -6768,7 +6955,8 @@ var membershipAuthorityEntities = {
|
|
|
6768
6955
|
graceEndsAt: attr9("number", true),
|
|
6769
6956
|
...times(),
|
|
6770
6957
|
refundRequestedAt: attr9("number", true),
|
|
6771
|
-
restartId: key2(true)
|
|
6958
|
+
restartId: key2(true),
|
|
6959
|
+
...dispute()
|
|
6772
6960
|
} },
|
|
6773
6961
|
membershipRestart: { attrs: {
|
|
6774
6962
|
id: id10(),
|
|
@@ -6844,7 +7032,8 @@ var membershipAuthorityEntities = {
|
|
|
6844
7032
|
refundedCents: attr9("number", true),
|
|
6845
7033
|
providerRefundIds: attr9("json", true),
|
|
6846
7034
|
regiftedAt: attr9("number", true),
|
|
6847
|
-
formerRecipients: attr9("json", true)
|
|
7035
|
+
formerRecipients: attr9("json", true),
|
|
7036
|
+
...dispute()
|
|
6848
7037
|
} },
|
|
6849
7038
|
networkSeatRecipient: { attrs: { id: id10(), appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), seatId: key2(), recipientEmailNormalized: key2(), createdAt: attr9("number") } },
|
|
6850
7039
|
membershipEffect: { attrs: {
|
|
@@ -7204,7 +7393,7 @@ var record2 = (value) => value !== null && typeof value === "object" && !Array.i
|
|
|
7204
7393
|
function unknownKeys2(value, allowed2, path, errors) {
|
|
7205
7394
|
for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
7206
7395
|
}
|
|
7207
|
-
function
|
|
7396
|
+
function text5(value, path, errors, max = MAX_TEXT2, required = true) {
|
|
7208
7397
|
if (value === void 0 && !required) return void 0;
|
|
7209
7398
|
if (typeof value !== "string" || required && !value.trim()) {
|
|
7210
7399
|
errors.push(`${path} must be a non-empty string`);
|
|
@@ -7230,7 +7419,7 @@ function scopeValue(value, path, errors) {
|
|
|
7230
7419
|
return { kind: raw2.kind };
|
|
7231
7420
|
}
|
|
7232
7421
|
if (raw2.kind === "chapter") {
|
|
7233
|
-
const chapterId =
|
|
7422
|
+
const chapterId = text5(raw2.chapterId, `${path}.chapterId`, errors, 63) ?? "";
|
|
7234
7423
|
if (chapterId && !APP_ID.test(chapterId)) errors.push(`${path}.chapterId must be a lowercase app slug`);
|
|
7235
7424
|
return { kind: "chapter", chapterId };
|
|
7236
7425
|
}
|
|
@@ -7244,22 +7433,22 @@ function resourceValue(value, path, errors) {
|
|
|
7244
7433
|
return null;
|
|
7245
7434
|
}
|
|
7246
7435
|
unknownKeys2(raw2, RESOURCE_KEYS, path, errors);
|
|
7247
|
-
const id11 =
|
|
7436
|
+
const id11 = text5(raw2.id, `${path}.id`, errors, 80) ?? "";
|
|
7248
7437
|
if (id11 && !ID2.test(id11)) errors.push(`${path}.id must be lowercase kebab-case`);
|
|
7249
|
-
const kind =
|
|
7438
|
+
const kind = text5(raw2.kind, `${path}.kind`, errors) ?? "";
|
|
7250
7439
|
if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
|
|
7251
|
-
const title =
|
|
7252
|
-
const summary =
|
|
7253
|
-
const status2 =
|
|
7440
|
+
const title = text5(raw2.title, `${path}.title`, errors, 240) ?? "";
|
|
7441
|
+
const summary = text5(raw2.summary, `${path}.summary`, errors, 1e3, false);
|
|
7442
|
+
const status2 = text5(raw2.status, `${path}.status`, errors) ?? "";
|
|
7254
7443
|
if (!STATUSES.has(status2)) errors.push(`${path}.status must be draft, published, or archived`);
|
|
7255
7444
|
const revision = integer3(raw2.revision, `${path}.revision`, errors);
|
|
7256
7445
|
const scope2 = scopeValue(raw2.scope, `${path}.scope`, errors) ?? { kind: "global" };
|
|
7257
|
-
const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) =>
|
|
7446
|
+
const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) => text5(tag, `${path}.tags[${index2}]`, errors, 80) ?? "") : [];
|
|
7258
7447
|
if (!Array.isArray(raw2.tags)) errors.push(`${path}.tags must be an array`);
|
|
7259
7448
|
if (tags.length > MAX_TAGS) errors.push(`${path}.tags may contain at most ${MAX_TAGS} items`);
|
|
7260
7449
|
if (new Set(tags).size !== tags.length) errors.push(`${path}.tags must be unique`);
|
|
7261
|
-
const bodyMarkdown =
|
|
7262
|
-
const url =
|
|
7450
|
+
const bodyMarkdown = text5(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN2, false);
|
|
7451
|
+
const url = text5(raw2.url, `${path}.url`, errors, 2048, false);
|
|
7263
7452
|
if (url) {
|
|
7264
7453
|
try {
|
|
7265
7454
|
if (new URL(url).protocol !== "https:") errors.push(`${path}.url must use https`);
|
|
@@ -7271,9 +7460,9 @@ function resourceValue(value, path, errors) {
|
|
|
7271
7460
|
if (raw2.brandAsset !== void 0 && !asset) errors.push(`${path}.brandAsset must be an object`);
|
|
7272
7461
|
if (asset) unknownKeys2(asset, ASSET_KEYS, `${path}.brandAsset`, errors);
|
|
7273
7462
|
const brandAsset = asset ? {
|
|
7274
|
-
bookId:
|
|
7275
|
-
assetId:
|
|
7276
|
-
...asset.filename === void 0 ? {} : { filename:
|
|
7463
|
+
bookId: text5(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
|
|
7464
|
+
assetId: text5(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
|
|
7465
|
+
...asset.filename === void 0 ? {} : { filename: text5(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
|
|
7277
7466
|
} : void 0;
|
|
7278
7467
|
if ((kind === "document" || kind === "note") && !bodyMarkdown) errors.push(`${path}.bodyMarkdown is required for ${kind}`);
|
|
7279
7468
|
if ((kind === "link" || kind === "inspiration") && !url) errors.push(`${path}.url is required for ${kind}`);
|
|
@@ -7288,17 +7477,17 @@ function resourceValue(value, path, errors) {
|
|
|
7288
7477
|
if (provenanceRaw?.promotedFrom !== void 0 && !promotedRaw) errors.push(`${path}.provenance.promotedFrom must be an object`);
|
|
7289
7478
|
if (promotedRaw) unknownKeys2(promotedRaw, PROMOTION_KEYS, `${path}.provenance.promotedFrom`, errors);
|
|
7290
7479
|
const promotedFrom = promotedRaw ? {
|
|
7291
|
-
chapterId:
|
|
7292
|
-
resourceId:
|
|
7480
|
+
chapterId: text5(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
|
|
7481
|
+
resourceId: text5(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
|
|
7293
7482
|
resourceRevision: integer3(promotedRaw.resourceRevision, `${path}.provenance.promotedFrom.resourceRevision`, errors),
|
|
7294
7483
|
documentRevision: integer3(promotedRaw.documentRevision, `${path}.provenance.promotedFrom.documentRevision`, errors),
|
|
7295
|
-
promotedBy:
|
|
7484
|
+
promotedBy: text5(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
|
|
7296
7485
|
promotedAt: integer3(promotedRaw.promotedAt, `${path}.provenance.promotedFrom.promotedAt`, errors, 0)
|
|
7297
7486
|
} : void 0;
|
|
7298
7487
|
const provenance = {
|
|
7299
|
-
createdBy:
|
|
7488
|
+
createdBy: text5(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
|
|
7300
7489
|
createdAt: integer3(provenanceRaw?.createdAt, `${path}.provenance.createdAt`, errors, 0),
|
|
7301
|
-
updatedBy:
|
|
7490
|
+
updatedBy: text5(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
|
|
7302
7491
|
updatedAt: integer3(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
|
|
7303
7492
|
...promotedFrom ? { promotedFrom } : {}
|
|
7304
7493
|
};
|
|
@@ -7317,7 +7506,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
|
|
|
7317
7506
|
if (sourceRaw) unknownKeys2(sourceRaw, SOURCE_KEYS2, "document.source", errors);
|
|
7318
7507
|
const sourceType = sourceRaw?.type;
|
|
7319
7508
|
if (sourceType !== "chapter" && sourceType !== "built_not_found") errors.push("document.source.type must be chapter or built_not_found");
|
|
7320
|
-
const sourceAppId =
|
|
7509
|
+
const sourceAppId = text5(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
|
|
7321
7510
|
if (sourceAppId && !APP_ID.test(sourceAppId)) errors.push("document.source.appId must be a lowercase app slug");
|
|
7322
7511
|
if (options.sourceType && sourceType !== options.sourceType) errors.push(`document.source.type must be ${options.sourceType}`);
|
|
7323
7512
|
if (options.sourceAppId && sourceAppId !== options.sourceAppId) errors.push(`document.source.appId must be ${options.sourceAppId}`);
|
|
@@ -7325,8 +7514,8 @@ function validateBuilderLibraryDocument(value, options = {}) {
|
|
|
7325
7514
|
if (raw2.target !== void 0 && !targetRaw) errors.push("document.target must be an object");
|
|
7326
7515
|
if (targetRaw) unknownKeys2(targetRaw, TARGET_KEYS2, "document.target", errors);
|
|
7327
7516
|
const target = targetRaw ? {
|
|
7328
|
-
appId:
|
|
7329
|
-
environment:
|
|
7517
|
+
appId: text5(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
|
|
7518
|
+
environment: text5(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
|
|
7330
7519
|
} : void 0;
|
|
7331
7520
|
if (options.targetAppId && target?.appId !== options.targetAppId) errors.push(`document.target.appId must be ${options.targetAppId}`);
|
|
7332
7521
|
if (!Array.isArray(raw2.resources)) errors.push("document.resources must be an array");
|
|
@@ -7421,9 +7610,9 @@ async function createBuilderLibraryEnvelope(document) {
|
|
|
7421
7610
|
}
|
|
7422
7611
|
|
|
7423
7612
|
// src/email-retirement.ts
|
|
7424
|
-
function isRetiredEmailCopy(subject,
|
|
7613
|
+
function isRetiredEmailCopy(subject, text8) {
|
|
7425
7614
|
const subjectCopy = subject.trimStart().replace(/^\[dev\]\s*/i, "");
|
|
7426
|
-
const bodyCopy =
|
|
7615
|
+
const bodyCopy = text8.trimStart().replace(/^\(dev redirect; original recipient: [^\r\n]*\)\r?\n\r?\n/, "");
|
|
7427
7616
|
return /^\(\s*unused\s*:/i.test(subjectCopy) || /^Unused\.\s*Switched off\b/i.test(bodyCopy);
|
|
7428
7617
|
}
|
|
7429
7618
|
|
|
@@ -7501,10 +7690,10 @@ function planDelivery(input) {
|
|
|
7501
7690
|
if (isRetiredEmailCopy(renderedSubject, renderedText)) return { deliver: false, reason: "template-retired" };
|
|
7502
7691
|
if ((0, import_crm5.validateEmailCopy)({ subject: renderedSubject, text: renderedText }, { allowedVariables: [] }).length) return { deliver: false, reason: "template-invalid" };
|
|
7503
7692
|
const subject = (redirect ? "[dev] " : "") + renderedSubject;
|
|
7504
|
-
const
|
|
7693
|
+
const text8 = redirect ? `(dev redirect; original recipient: ${input.to})
|
|
7505
7694
|
|
|
7506
7695
|
` + renderedText : renderedText;
|
|
7507
|
-
return { deliver: true, transport, to, subject, text:
|
|
7696
|
+
return { deliver: true, transport, to, subject, text: text8, redirected: redirect };
|
|
7508
7697
|
}
|
|
7509
7698
|
|
|
7510
7699
|
// src/email-readiness.ts
|
|
@@ -7778,7 +7967,7 @@ async function reconcileLifecycleDelivery(db, input, now) {
|
|
|
7778
7967
|
}
|
|
7779
7968
|
|
|
7780
7969
|
// src/payments.ts
|
|
7781
|
-
var
|
|
7970
|
+
var import_stripe12 = require("@odla-ai/stripe");
|
|
7782
7971
|
function paymentsReady(group, hasSecretKey) {
|
|
7783
7972
|
return Boolean(group.stripePublishableKey && group.stripePriceId && hasSecretKey);
|
|
7784
7973
|
}
|
|
@@ -7839,6 +8028,17 @@ function findGiftRef(obj) {
|
|
|
7839
8028
|
}
|
|
7840
8029
|
function normalizeWebhookEvent(event2, options = {}) {
|
|
7841
8030
|
const obj = event2.data?.object ?? {};
|
|
8031
|
+
if (event2.type.startsWith("charge.dispute.")) {
|
|
8032
|
+
const chargeId = typeof obj.charge === "string" ? obj.charge : String(obj.charge?.id ?? "");
|
|
8033
|
+
if (!/^ch_[A-Za-z0-9]+$/.test(chargeId) || typeof obj.id !== "string" || !obj.id) return { kind: "ignored", type: event2.type };
|
|
8034
|
+
return {
|
|
8035
|
+
kind: "disputed",
|
|
8036
|
+
chargeId,
|
|
8037
|
+
disputeId: obj.id,
|
|
8038
|
+
disputeStatus: typeof obj.status === "string" ? obj.status : "unknown",
|
|
8039
|
+
...typeof obj.payment_intent === "string" ? { paymentIntentId: obj.payment_intent } : {}
|
|
8040
|
+
};
|
|
8041
|
+
}
|
|
7842
8042
|
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 };
|
|
7843
8043
|
const gift = findGiftRef(obj);
|
|
7844
8044
|
if (gift.giftIntentId) {
|
|
@@ -8149,7 +8349,7 @@ var ID4 = /^[a-z][a-z0-9-]{1,79}$/;
|
|
|
8149
8349
|
var PRICE = /^price_[A-Za-z0-9]{12,}$/;
|
|
8150
8350
|
var CURRENCY = /^[a-z]{3}$/;
|
|
8151
8351
|
var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
8152
|
-
var
|
|
8352
|
+
var text6 = (value, path, errors, max) => {
|
|
8153
8353
|
if (typeof value !== "string" || !value.trim()) {
|
|
8154
8354
|
errors.push(`${path} is required`);
|
|
8155
8355
|
return "";
|
|
@@ -8177,23 +8377,23 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
8177
8377
|
}
|
|
8178
8378
|
const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
|
|
8179
8379
|
for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
8180
|
-
const id11 =
|
|
8380
|
+
const id11 = text6(item.id, `${path}.id`, errors, 80);
|
|
8181
8381
|
if (id11 && !ID4.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
8182
8382
|
if (ids2.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
|
|
8183
8383
|
ids2.add(id11);
|
|
8184
8384
|
const targetRaw = record3(item.target);
|
|
8185
8385
|
let target = { type: "entitlement", entitlementKey: "invalid" };
|
|
8186
8386
|
if (targetRaw?.type === "tier") {
|
|
8187
|
-
const tierId =
|
|
8387
|
+
const tierId = text6(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
|
|
8188
8388
|
if (!tierIds.has(tierId)) errors.push(`${path}.target.tierId must reference a declared tier`);
|
|
8189
8389
|
target = { type: "tier", tierId };
|
|
8190
8390
|
} else if (targetRaw?.type === "entitlement") {
|
|
8191
|
-
const entitlementKey =
|
|
8391
|
+
const entitlementKey = text6(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
|
|
8192
8392
|
if (entitlementKey && !ID4.test(entitlementKey)) errors.push(`${path}.target.entitlementKey must be a lowercase kebab-case id`);
|
|
8193
8393
|
target = { type: "entitlement", entitlementKey };
|
|
8194
8394
|
} else errors.push(`${path}.target.type must be tier or entitlement`);
|
|
8195
8395
|
if (item.billingMode !== "one_time" && item.billingMode !== "subscription") errors.push(`${path}.billingMode must be one_time or subscription`);
|
|
8196
|
-
const stripePriceId =
|
|
8396
|
+
const stripePriceId = text6(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
|
|
8197
8397
|
if (stripePriceId && !PRICE.test(stripePriceId)) errors.push(`${path}.stripePriceId must be a real Stripe Price id`);
|
|
8198
8398
|
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`);
|
|
8199
8399
|
const currency = typeof item.currency === "string" ? item.currency.trim().toLowerCase() : "";
|
|
@@ -8215,9 +8415,9 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
8215
8415
|
redemptionLifetimeHours: lifetime,
|
|
8216
8416
|
...activeFrom !== void 0 ? { activeFrom } : {},
|
|
8217
8417
|
...activeUntil !== void 0 ? { activeUntil } : {},
|
|
8218
|
-
title:
|
|
8219
|
-
description:
|
|
8220
|
-
ctaLabel:
|
|
8418
|
+
title: text6(item.title, `${path}.title`, errors, 160),
|
|
8419
|
+
description: text6(item.description, `${path}.description`, errors, 2e3),
|
|
8420
|
+
ctaLabel: text6(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
|
|
8221
8421
|
});
|
|
8222
8422
|
}
|
|
8223
8423
|
return errors.length ? { ok: false, errors } : { ok: true, value: { schemaVersion: 1, offers } };
|
|
@@ -8676,9 +8876,9 @@ function subAnnualCents(sub) {
|
|
|
8676
8876
|
}
|
|
8677
8877
|
|
|
8678
8878
|
// src/payments-stripe.ts
|
|
8679
|
-
var
|
|
8879
|
+
var import_stripe13 = require("@odla-ai/stripe");
|
|
8680
8880
|
async function stripeCall(sk, method, path, params, idempotencyKey) {
|
|
8681
|
-
return (0,
|
|
8881
|
+
return (0, import_stripe13.stripeRequest)(
|
|
8682
8882
|
{ secretKey: sk },
|
|
8683
8883
|
method,
|
|
8684
8884
|
path,
|
|
@@ -9300,10 +9500,10 @@ function boundedString(value, path, errors, max, required = false) {
|
|
|
9300
9500
|
errors.push(`${path} must be a string`);
|
|
9301
9501
|
return void 0;
|
|
9302
9502
|
}
|
|
9303
|
-
const
|
|
9304
|
-
if (required && !
|
|
9305
|
-
if (
|
|
9306
|
-
return
|
|
9503
|
+
const text8 = value.trim();
|
|
9504
|
+
if (required && !text8) errors.push(`${path} is required`);
|
|
9505
|
+
if (text8.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
|
|
9506
|
+
return text8;
|
|
9307
9507
|
}
|
|
9308
9508
|
function validateMembershipPageDocument(input, options = {}) {
|
|
9309
9509
|
const errors = [];
|
|
@@ -9352,8 +9552,8 @@ function validateMembershipPageDocument(input, options = {}) {
|
|
|
9352
9552
|
else {
|
|
9353
9553
|
if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);
|
|
9354
9554
|
benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {
|
|
9355
|
-
const
|
|
9356
|
-
return
|
|
9555
|
+
const text8 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
|
|
9556
|
+
return text8 ? [text8] : [];
|
|
9357
9557
|
});
|
|
9358
9558
|
}
|
|
9359
9559
|
}
|
|
@@ -9452,7 +9652,7 @@ var recordOf = (value) => value !== null && typeof value === "object" && !Array.
|
|
|
9452
9652
|
function unknownKeys3(value, allowed2, path, errors) {
|
|
9453
9653
|
for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
9454
9654
|
}
|
|
9455
|
-
function
|
|
9655
|
+
function text7(value, path, errors, max = 65536) {
|
|
9456
9656
|
if (typeof value !== "string" || !value.trim()) {
|
|
9457
9657
|
errors.push(`${path} is required`);
|
|
9458
9658
|
return "";
|
|
@@ -9475,11 +9675,11 @@ function tiersOf(value, errors) {
|
|
|
9475
9675
|
return [];
|
|
9476
9676
|
}
|
|
9477
9677
|
unknownKeys3(item, TIER_KEYS2, path, errors);
|
|
9478
|
-
const id11 =
|
|
9678
|
+
const id11 = text7(item.id, `${path}.id`, errors, 80);
|
|
9479
9679
|
if (id11 && !TIER.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
9480
9680
|
if (seen.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
|
|
9481
9681
|
seen.add(id11);
|
|
9482
|
-
const name =
|
|
9682
|
+
const name = text7(item.name, `${path}.name`, errors, 160);
|
|
9483
9683
|
if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
|
|
9484
9684
|
if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
|
|
9485
9685
|
if (!Number.isInteger(item.priceCents) || Number(item.priceCents) < 0 || Number(item.priceCents) > 1e8) {
|
|
@@ -9497,7 +9697,7 @@ function tiersOf(value, errors) {
|
|
|
9497
9697
|
if (item.interval !== void 0 && item.interval !== "year" && item.interval !== "month") {
|
|
9498
9698
|
errors.push(`${path}.interval must be year or month`);
|
|
9499
9699
|
}
|
|
9500
|
-
const blurb = item.blurb === void 0 ? void 0 :
|
|
9700
|
+
const blurb = item.blurb === void 0 ? void 0 : text7(item.blurb, `${path}.blurb`, errors, 4e3);
|
|
9501
9701
|
return [{
|
|
9502
9702
|
id: id11,
|
|
9503
9703
|
name,
|
|
@@ -9555,7 +9755,7 @@ function policyOf(value, errors, tiers2) {
|
|
|
9555
9755
|
const result = {};
|
|
9556
9756
|
for (const key3 of allowed2) {
|
|
9557
9757
|
if (input[key3] === void 0 && key3 !== "disclaimerText" && key3 !== "refundPolicyText") continue;
|
|
9558
|
-
result[key3] =
|
|
9758
|
+
result[key3] = text7(input[key3], `document.policy.${key3}`, errors);
|
|
9559
9759
|
}
|
|
9560
9760
|
const tierPolicies = {};
|
|
9561
9761
|
if (input.tierPolicies !== void 0) {
|
|
@@ -9570,8 +9770,8 @@ function policyOf(value, errors, tiers2) {
|
|
|
9570
9770
|
}
|
|
9571
9771
|
unknownKeys3(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
|
|
9572
9772
|
tierPolicies[id11] = {
|
|
9573
|
-
refundPolicyText:
|
|
9574
|
-
...policy2.trustCopy !== void 0 ? { trustCopy:
|
|
9773
|
+
refundPolicyText: text7(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
|
|
9774
|
+
...policy2.trustCopy !== void 0 ? { trustCopy: text7(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
|
|
9575
9775
|
};
|
|
9576
9776
|
}
|
|
9577
9777
|
}
|
|
@@ -9611,9 +9811,9 @@ function validateSignupControlDocument(input) {
|
|
|
9611
9811
|
if (!Number.isSafeInteger(root.generatedAt) || Number(root.generatedAt) < 1) errors.push("document.generatedAt must be epoch milliseconds");
|
|
9612
9812
|
const source = recordOf(root.source);
|
|
9613
9813
|
const target = recordOf(root.target);
|
|
9614
|
-
const appId =
|
|
9615
|
-
const chapterId =
|
|
9616
|
-
const environment =
|
|
9814
|
+
const appId = text7(source?.appId, "document.source.appId", errors, 63);
|
|
9815
|
+
const chapterId = text7(target?.chapterId, "document.target.chapterId", errors, 63);
|
|
9816
|
+
const environment = text7(target?.environment, "document.target.environment", errors, 63);
|
|
9617
9817
|
if (appId && !SLUG9.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
|
|
9618
9818
|
if (chapterId && !SLUG9.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
|
|
9619
9819
|
if (environment && !ENVIRONMENT.test(environment)) errors.push("document.target.environment is invalid");
|
|
@@ -9790,9 +9990,9 @@ function markdownBenefits(lines, path, errors) {
|
|
|
9790
9990
|
errors.push(`${path} line ${index2 + 1} must be a Markdown bullet`);
|
|
9791
9991
|
continue;
|
|
9792
9992
|
}
|
|
9793
|
-
const
|
|
9794
|
-
if (
|
|
9795
|
-
if (
|
|
9993
|
+
const text8 = (match[1] ?? "").trim();
|
|
9994
|
+
if (text8.length > 2e3) errors.push(`${path} line ${index2 + 1} must be at most 2,000 characters`);
|
|
9995
|
+
if (text8) benefits.push(text8);
|
|
9796
9996
|
}
|
|
9797
9997
|
if (benefits.length > MAX_BENEFITS2) errors.push(`${path} supports at most ${MAX_BENEFITS2} entries`);
|
|
9798
9998
|
return benefits.slice(0, MAX_BENEFITS2);
|