@odla-ai/chapter 0.58.1 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -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-WFTWT3V7.js} +141 -56
- package/dist/chunk-WFTWT3V7.js.map +1 -0
- package/dist/{chunk-CKC6X5H2.js → chunk-Y2H4VURI.js} +399 -308
- package/dist/chunk-Y2H4VURI.js.map +1 -0
- package/dist/{copy-context-DE4YMAbz.d.ts → copy-context-BuJBQafl.d.ts} +4 -0
- package/dist/index.cjs +218 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +214 -80
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +36 -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 +33 -5
- package/dist/ui/member/index.js +10 -2
- package/dist/worker/index.cjs +610 -183
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +64 -2
- package/dist/worker/index.d.ts +64 -2
- package/dist/worker/index.js +603 -176
- 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 text7 = id11.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
2001
|
+
return text7 ? `${text7[0].toUpperCase()}${text7.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) {
|
|
@@ -4849,12 +4858,113 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
4849
4858
|
return membershipFail("membership_renewal_retry_required");
|
|
4850
4859
|
}
|
|
4851
4860
|
|
|
4861
|
+
// src/membership-dispute-authority.ts
|
|
4862
|
+
var import_stripe4 = require("@odla-ai/stripe");
|
|
4863
|
+
var MEMBERSHIP_DISPUTED_STATUS = "payment_disputed";
|
|
4864
|
+
var DISPUTE_WON_STATUSES = ["won", "warning_closed"];
|
|
4865
|
+
var CLOSED_STATUSES = [...DISPUTE_WON_STATUSES, "lost"];
|
|
4866
|
+
var TERMINAL = ["refunded", "refund_pending", "canceled", "declined", "restarting", "superseded"];
|
|
4867
|
+
var text3 = (value) => typeof value === "string" && value ? value : void 0;
|
|
4868
|
+
var refOf = (value) => typeof value === "string" ? text3(value) : text3(value?.id);
|
|
4869
|
+
async function readDisputedCharge(secretKey, chargeId, fetch2) {
|
|
4870
|
+
if (!/^ch_[A-Za-z0-9]+$/.test(chargeId)) membershipFail("invalid_dispute_charge");
|
|
4871
|
+
const config = { secretKey, ...fetch2 ? { fetch: fetch2 } : {} };
|
|
4872
|
+
const charge = await (0, import_stripe4.stripeRequest)(config, "GET", `/v1/charges/${chargeId}`);
|
|
4873
|
+
if (!charge.ok || charge.body.id !== chargeId) membershipFail("membership_dispute_provider_unavailable");
|
|
4874
|
+
const customerId = refOf(charge.body.customer), invoiceId = refOf(charge.body.invoice), paymentIntentId = refOf(charge.body.payment_intent);
|
|
4875
|
+
if (!customerId) membershipFail("membership_dispute_provider_unavailable");
|
|
4876
|
+
let subscriptionId;
|
|
4877
|
+
if (invoiceId) {
|
|
4878
|
+
const invoice = await (0, import_stripe4.stripeRequest)(config, "GET", `/v1/invoices/${invoiceId}`);
|
|
4879
|
+
if (!invoice.ok || invoice.body.id !== invoiceId) membershipFail("membership_dispute_provider_unavailable");
|
|
4880
|
+
subscriptionId = refOf(invoice.body.subscription) ?? refOf(invoice.body.parent?.subscription_details?.subscription);
|
|
4881
|
+
}
|
|
4882
|
+
const seatId = text3(charge.body.metadata?.seatId);
|
|
4883
|
+
return {
|
|
4884
|
+
chargeId,
|
|
4885
|
+
customerId,
|
|
4886
|
+
...invoiceId ? { invoiceId } : {},
|
|
4887
|
+
...subscriptionId ? { subscriptionId } : {},
|
|
4888
|
+
...paymentIntentId ? { paymentIntentId } : {},
|
|
4889
|
+
...seatId ? { seatId } : {}
|
|
4890
|
+
};
|
|
4891
|
+
}
|
|
4892
|
+
function disputedChargeCoversMembership(charge, member) {
|
|
4893
|
+
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);
|
|
4894
|
+
}
|
|
4895
|
+
async function reconcileMembershipDispute(db, input, now, secretKey, resolved) {
|
|
4896
|
+
membershipClock(now);
|
|
4897
|
+
const row2 = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
|
|
4898
|
+
const fact = row2?.fact;
|
|
4899
|
+
const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, row2?.digest, "membership_billing_evidence_pending");
|
|
4900
|
+
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");
|
|
4901
|
+
const id11 = await networkMembershipId(input);
|
|
4902
|
+
const receipt = (value, applied = value.disputeEventId === input.eventId) => ({
|
|
4903
|
+
membershipId: id11,
|
|
4904
|
+
status: String(value.status),
|
|
4905
|
+
termEndsAt: Number(value.termEndsAt),
|
|
4906
|
+
graceEndsAt: Number(value.graceEndsAt ?? 0),
|
|
4907
|
+
autoRenew: value.autoRenew === true,
|
|
4908
|
+
...text3(value.disputeStatus) ? { disputeStatus: text3(value.disputeStatus) } : {},
|
|
4909
|
+
applied
|
|
4910
|
+
});
|
|
4911
|
+
const known = await membershipLoad(db, "networkMembership", id11);
|
|
4912
|
+
requireMembershipScope(known, input);
|
|
4913
|
+
if (known.disputeEventId === input.eventId) return receipt(known);
|
|
4914
|
+
const charge = resolved && resolved.chargeId === fact.chargeId ? resolved : await readDisputedCharge(secretKey, fact.chargeId);
|
|
4915
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
4916
|
+
const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
|
|
4917
|
+
requireMembershipScope(member, input);
|
|
4918
|
+
if (charge.customerId !== member.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
|
|
4919
|
+
if (!disputedChargeCoversMembership(charge, member) || member.disputeEventId === input.eventId) return receipt(member);
|
|
4920
|
+
const status2 = String(fact.status ?? "unknown"), closed = CLOSED_STATUSES.includes(status2);
|
|
4921
|
+
const sameDispute = member.disputeId === fact.objectId;
|
|
4922
|
+
if (sameDispute && Number(member.disputeClosedAt ?? 0) > 0 && !closed) return receipt(member, false);
|
|
4923
|
+
const won = DISPUTE_WON_STATUSES.includes(status2);
|
|
4924
|
+
const suspended = member.status === MEMBERSHIP_DISPUTED_STATUS, terminal = TERMINAL.includes(String(member.status));
|
|
4925
|
+
const next = terminal ? String(member.status) : won ? suspended ? String(member.disputePriorStatus ?? (member.journeyApprovedAt ? "active" : "paid_pending_approval")) : String(member.status) : MEMBERSHIP_DISPUTED_STATUS;
|
|
4926
|
+
const attrs = {
|
|
4927
|
+
status: next,
|
|
4928
|
+
disputeId: fact.objectId,
|
|
4929
|
+
disputeStatus: status2,
|
|
4930
|
+
disputeEventId: input.eventId,
|
|
4931
|
+
disputeClosedAt: closed ? now : 0,
|
|
4932
|
+
...fact.disputeReason ? { disputeReason: fact.disputeReason } : {},
|
|
4933
|
+
...fact.amountCents !== void 0 ? { disputeAmountCents: fact.amountCents } : {},
|
|
4934
|
+
...sameDispute ? {} : { disputedAt: now },
|
|
4935
|
+
...next === MEMBERSHIP_DISPUTED_STATUS && !suspended ? { disputePriorStatus: String(member.status) } : {},
|
|
4936
|
+
revision: Number(member.revision) + 1,
|
|
4937
|
+
updatedAt: now
|
|
4938
|
+
};
|
|
4939
|
+
try {
|
|
4940
|
+
await db.transact([membershipUpdate("networkMembership", id11, attrs)], { mutationId: `membership-dispute:${id11}:${input.eventId}`, guards: [
|
|
4941
|
+
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
|
|
4942
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
|
|
4943
|
+
{ ns: "networkCommercialEvents", id: String(row2.id), exists: true, equals: { digest: fact.digest } },
|
|
4944
|
+
evidenceGuard
|
|
4945
|
+
] });
|
|
4946
|
+
} catch (error) {
|
|
4947
|
+
if (error.code !== "transact_guard_failed") throw error;
|
|
4948
|
+
continue;
|
|
4949
|
+
}
|
|
4950
|
+
const saved = await membershipLoad(db, "networkMembership", id11);
|
|
4951
|
+
requireMembershipScope(saved, input);
|
|
4952
|
+
return receipt(saved, true);
|
|
4953
|
+
}
|
|
4954
|
+
return membershipFail("membership_dispute_retry_required");
|
|
4955
|
+
}
|
|
4956
|
+
|
|
4852
4957
|
// src/membership-provider-lifecycle.ts
|
|
4853
4958
|
async function reconcileMembershipBilling(db, input, now, stripeSecretName = "stripe_secret_key") {
|
|
4854
4959
|
membershipClock(now);
|
|
4855
4960
|
const row2 = await membershipLoad(db, "networkCommercialEvents", `network-commercial-event:${input.eventId}`);
|
|
4856
4961
|
const fact = row2?.fact;
|
|
4857
4962
|
if (fact?.objectKind === "invoice") return reconcileMembershipRenewal(db, input, now);
|
|
4963
|
+
if (fact?.objectKind === "dispute") {
|
|
4964
|
+
const secret = await getVaultSecret(db, stripeSecretName);
|
|
4965
|
+
if (!secret) membershipFail("membership_billing_provider_unavailable");
|
|
4966
|
+
return reconcileMembershipDispute(db, input, now, secret);
|
|
4967
|
+
}
|
|
4858
4968
|
const evidenceGuard = await commercialEvidenceGuard(db, input.eventId, row2?.digest, "membership_billing_evidence_pending");
|
|
4859
4969
|
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
4970
|
appId: input.appId,
|
|
@@ -4897,7 +5007,7 @@ async function reconcileMembershipBilling(db, input, now, stripeSecretName = "st
|
|
|
4897
5007
|
}
|
|
4898
5008
|
if (fact.subscriptionId !== member.providerSubscriptionId || fact.objectKind !== "subscription") membershipFail("membership_billing_scope_mismatch");
|
|
4899
5009
|
if (["refund_pending", "refunded", "declined", "canceled", "restarting"].includes(String(member.status))) return receipt();
|
|
4900
|
-
const result = await (0,
|
|
5010
|
+
const result = await (0, import_stripe5.stripeRequest)({ secretKey: sk }, "GET", `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`);
|
|
4901
5011
|
const sub = result.body;
|
|
4902
5012
|
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
5013
|
membershipFail("membership_billing_provider_mismatch");
|
|
@@ -4954,7 +5064,7 @@ async function expireRestartedMembershipSeat(db, target, member, now) {
|
|
|
4954
5064
|
}
|
|
4955
5065
|
|
|
4956
5066
|
// src/membership-restart-authority.ts
|
|
4957
|
-
var
|
|
5067
|
+
var import_stripe6 = require("@odla-ai/stripe");
|
|
4958
5068
|
|
|
4959
5069
|
// src/stripe-event-digest.ts
|
|
4960
5070
|
async function stripePayloadDigest(value) {
|
|
@@ -4969,14 +5079,14 @@ function membershipCanRestart(member, now) {
|
|
|
4969
5079
|
}
|
|
4970
5080
|
async function stopPreviousSubscription(member, target, secret) {
|
|
4971
5081
|
const path = `/v1/subscriptions/${encodeURIComponent(String(member.providerSubscriptionId))}`;
|
|
4972
|
-
const result = await (0,
|
|
5082
|
+
const result = await (0, import_stripe6.stripeRequest)({ secretKey: secret }, "GET", path);
|
|
4973
5083
|
const sub = result.body;
|
|
4974
5084
|
if (!result.ok || sub.id !== member.providerSubscriptionId || sub.customer !== member.providerCustomerId || sub.livemode !== (target.environment === "prod") || !Object.entries({
|
|
4975
5085
|
...stripePartitionMetadata(target),
|
|
4976
5086
|
applicationId: target.applicationId
|
|
4977
5087
|
}).every(([key3, value]) => sub.metadata?.[key3] === value)) membershipFail("membership_restart_provider_mismatch");
|
|
4978
5088
|
if (sub.status === "canceled") return;
|
|
4979
|
-
const canceled = await (0,
|
|
5089
|
+
const canceled = await (0, import_stripe6.stripeRequest)(
|
|
4980
5090
|
{ secretKey: secret },
|
|
4981
5091
|
"DELETE",
|
|
4982
5092
|
path,
|
|
@@ -5077,16 +5187,16 @@ async function verifyMembershipAccessReceipt(value, input, now) {
|
|
|
5077
5187
|
}
|
|
5078
5188
|
|
|
5079
5189
|
// src/membership-seat-stripe.ts
|
|
5080
|
-
var
|
|
5190
|
+
var import_stripe8 = require("@odla-ai/stripe");
|
|
5081
5191
|
|
|
5082
5192
|
// src/subscription-provider-contract.ts
|
|
5083
|
-
var
|
|
5193
|
+
var import_stripe7 = require("@odla-ai/stripe");
|
|
5084
5194
|
function subscriptionContractMismatch() {
|
|
5085
5195
|
throw Object.assign(new Error("Stripe subscription does not match the checkout contract"), { code: "stripe_contract_mismatch" });
|
|
5086
5196
|
}
|
|
5087
5197
|
async function verifySubscriptionPrice(secretKey, input, resume = false) {
|
|
5088
5198
|
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,
|
|
5199
|
+
const result = await (0, import_stripe7.stripeRequest)({ secretKey }, "GET", `/v1/prices/${encodeURIComponent(input.priceId)}`);
|
|
5090
5200
|
const price = result.body;
|
|
5091
5201
|
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
5202
|
return typeof price.product === "string" ? price.product : void 0;
|
|
@@ -5104,7 +5214,7 @@ var metadataFor = (seat) => ({
|
|
|
5104
5214
|
});
|
|
5105
5215
|
var matches = (value, expected) => value && Object.entries(expected).every(([key3, item]) => value[key3] === item);
|
|
5106
5216
|
async function read(sk, path, params) {
|
|
5107
|
-
const response = await (0,
|
|
5217
|
+
const response = await (0, import_stripe8.stripeRequest)({ secretKey: sk }, "GET", path, params);
|
|
5108
5218
|
if (!response.ok) return reject3();
|
|
5109
5219
|
return response.body;
|
|
5110
5220
|
}
|
|
@@ -5141,11 +5251,11 @@ async function createNamedSeatStripePayment(sk, seat, now) {
|
|
|
5141
5251
|
}
|
|
5142
5252
|
await subscriptionForSeat(sk, seat);
|
|
5143
5253
|
const metadata = metadataFor(seat);
|
|
5144
|
-
const result = seat.providerPaymentIntentId ? await (0,
|
|
5254
|
+
const result = seat.providerPaymentIntentId ? await (0, import_stripe8.stripeRequest)(
|
|
5145
5255
|
{ secretKey: sk },
|
|
5146
5256
|
"GET",
|
|
5147
5257
|
`/v1/payment_intents/${encodeURIComponent(seat.providerPaymentIntentId)}`
|
|
5148
|
-
) : await (0,
|
|
5258
|
+
) : await (0, import_stripe8.stripeRequest)({ secretKey: sk }, "POST", "/v1/payment_intents", {
|
|
5149
5259
|
amount: seat.amountCents,
|
|
5150
5260
|
currency: seat.currency,
|
|
5151
5261
|
customer: seat.providerCustomerId,
|
|
@@ -5167,7 +5277,7 @@ async function attachNamedSeatStripeRenewal(sk, seat) {
|
|
|
5167
5277
|
return String(existing.id);
|
|
5168
5278
|
}
|
|
5169
5279
|
if (seat.combinedInvoice) reject3();
|
|
5170
|
-
const result = await (0,
|
|
5280
|
+
const result = await (0, import_stripe8.stripeRequest)({ secretKey: sk }, "POST", "/v1/subscription_items", {
|
|
5171
5281
|
subscription: seat.providerSubscriptionId,
|
|
5172
5282
|
price: seat.priceId,
|
|
5173
5283
|
quantity: 1,
|
|
@@ -5442,10 +5552,10 @@ async function recordRecipient(db, input, now, approve) {
|
|
|
5442
5552
|
}
|
|
5443
5553
|
|
|
5444
5554
|
// src/membership-seat-refund-stripe.ts
|
|
5445
|
-
var
|
|
5555
|
+
var import_stripe10 = require("@odla-ai/stripe");
|
|
5446
5556
|
|
|
5447
5557
|
// src/membership-checkout-seat-refund.ts
|
|
5448
|
-
var
|
|
5558
|
+
var import_stripe9 = require("@odla-ai/stripe");
|
|
5449
5559
|
var fail4 = (code = "named_seat_refund_mismatch") => {
|
|
5450
5560
|
throw Object.assign(new Error(code), { code });
|
|
5451
5561
|
};
|
|
@@ -5454,7 +5564,7 @@ async function refundCheckoutNamedSeat(secretKey, seat) {
|
|
|
5454
5564
|
const invoiceRef = seat.combinedInvoice;
|
|
5455
5565
|
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
5566
|
const call = async (method, path, params, key3) => {
|
|
5457
|
-
const result = await (0,
|
|
5567
|
+
const result = await (0, import_stripe9.stripeRequest)({ secretKey }, method, path, params, key3 ? { idempotencyKey: key3 } : {});
|
|
5458
5568
|
if (!result.ok || !result.body) return fail4("named_seat_refund_provider_unavailable");
|
|
5459
5569
|
return result.body;
|
|
5460
5570
|
};
|
|
@@ -5527,7 +5637,7 @@ async function refundNamedSeatStripePayment(secretKey, seat) {
|
|
|
5527
5637
|
if (seat.combinedInvoice) return refundCheckoutNamedSeat(secretKey, seat);
|
|
5528
5638
|
if (!seat.providerPaymentIntentId || seat.status !== "refund_pending") fail5();
|
|
5529
5639
|
const call = async (method, path, params, idempotencyKey) => {
|
|
5530
|
-
const result = await (0,
|
|
5640
|
+
const result = await (0, import_stripe10.stripeRequest)({ secretKey }, method, path, params, idempotencyKey ? { idempotencyKey } : {});
|
|
5531
5641
|
if (!result.ok || !result.body) return fail5("named_seat_refund_provider_unavailable");
|
|
5532
5642
|
return result.body;
|
|
5533
5643
|
};
|
|
@@ -6229,7 +6339,7 @@ async function readBoundedJson(response, signal, maxBytes, label) {
|
|
|
6229
6339
|
};
|
|
6230
6340
|
signal.addEventListener("abort", cancel, { once: true });
|
|
6231
6341
|
let size = 0;
|
|
6232
|
-
let
|
|
6342
|
+
let text7 = "";
|
|
6233
6343
|
const decoder = new TextDecoder();
|
|
6234
6344
|
try {
|
|
6235
6345
|
while (true) {
|
|
@@ -6239,10 +6349,10 @@ async function readBoundedJson(response, signal, maxBytes, label) {
|
|
|
6239
6349
|
if (done) break;
|
|
6240
6350
|
size += value.byteLength;
|
|
6241
6351
|
if (size > maxBytes) throw new TypeError(`${label} exceeds limit`);
|
|
6242
|
-
|
|
6352
|
+
text7 += decoder.decode(value, { stream: true });
|
|
6243
6353
|
}
|
|
6244
6354
|
try {
|
|
6245
|
-
return JSON.parse(
|
|
6355
|
+
return JSON.parse(text7 + decoder.decode());
|
|
6246
6356
|
} catch {
|
|
6247
6357
|
throw new TypeError(`invalid ${label}`);
|
|
6248
6358
|
}
|
|
@@ -6456,7 +6566,7 @@ async function requestMembershipRefund(options) {
|
|
|
6456
6566
|
async function requestMembershipRenewal(options) {
|
|
6457
6567
|
const result = await requestAuthority(options, "renewals", { applicationId: options.applicationId, skuId: options.skuId, eventId: options.eventId });
|
|
6458
6568
|
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)) {
|
|
6569
|
+
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
6570
|
throw new Error("invalid membership renewal receipt");
|
|
6461
6571
|
}
|
|
6462
6572
|
return { membershipId: row2.membershipId, status: row2.status, termEndsAt: row2.termEndsAt, graceEndsAt: row2.graceEndsAt, autoRenew: row2.autoRenew };
|
|
@@ -6516,7 +6626,7 @@ async function requestMembershipCancellation(options) {
|
|
|
6516
6626
|
}
|
|
6517
6627
|
|
|
6518
6628
|
// src/membership-seat-cancel.ts
|
|
6519
|
-
var
|
|
6629
|
+
var import_stripe11 = require("@odla-ai/stripe");
|
|
6520
6630
|
async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
|
|
6521
6631
|
membershipClock(now);
|
|
6522
6632
|
const state = await loadMembershipAuthorityState(db, target);
|
|
@@ -6544,7 +6654,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
|
|
|
6544
6654
|
}
|
|
6545
6655
|
const metadata = { ...stripePartitionMetadata(target), applicationId: target.applicationId, membershipId: seat.primaryMembershipId, seatId: seat.id };
|
|
6546
6656
|
const request = async (method, path, params) => {
|
|
6547
|
-
const result = await (0,
|
|
6657
|
+
const result = await (0, import_stripe11.stripeRequest)(
|
|
6548
6658
|
{ secretKey },
|
|
6549
6659
|
method,
|
|
6550
6660
|
path,
|
|
@@ -6557,7 +6667,7 @@ async function cancelUnpaidNamedSeat(db, target, secretKey, now) {
|
|
|
6557
6667
|
let payment;
|
|
6558
6668
|
if (seat.providerPaymentIntentId) payment = await request("GET", `/v1/payment_intents/${encodeURIComponent(String(seat.providerPaymentIntentId))}`);
|
|
6559
6669
|
else {
|
|
6560
|
-
const snapshot = await listStripeObjects((0,
|
|
6670
|
+
const snapshot = await listStripeObjects((0, import_stripe11.initStripe)({ secretKey }), "/v1/payment_intents", { customer: String(seat.providerCustomerId) });
|
|
6561
6671
|
const matches2 = snapshot.objects.filter((row2) => row2.metadata?.seatId === target.seatId);
|
|
6562
6672
|
if (!snapshot.complete || matches2.length !== 1) throw new Error("seat payment creation outcome unresolved");
|
|
6563
6673
|
payment = matches2[0];
|
|
@@ -6768,7 +6878,20 @@ var membershipAuthorityEntities = {
|
|
|
6768
6878
|
graceEndsAt: attr9("number", true),
|
|
6769
6879
|
...times(),
|
|
6770
6880
|
refundRequestedAt: attr9("number", true),
|
|
6771
|
-
restartId: key2(true)
|
|
6881
|
+
restartId: key2(true),
|
|
6882
|
+
// A card-network dispute on this membership's payment: Stripe's dispute id and status, the reason the cardholder gave,
|
|
6883
|
+
// the status the membership held before it was suspended, and the re-issued charge once an operator collected again.
|
|
6884
|
+
disputeId: key2(true),
|
|
6885
|
+
disputeStatus: attr9("string", true),
|
|
6886
|
+
disputeReason: attr9("string", true),
|
|
6887
|
+
disputeAmountCents: attr9("number", true),
|
|
6888
|
+
disputedAt: attr9("number", true),
|
|
6889
|
+
disputeClosedAt: attr9("number", true),
|
|
6890
|
+
disputePriorStatus: attr9("string", true),
|
|
6891
|
+
disputeEventId: key2(true),
|
|
6892
|
+
rechargePaymentIntentId: key2(true),
|
|
6893
|
+
rechargedAt: attr9("number", true),
|
|
6894
|
+
rechargeAttempt: attr9("json", true)
|
|
6772
6895
|
} },
|
|
6773
6896
|
membershipRestart: { attrs: {
|
|
6774
6897
|
id: id10(),
|
|
@@ -7204,7 +7327,7 @@ var record2 = (value) => value !== null && typeof value === "object" && !Array.i
|
|
|
7204
7327
|
function unknownKeys2(value, allowed2, path, errors) {
|
|
7205
7328
|
for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
7206
7329
|
}
|
|
7207
|
-
function
|
|
7330
|
+
function text4(value, path, errors, max = MAX_TEXT2, required = true) {
|
|
7208
7331
|
if (value === void 0 && !required) return void 0;
|
|
7209
7332
|
if (typeof value !== "string" || required && !value.trim()) {
|
|
7210
7333
|
errors.push(`${path} must be a non-empty string`);
|
|
@@ -7230,7 +7353,7 @@ function scopeValue(value, path, errors) {
|
|
|
7230
7353
|
return { kind: raw2.kind };
|
|
7231
7354
|
}
|
|
7232
7355
|
if (raw2.kind === "chapter") {
|
|
7233
|
-
const chapterId =
|
|
7356
|
+
const chapterId = text4(raw2.chapterId, `${path}.chapterId`, errors, 63) ?? "";
|
|
7234
7357
|
if (chapterId && !APP_ID.test(chapterId)) errors.push(`${path}.chapterId must be a lowercase app slug`);
|
|
7235
7358
|
return { kind: "chapter", chapterId };
|
|
7236
7359
|
}
|
|
@@ -7244,22 +7367,22 @@ function resourceValue(value, path, errors) {
|
|
|
7244
7367
|
return null;
|
|
7245
7368
|
}
|
|
7246
7369
|
unknownKeys2(raw2, RESOURCE_KEYS, path, errors);
|
|
7247
|
-
const id11 =
|
|
7370
|
+
const id11 = text4(raw2.id, `${path}.id`, errors, 80) ?? "";
|
|
7248
7371
|
if (id11 && !ID2.test(id11)) errors.push(`${path}.id must be lowercase kebab-case`);
|
|
7249
|
-
const kind =
|
|
7372
|
+
const kind = text4(raw2.kind, `${path}.kind`, errors) ?? "";
|
|
7250
7373
|
if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
|
|
7251
|
-
const title =
|
|
7252
|
-
const summary =
|
|
7253
|
-
const status2 =
|
|
7374
|
+
const title = text4(raw2.title, `${path}.title`, errors, 240) ?? "";
|
|
7375
|
+
const summary = text4(raw2.summary, `${path}.summary`, errors, 1e3, false);
|
|
7376
|
+
const status2 = text4(raw2.status, `${path}.status`, errors) ?? "";
|
|
7254
7377
|
if (!STATUSES.has(status2)) errors.push(`${path}.status must be draft, published, or archived`);
|
|
7255
7378
|
const revision = integer3(raw2.revision, `${path}.revision`, errors);
|
|
7256
7379
|
const scope2 = scopeValue(raw2.scope, `${path}.scope`, errors) ?? { kind: "global" };
|
|
7257
|
-
const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) =>
|
|
7380
|
+
const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) => text4(tag, `${path}.tags[${index2}]`, errors, 80) ?? "") : [];
|
|
7258
7381
|
if (!Array.isArray(raw2.tags)) errors.push(`${path}.tags must be an array`);
|
|
7259
7382
|
if (tags.length > MAX_TAGS) errors.push(`${path}.tags may contain at most ${MAX_TAGS} items`);
|
|
7260
7383
|
if (new Set(tags).size !== tags.length) errors.push(`${path}.tags must be unique`);
|
|
7261
|
-
const bodyMarkdown =
|
|
7262
|
-
const url =
|
|
7384
|
+
const bodyMarkdown = text4(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN2, false);
|
|
7385
|
+
const url = text4(raw2.url, `${path}.url`, errors, 2048, false);
|
|
7263
7386
|
if (url) {
|
|
7264
7387
|
try {
|
|
7265
7388
|
if (new URL(url).protocol !== "https:") errors.push(`${path}.url must use https`);
|
|
@@ -7271,9 +7394,9 @@ function resourceValue(value, path, errors) {
|
|
|
7271
7394
|
if (raw2.brandAsset !== void 0 && !asset) errors.push(`${path}.brandAsset must be an object`);
|
|
7272
7395
|
if (asset) unknownKeys2(asset, ASSET_KEYS, `${path}.brandAsset`, errors);
|
|
7273
7396
|
const brandAsset = asset ? {
|
|
7274
|
-
bookId:
|
|
7275
|
-
assetId:
|
|
7276
|
-
...asset.filename === void 0 ? {} : { filename:
|
|
7397
|
+
bookId: text4(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
|
|
7398
|
+
assetId: text4(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
|
|
7399
|
+
...asset.filename === void 0 ? {} : { filename: text4(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
|
|
7277
7400
|
} : void 0;
|
|
7278
7401
|
if ((kind === "document" || kind === "note") && !bodyMarkdown) errors.push(`${path}.bodyMarkdown is required for ${kind}`);
|
|
7279
7402
|
if ((kind === "link" || kind === "inspiration") && !url) errors.push(`${path}.url is required for ${kind}`);
|
|
@@ -7288,17 +7411,17 @@ function resourceValue(value, path, errors) {
|
|
|
7288
7411
|
if (provenanceRaw?.promotedFrom !== void 0 && !promotedRaw) errors.push(`${path}.provenance.promotedFrom must be an object`);
|
|
7289
7412
|
if (promotedRaw) unknownKeys2(promotedRaw, PROMOTION_KEYS, `${path}.provenance.promotedFrom`, errors);
|
|
7290
7413
|
const promotedFrom = promotedRaw ? {
|
|
7291
|
-
chapterId:
|
|
7292
|
-
resourceId:
|
|
7414
|
+
chapterId: text4(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
|
|
7415
|
+
resourceId: text4(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
|
|
7293
7416
|
resourceRevision: integer3(promotedRaw.resourceRevision, `${path}.provenance.promotedFrom.resourceRevision`, errors),
|
|
7294
7417
|
documentRevision: integer3(promotedRaw.documentRevision, `${path}.provenance.promotedFrom.documentRevision`, errors),
|
|
7295
|
-
promotedBy:
|
|
7418
|
+
promotedBy: text4(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
|
|
7296
7419
|
promotedAt: integer3(promotedRaw.promotedAt, `${path}.provenance.promotedFrom.promotedAt`, errors, 0)
|
|
7297
7420
|
} : void 0;
|
|
7298
7421
|
const provenance = {
|
|
7299
|
-
createdBy:
|
|
7422
|
+
createdBy: text4(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
|
|
7300
7423
|
createdAt: integer3(provenanceRaw?.createdAt, `${path}.provenance.createdAt`, errors, 0),
|
|
7301
|
-
updatedBy:
|
|
7424
|
+
updatedBy: text4(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
|
|
7302
7425
|
updatedAt: integer3(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
|
|
7303
7426
|
...promotedFrom ? { promotedFrom } : {}
|
|
7304
7427
|
};
|
|
@@ -7317,7 +7440,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
|
|
|
7317
7440
|
if (sourceRaw) unknownKeys2(sourceRaw, SOURCE_KEYS2, "document.source", errors);
|
|
7318
7441
|
const sourceType = sourceRaw?.type;
|
|
7319
7442
|
if (sourceType !== "chapter" && sourceType !== "built_not_found") errors.push("document.source.type must be chapter or built_not_found");
|
|
7320
|
-
const sourceAppId =
|
|
7443
|
+
const sourceAppId = text4(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
|
|
7321
7444
|
if (sourceAppId && !APP_ID.test(sourceAppId)) errors.push("document.source.appId must be a lowercase app slug");
|
|
7322
7445
|
if (options.sourceType && sourceType !== options.sourceType) errors.push(`document.source.type must be ${options.sourceType}`);
|
|
7323
7446
|
if (options.sourceAppId && sourceAppId !== options.sourceAppId) errors.push(`document.source.appId must be ${options.sourceAppId}`);
|
|
@@ -7325,8 +7448,8 @@ function validateBuilderLibraryDocument(value, options = {}) {
|
|
|
7325
7448
|
if (raw2.target !== void 0 && !targetRaw) errors.push("document.target must be an object");
|
|
7326
7449
|
if (targetRaw) unknownKeys2(targetRaw, TARGET_KEYS2, "document.target", errors);
|
|
7327
7450
|
const target = targetRaw ? {
|
|
7328
|
-
appId:
|
|
7329
|
-
environment:
|
|
7451
|
+
appId: text4(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
|
|
7452
|
+
environment: text4(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
|
|
7330
7453
|
} : void 0;
|
|
7331
7454
|
if (options.targetAppId && target?.appId !== options.targetAppId) errors.push(`document.target.appId must be ${options.targetAppId}`);
|
|
7332
7455
|
if (!Array.isArray(raw2.resources)) errors.push("document.resources must be an array");
|
|
@@ -7421,9 +7544,9 @@ async function createBuilderLibraryEnvelope(document) {
|
|
|
7421
7544
|
}
|
|
7422
7545
|
|
|
7423
7546
|
// src/email-retirement.ts
|
|
7424
|
-
function isRetiredEmailCopy(subject,
|
|
7547
|
+
function isRetiredEmailCopy(subject, text7) {
|
|
7425
7548
|
const subjectCopy = subject.trimStart().replace(/^\[dev\]\s*/i, "");
|
|
7426
|
-
const bodyCopy =
|
|
7549
|
+
const bodyCopy = text7.trimStart().replace(/^\(dev redirect; original recipient: [^\r\n]*\)\r?\n\r?\n/, "");
|
|
7427
7550
|
return /^\(\s*unused\s*:/i.test(subjectCopy) || /^Unused\.\s*Switched off\b/i.test(bodyCopy);
|
|
7428
7551
|
}
|
|
7429
7552
|
|
|
@@ -7501,10 +7624,10 @@ function planDelivery(input) {
|
|
|
7501
7624
|
if (isRetiredEmailCopy(renderedSubject, renderedText)) return { deliver: false, reason: "template-retired" };
|
|
7502
7625
|
if ((0, import_crm5.validateEmailCopy)({ subject: renderedSubject, text: renderedText }, { allowedVariables: [] }).length) return { deliver: false, reason: "template-invalid" };
|
|
7503
7626
|
const subject = (redirect ? "[dev] " : "") + renderedSubject;
|
|
7504
|
-
const
|
|
7627
|
+
const text7 = redirect ? `(dev redirect; original recipient: ${input.to})
|
|
7505
7628
|
|
|
7506
7629
|
` + renderedText : renderedText;
|
|
7507
|
-
return { deliver: true, transport, to, subject, text:
|
|
7630
|
+
return { deliver: true, transport, to, subject, text: text7, redirected: redirect };
|
|
7508
7631
|
}
|
|
7509
7632
|
|
|
7510
7633
|
// src/email-readiness.ts
|
|
@@ -7778,7 +7901,7 @@ async function reconcileLifecycleDelivery(db, input, now) {
|
|
|
7778
7901
|
}
|
|
7779
7902
|
|
|
7780
7903
|
// src/payments.ts
|
|
7781
|
-
var
|
|
7904
|
+
var import_stripe12 = require("@odla-ai/stripe");
|
|
7782
7905
|
function paymentsReady(group, hasSecretKey) {
|
|
7783
7906
|
return Boolean(group.stripePublishableKey && group.stripePriceId && hasSecretKey);
|
|
7784
7907
|
}
|
|
@@ -7839,6 +7962,17 @@ function findGiftRef(obj) {
|
|
|
7839
7962
|
}
|
|
7840
7963
|
function normalizeWebhookEvent(event2, options = {}) {
|
|
7841
7964
|
const obj = event2.data?.object ?? {};
|
|
7965
|
+
if (event2.type.startsWith("charge.dispute.")) {
|
|
7966
|
+
const chargeId = typeof obj.charge === "string" ? obj.charge : String(obj.charge?.id ?? "");
|
|
7967
|
+
if (!/^ch_[A-Za-z0-9]+$/.test(chargeId) || typeof obj.id !== "string" || !obj.id) return { kind: "ignored", type: event2.type };
|
|
7968
|
+
return {
|
|
7969
|
+
kind: "disputed",
|
|
7970
|
+
chargeId,
|
|
7971
|
+
disputeId: obj.id,
|
|
7972
|
+
disputeStatus: typeof obj.status === "string" ? obj.status : "unknown",
|
|
7973
|
+
...typeof obj.payment_intent === "string" ? { paymentIntentId: obj.payment_intent } : {}
|
|
7974
|
+
};
|
|
7975
|
+
}
|
|
7842
7976
|
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
7977
|
const gift = findGiftRef(obj);
|
|
7844
7978
|
if (gift.giftIntentId) {
|
|
@@ -8149,7 +8283,7 @@ var ID4 = /^[a-z][a-z0-9-]{1,79}$/;
|
|
|
8149
8283
|
var PRICE = /^price_[A-Za-z0-9]{12,}$/;
|
|
8150
8284
|
var CURRENCY = /^[a-z]{3}$/;
|
|
8151
8285
|
var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
8152
|
-
var
|
|
8286
|
+
var text5 = (value, path, errors, max) => {
|
|
8153
8287
|
if (typeof value !== "string" || !value.trim()) {
|
|
8154
8288
|
errors.push(`${path} is required`);
|
|
8155
8289
|
return "";
|
|
@@ -8177,23 +8311,23 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
8177
8311
|
}
|
|
8178
8312
|
const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
|
|
8179
8313
|
for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
8180
|
-
const id11 =
|
|
8314
|
+
const id11 = text5(item.id, `${path}.id`, errors, 80);
|
|
8181
8315
|
if (id11 && !ID4.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
8182
8316
|
if (ids2.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
|
|
8183
8317
|
ids2.add(id11);
|
|
8184
8318
|
const targetRaw = record3(item.target);
|
|
8185
8319
|
let target = { type: "entitlement", entitlementKey: "invalid" };
|
|
8186
8320
|
if (targetRaw?.type === "tier") {
|
|
8187
|
-
const tierId =
|
|
8321
|
+
const tierId = text5(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
|
|
8188
8322
|
if (!tierIds.has(tierId)) errors.push(`${path}.target.tierId must reference a declared tier`);
|
|
8189
8323
|
target = { type: "tier", tierId };
|
|
8190
8324
|
} else if (targetRaw?.type === "entitlement") {
|
|
8191
|
-
const entitlementKey =
|
|
8325
|
+
const entitlementKey = text5(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
|
|
8192
8326
|
if (entitlementKey && !ID4.test(entitlementKey)) errors.push(`${path}.target.entitlementKey must be a lowercase kebab-case id`);
|
|
8193
8327
|
target = { type: "entitlement", entitlementKey };
|
|
8194
8328
|
} else errors.push(`${path}.target.type must be tier or entitlement`);
|
|
8195
8329
|
if (item.billingMode !== "one_time" && item.billingMode !== "subscription") errors.push(`${path}.billingMode must be one_time or subscription`);
|
|
8196
|
-
const stripePriceId =
|
|
8330
|
+
const stripePriceId = text5(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
|
|
8197
8331
|
if (stripePriceId && !PRICE.test(stripePriceId)) errors.push(`${path}.stripePriceId must be a real Stripe Price id`);
|
|
8198
8332
|
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
8333
|
const currency = typeof item.currency === "string" ? item.currency.trim().toLowerCase() : "";
|
|
@@ -8215,9 +8349,9 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
8215
8349
|
redemptionLifetimeHours: lifetime,
|
|
8216
8350
|
...activeFrom !== void 0 ? { activeFrom } : {},
|
|
8217
8351
|
...activeUntil !== void 0 ? { activeUntil } : {},
|
|
8218
|
-
title:
|
|
8219
|
-
description:
|
|
8220
|
-
ctaLabel:
|
|
8352
|
+
title: text5(item.title, `${path}.title`, errors, 160),
|
|
8353
|
+
description: text5(item.description, `${path}.description`, errors, 2e3),
|
|
8354
|
+
ctaLabel: text5(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
|
|
8221
8355
|
});
|
|
8222
8356
|
}
|
|
8223
8357
|
return errors.length ? { ok: false, errors } : { ok: true, value: { schemaVersion: 1, offers } };
|
|
@@ -8676,9 +8810,9 @@ function subAnnualCents(sub) {
|
|
|
8676
8810
|
}
|
|
8677
8811
|
|
|
8678
8812
|
// src/payments-stripe.ts
|
|
8679
|
-
var
|
|
8813
|
+
var import_stripe13 = require("@odla-ai/stripe");
|
|
8680
8814
|
async function stripeCall(sk, method, path, params, idempotencyKey) {
|
|
8681
|
-
return (0,
|
|
8815
|
+
return (0, import_stripe13.stripeRequest)(
|
|
8682
8816
|
{ secretKey: sk },
|
|
8683
8817
|
method,
|
|
8684
8818
|
path,
|
|
@@ -9300,10 +9434,10 @@ function boundedString(value, path, errors, max, required = false) {
|
|
|
9300
9434
|
errors.push(`${path} must be a string`);
|
|
9301
9435
|
return void 0;
|
|
9302
9436
|
}
|
|
9303
|
-
const
|
|
9304
|
-
if (required && !
|
|
9305
|
-
if (
|
|
9306
|
-
return
|
|
9437
|
+
const text7 = value.trim();
|
|
9438
|
+
if (required && !text7) errors.push(`${path} is required`);
|
|
9439
|
+
if (text7.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
|
|
9440
|
+
return text7;
|
|
9307
9441
|
}
|
|
9308
9442
|
function validateMembershipPageDocument(input, options = {}) {
|
|
9309
9443
|
const errors = [];
|
|
@@ -9352,8 +9486,8 @@ function validateMembershipPageDocument(input, options = {}) {
|
|
|
9352
9486
|
else {
|
|
9353
9487
|
if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);
|
|
9354
9488
|
benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {
|
|
9355
|
-
const
|
|
9356
|
-
return
|
|
9489
|
+
const text7 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
|
|
9490
|
+
return text7 ? [text7] : [];
|
|
9357
9491
|
});
|
|
9358
9492
|
}
|
|
9359
9493
|
}
|
|
@@ -9452,7 +9586,7 @@ var recordOf = (value) => value !== null && typeof value === "object" && !Array.
|
|
|
9452
9586
|
function unknownKeys3(value, allowed2, path, errors) {
|
|
9453
9587
|
for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
9454
9588
|
}
|
|
9455
|
-
function
|
|
9589
|
+
function text6(value, path, errors, max = 65536) {
|
|
9456
9590
|
if (typeof value !== "string" || !value.trim()) {
|
|
9457
9591
|
errors.push(`${path} is required`);
|
|
9458
9592
|
return "";
|
|
@@ -9475,11 +9609,11 @@ function tiersOf(value, errors) {
|
|
|
9475
9609
|
return [];
|
|
9476
9610
|
}
|
|
9477
9611
|
unknownKeys3(item, TIER_KEYS2, path, errors);
|
|
9478
|
-
const id11 =
|
|
9612
|
+
const id11 = text6(item.id, `${path}.id`, errors, 80);
|
|
9479
9613
|
if (id11 && !TIER.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
9480
9614
|
if (seen.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
|
|
9481
9615
|
seen.add(id11);
|
|
9482
|
-
const name =
|
|
9616
|
+
const name = text6(item.name, `${path}.name`, errors, 160);
|
|
9483
9617
|
if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
|
|
9484
9618
|
if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
|
|
9485
9619
|
if (!Number.isInteger(item.priceCents) || Number(item.priceCents) < 0 || Number(item.priceCents) > 1e8) {
|
|
@@ -9497,7 +9631,7 @@ function tiersOf(value, errors) {
|
|
|
9497
9631
|
if (item.interval !== void 0 && item.interval !== "year" && item.interval !== "month") {
|
|
9498
9632
|
errors.push(`${path}.interval must be year or month`);
|
|
9499
9633
|
}
|
|
9500
|
-
const blurb = item.blurb === void 0 ? void 0 :
|
|
9634
|
+
const blurb = item.blurb === void 0 ? void 0 : text6(item.blurb, `${path}.blurb`, errors, 4e3);
|
|
9501
9635
|
return [{
|
|
9502
9636
|
id: id11,
|
|
9503
9637
|
name,
|
|
@@ -9555,7 +9689,7 @@ function policyOf(value, errors, tiers2) {
|
|
|
9555
9689
|
const result = {};
|
|
9556
9690
|
for (const key3 of allowed2) {
|
|
9557
9691
|
if (input[key3] === void 0 && key3 !== "disclaimerText" && key3 !== "refundPolicyText") continue;
|
|
9558
|
-
result[key3] =
|
|
9692
|
+
result[key3] = text6(input[key3], `document.policy.${key3}`, errors);
|
|
9559
9693
|
}
|
|
9560
9694
|
const tierPolicies = {};
|
|
9561
9695
|
if (input.tierPolicies !== void 0) {
|
|
@@ -9570,8 +9704,8 @@ function policyOf(value, errors, tiers2) {
|
|
|
9570
9704
|
}
|
|
9571
9705
|
unknownKeys3(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
|
|
9572
9706
|
tierPolicies[id11] = {
|
|
9573
|
-
refundPolicyText:
|
|
9574
|
-
...policy2.trustCopy !== void 0 ? { trustCopy:
|
|
9707
|
+
refundPolicyText: text6(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
|
|
9708
|
+
...policy2.trustCopy !== void 0 ? { trustCopy: text6(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
|
|
9575
9709
|
};
|
|
9576
9710
|
}
|
|
9577
9711
|
}
|
|
@@ -9611,9 +9745,9 @@ function validateSignupControlDocument(input) {
|
|
|
9611
9745
|
if (!Number.isSafeInteger(root.generatedAt) || Number(root.generatedAt) < 1) errors.push("document.generatedAt must be epoch milliseconds");
|
|
9612
9746
|
const source = recordOf(root.source);
|
|
9613
9747
|
const target = recordOf(root.target);
|
|
9614
|
-
const appId =
|
|
9615
|
-
const chapterId =
|
|
9616
|
-
const environment =
|
|
9748
|
+
const appId = text6(source?.appId, "document.source.appId", errors, 63);
|
|
9749
|
+
const chapterId = text6(target?.chapterId, "document.target.chapterId", errors, 63);
|
|
9750
|
+
const environment = text6(target?.environment, "document.target.environment", errors, 63);
|
|
9617
9751
|
if (appId && !SLUG9.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
|
|
9618
9752
|
if (chapterId && !SLUG9.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
|
|
9619
9753
|
if (environment && !ENVIRONMENT.test(environment)) errors.push("document.target.environment is invalid");
|
|
@@ -9790,9 +9924,9 @@ function markdownBenefits(lines, path, errors) {
|
|
|
9790
9924
|
errors.push(`${path} line ${index2 + 1} must be a Markdown bullet`);
|
|
9791
9925
|
continue;
|
|
9792
9926
|
}
|
|
9793
|
-
const
|
|
9794
|
-
if (
|
|
9795
|
-
if (
|
|
9927
|
+
const text7 = (match[1] ?? "").trim();
|
|
9928
|
+
if (text7.length > 2e3) errors.push(`${path} line ${index2 + 1} must be at most 2,000 characters`);
|
|
9929
|
+
if (text7) benefits.push(text7);
|
|
9796
9930
|
}
|
|
9797
9931
|
if (benefits.length > MAX_BENEFITS2) errors.push(`${path} supports at most ${MAX_BENEFITS2} entries`);
|
|
9798
9932
|
return benefits.slice(0, MAX_BENEFITS2);
|