run402 4.10.2 → 4.11.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 +10 -6
- package/lib/pay.mjs +13 -8
- package/lib/pay.test.mjs +78 -3
- package/package.json +1 -1
- package/sdk/dist/index.d.ts +1 -1
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +1 -1
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/namespaces/deploy.js +11 -1
- package/sdk/dist/namespaces/deploy.js.map +1 -1
- package/sdk/dist/namespaces/deploy.types.d.ts +2 -0
- package/sdk/dist/namespaces/deploy.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.types.js.map +1 -1
- package/sdk/dist/namespaces/pay.d.ts +141 -2
- package/sdk/dist/namespaces/pay.d.ts.map +1 -1
- package/sdk/dist/namespaces/pay.js +342 -2
- package/sdk/dist/namespaces/pay.js.map +1 -1
- package/sdk/dist/node/_paid-stack.d.ts +16 -0
- package/sdk/dist/node/_paid-stack.d.ts.map +1 -1
- package/sdk/dist/node/_paid-stack.js +11 -0
- package/sdk/dist/node/_paid-stack.js.map +1 -1
- package/sdk/dist/node/index.d.ts +1 -1
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +1 -1
- package/sdk/dist/node/index.js.map +1 -1
- package/sdk/dist/node/paid-fetch.d.ts +20 -1
- package/sdk/dist/node/paid-fetch.d.ts.map +1 -1
- package/sdk/dist/node/paid-fetch.js +336 -13
- package/sdk/dist/node/paid-fetch.js.map +1 -1
- package/sdk/dist/node/payment-attempts.d.ts +7 -1
- package/sdk/dist/node/payment-attempts.d.ts.map +1 -1
- package/sdk/dist/node/payment-attempts.js +14 -3
- package/sdk/dist/node/payment-attempts.js.map +1 -1
|
@@ -165,7 +165,7 @@ interface TrackedPaidFetchOptions {
|
|
|
165
165
|
createAttemptId?: () => string;
|
|
166
166
|
now?: () => string;
|
|
167
167
|
fetch?: FetchFn;
|
|
168
|
-
classifyPaymentResponse?: (response: Response) => Promise<"completed" | "failed" | "already_settled" | "ambiguous">;
|
|
168
|
+
classifyPaymentResponse?: (response: Response) => Promise<"completed" | "failed" | "already_settled" | "intent_pending" | "ambiguous">;
|
|
169
169
|
}
|
|
170
170
|
/** @internal Exported only for deterministic source-level tests; not re-exported by the package. */
|
|
171
171
|
export declare function checkBalanceAcrossProviders(clients: readonly RpcClient[], tokenAddress: string, walletAddress: string, network: string, options?: BalanceRetryOptions): Promise<bigint>;
|
|
@@ -185,6 +185,25 @@ export declare const __paidFetchInternals: {
|
|
|
185
185
|
};
|
|
186
186
|
interface X402BuyerFetchOptions extends TrackedPaidFetchOptions {
|
|
187
187
|
supportedNetworks: readonly string[];
|
|
188
|
+
payerAddresses?: readonly string[];
|
|
189
|
+
offerReceipt?: X402OfferReceiptRuntime;
|
|
190
|
+
verifyAtSeconds?: () => number;
|
|
191
|
+
}
|
|
192
|
+
interface X402OfferReceiptRuntime {
|
|
193
|
+
extractOffersFromPaymentRequired: (required: unknown) => unknown[];
|
|
194
|
+
decodeSignedOffers: (offers: unknown[]) => unknown[];
|
|
195
|
+
findAcceptsObjectFromSignedOffer: (offer: unknown, accepts: unknown[]) => unknown | undefined;
|
|
196
|
+
isEIP712SignedOffer: (offer: unknown) => boolean;
|
|
197
|
+
isEIP712SignedReceipt: (receipt: unknown) => boolean;
|
|
198
|
+
verifyOfferSignatureEIP712: (offer: unknown) => Promise<{
|
|
199
|
+
signer: `0x${string}`;
|
|
200
|
+
payload: Record<string, unknown>;
|
|
201
|
+
}>;
|
|
202
|
+
verifyReceiptSignatureEIP712: (receipt: unknown) => Promise<{
|
|
203
|
+
signer: `0x${string}`;
|
|
204
|
+
payload: Record<string, unknown>;
|
|
205
|
+
}>;
|
|
206
|
+
extractReceiptFromResponse: (response: Response) => unknown | undefined;
|
|
188
207
|
}
|
|
189
208
|
/**
|
|
190
209
|
* Receipt-bearing buyer orchestration over an already-configured x402 client.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paid-fetch.d.ts","sourceRoot":"","sources":["../../src/node/paid-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAiB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,EAGL,WAAW,EAEZ,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAEL,iBAAiB,
|
|
1
|
+
{"version":3,"file":"paid-fetch.d.ts","sourceRoot":"","sources":["../../src/node/paid-fetch.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,7 +20,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
20
20
|
import { createHash } from "node:crypto";
|
|
21
21
|
import { LocalError, PaymentAttemptError, Run402Error, } from "../errors.js";
|
|
22
22
|
import { PaidStackUnavailable, loadMppStack, loadX402Stack } from "./_paid-stack.js";
|
|
23
|
-
import { DEFAULT_PAYMENT_MAX_USD_MICROS, PaymentBuyerError, paymentExceedsMaxError, responseSignalsReplay, walletUnavailableError, } from "../namespaces/pay.js";
|
|
23
|
+
import { DEFAULT_PAYMENT_MAX_USD_MICROS, PaymentBuyerError, PaymentPolicyError, gatewayPaymentBuyerError, isTrustedRun402PaymentUrl, isTrustedRun402PendingResponse, payResponseMetadata, merchantReceiptRequiredError, paymentExceedsMaxError, readPaymentErrorEnvelope, responseSignalsReplay, walletUnavailableError, } from "../namespaces/pay.js";
|
|
24
24
|
import { attemptIdFromRequest, createFilePaymentAttemptStore, createPaymentAttemptId, hasPaymentAuthorization, requestSummary, withPaymentAttemptHeader, } from "./payment-attempts.js";
|
|
25
25
|
/**
|
|
26
26
|
* A machine-readable x402 balance-preflight failure.
|
|
@@ -369,6 +369,11 @@ export async function setupPaidFetch(options = {}) {
|
|
|
369
369
|
];
|
|
370
370
|
const buyer = createX402BuyerFetch(client, {
|
|
371
371
|
supportedNetworks,
|
|
372
|
+
payerAddresses: [
|
|
373
|
+
...(mainnetSigner ? [mainnetSigner.address] : []),
|
|
374
|
+
...(sepoliaSigner ? [sepoliaSigner.address] : []),
|
|
375
|
+
],
|
|
376
|
+
offerReceipt: stack.offerReceipt,
|
|
372
377
|
});
|
|
373
378
|
return withPayer(trackedFetch, {
|
|
374
379
|
source: options.paymentSigner ? "payment_signer" : resolvedAllowance.source,
|
|
@@ -499,6 +504,7 @@ export function createLazyPaidFetch(options = {}) {
|
|
|
499
504
|
payment: null,
|
|
500
505
|
outcome: "not_required",
|
|
501
506
|
replay: await responseSignalsReplay(response, payOptions.idempotencyKey !== undefined),
|
|
507
|
+
...payResponseMetadata(response),
|
|
502
508
|
};
|
|
503
509
|
}
|
|
504
510
|
if (configured?.payer.rail === "mpp") {
|
|
@@ -582,7 +588,7 @@ export function createX402BuyerFetch(client, options) {
|
|
|
582
588
|
if (firstResponse.status !== 402)
|
|
583
589
|
return firstResponse;
|
|
584
590
|
const required = await decodePaymentRequired(firstResponse);
|
|
585
|
-
const guarded = guardPaymentRequired(required, options.supportedNetworks, call.maxUsdMicros, request.url);
|
|
591
|
+
const guarded = await guardPaymentRequired(required, options.supportedNetworks, call.maxUsdMicros, request.url, call.requireReceipt, options.offerReceipt, options.verifyAtSeconds?.() ?? Math.floor(Date.now() / 1000));
|
|
586
592
|
let pending = creatingProofs.get(call.fingerprint);
|
|
587
593
|
if (!pending) {
|
|
588
594
|
pending = createProof(client, guarded)
|
|
@@ -600,6 +606,20 @@ export function createX402BuyerFetch(client, options) {
|
|
|
600
606
|
fetch: options.fetch,
|
|
601
607
|
async classifyPaymentResponse(response) {
|
|
602
608
|
const call = calls.getStore();
|
|
609
|
+
const envelope = await readPaymentErrorEnvelope(response);
|
|
610
|
+
if (call && isTrustedRun402PendingResponse({
|
|
611
|
+
requestUrl: call.requestUrl,
|
|
612
|
+
response,
|
|
613
|
+
envelope,
|
|
614
|
+
paymentBearing: true,
|
|
615
|
+
redirectsDisabled: true,
|
|
616
|
+
})) {
|
|
617
|
+
return "intent_pending";
|
|
618
|
+
}
|
|
619
|
+
if (call && envelope && trustedRun402ResponseOrigin(call.requestUrl, response) &&
|
|
620
|
+
gatewayPaymentBuyerError(response, envelope)) {
|
|
621
|
+
return "failed";
|
|
622
|
+
}
|
|
603
623
|
const failure = await upstreamFailure(response);
|
|
604
624
|
if (call?.replayedProof && isAlreadyUsedFailure(failure)) {
|
|
605
625
|
call.alreadySettled = true;
|
|
@@ -610,12 +630,15 @@ export function createX402BuyerFetch(client, options) {
|
|
|
610
630
|
});
|
|
611
631
|
return async (url, init, payOptions) => {
|
|
612
632
|
const maxUsdMicros = payOptions.maxUsdMicros ?? DEFAULT_PAYMENT_MAX_USD_MICROS;
|
|
613
|
-
const
|
|
633
|
+
const requireReceipt = payOptions.requireReceipt === true;
|
|
634
|
+
const fingerprint = await paymentRequestFingerprint(url, init, maxUsdMicros, requireReceipt);
|
|
614
635
|
const call = {
|
|
615
636
|
fingerprint,
|
|
637
|
+
requestUrl: url,
|
|
616
638
|
maxUsdMicros,
|
|
617
639
|
replayedProof: false,
|
|
618
640
|
alreadySettled: false,
|
|
641
|
+
requireReceipt,
|
|
619
642
|
};
|
|
620
643
|
try {
|
|
621
644
|
const response = await calls.run(call, () => tracked(url, init));
|
|
@@ -625,6 +648,7 @@ export function createX402BuyerFetch(client, options) {
|
|
|
625
648
|
payment: null,
|
|
626
649
|
outcome: "not_required",
|
|
627
650
|
replay: await responseSignalsReplay(response, payOptions.idempotencyKey !== undefined),
|
|
651
|
+
...payResponseMetadata(response),
|
|
628
652
|
};
|
|
629
653
|
}
|
|
630
654
|
if (call.alreadySettled) {
|
|
@@ -634,9 +658,16 @@ export function createX402BuyerFetch(client, options) {
|
|
|
634
658
|
payment: null,
|
|
635
659
|
outcome: "already_settled",
|
|
636
660
|
replay: true,
|
|
661
|
+
...payResponseMetadata(response),
|
|
637
662
|
};
|
|
638
663
|
}
|
|
639
664
|
if (!response.ok) {
|
|
665
|
+
const envelope = await readPaymentErrorEnvelope(response);
|
|
666
|
+
if (envelope && trustedRun402ResponseOrigin(url, response)) {
|
|
667
|
+
const gatewayError = gatewayPaymentBuyerError(response, envelope);
|
|
668
|
+
if (gatewayError)
|
|
669
|
+
throw gatewayError;
|
|
670
|
+
}
|
|
640
671
|
const failure = await upstreamFailure(response);
|
|
641
672
|
proofs.delete(fingerprint);
|
|
642
673
|
if (failure.code === "payment_insufficient_funds") {
|
|
@@ -650,14 +681,30 @@ export function createX402BuyerFetch(client, options) {
|
|
|
650
681
|
...(failure.x402Error ? { x402_error: failure.x402Error } : {}),
|
|
651
682
|
});
|
|
652
683
|
}
|
|
653
|
-
const payment = receiptFromResponse(response, call.proof.
|
|
684
|
+
const payment = await receiptFromResponse(response, call.proof, url, requireReceipt, options.offerReceipt, options.payerAddresses ?? []);
|
|
654
685
|
proofs.delete(fingerprint);
|
|
655
|
-
|
|
686
|
+
const result = {
|
|
656
687
|
response,
|
|
657
688
|
payment,
|
|
658
689
|
outcome: "settled",
|
|
659
690
|
replay: call.replayedProof,
|
|
691
|
+
nextActions: [],
|
|
692
|
+
...payResponseMetadata(response),
|
|
660
693
|
};
|
|
694
|
+
if (requireReceipt &&
|
|
695
|
+
payment.merchantReceipt.status !== "verified") {
|
|
696
|
+
const recovery = receiptRecovery(url, response, payOptions.idempotencyKey);
|
|
697
|
+
throw new PaymentPolicyError({
|
|
698
|
+
response,
|
|
699
|
+
result,
|
|
700
|
+
message: "The payment settled, but the promised merchant receipt could not be verified.",
|
|
701
|
+
fundsMoved: payment.fundsMoved,
|
|
702
|
+
mutationState: payment.delivery.status === "fulfilled" ? "committed" : "unknown",
|
|
703
|
+
safeToRetry: recovery.safeToRetry,
|
|
704
|
+
nextActions: recovery.nextActions,
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
return result;
|
|
661
708
|
}
|
|
662
709
|
catch (cause) {
|
|
663
710
|
if (cause instanceof PaymentBuyerError)
|
|
@@ -683,11 +730,12 @@ export function createX402BuyerFetch(client, options) {
|
|
|
683
730
|
}
|
|
684
731
|
};
|
|
685
732
|
}
|
|
686
|
-
async function createProof(client,
|
|
733
|
+
async function createProof(client, guarded) {
|
|
687
734
|
try {
|
|
688
|
-
const payload = await client.createPaymentPayload(required);
|
|
735
|
+
const payload = await client.createPaymentPayload(guarded.required);
|
|
689
736
|
return {
|
|
690
737
|
accepted: payload.accepted,
|
|
738
|
+
offer: guarded.verifiedOffers.find((offer) => sameRequirement(offer.requirement, payload.accepted)) ?? null,
|
|
691
739
|
headers: {
|
|
692
740
|
[payload.x402Version === 1 ? "X-PAYMENT" : "PAYMENT-SIGNATURE"]: encodeBase64Json(payload),
|
|
693
741
|
},
|
|
@@ -697,7 +745,7 @@ async function createProof(client, required) {
|
|
|
697
745
|
throw unwrapPolicyError(cause) ?? cause;
|
|
698
746
|
}
|
|
699
747
|
}
|
|
700
|
-
function guardPaymentRequired(required, supportedNetworks, maxUsdMicros, requestUrl) {
|
|
748
|
+
async function guardPaymentRequired(required, supportedNetworks, maxUsdMicros, requestUrl, requireReceipt, offerReceipt, verifyAtSeconds) {
|
|
701
749
|
let challengeUrl;
|
|
702
750
|
try {
|
|
703
751
|
challengeUrl = new URL(required.resource.url).toString();
|
|
@@ -731,7 +779,23 @@ function guardPaymentRequired(required, supportedNetworks, maxUsdMicros, request
|
|
|
731
779
|
const challenged = Math.min(...valid.map(({ amount }) => amount));
|
|
732
780
|
throw paymentExceedsMaxError(challenged, maxUsdMicros);
|
|
733
781
|
}
|
|
734
|
-
|
|
782
|
+
const accepted = withinLimit.map(({ accept }) => accept);
|
|
783
|
+
const verifiedOffers = offerReceipt
|
|
784
|
+
? await verifyDirectOffers(required, accepted, requestUrl, verifyAtSeconds, offerReceipt)
|
|
785
|
+
: [];
|
|
786
|
+
if (requireReceipt && verifiedOffers.length === 0) {
|
|
787
|
+
throw merchantReceiptRequiredError({
|
|
788
|
+
offered_requirements: accepted.length,
|
|
789
|
+
valid_wallet_rooted_offers: 0,
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
const eligible = requireReceipt
|
|
793
|
+
? accepted.filter((requirement) => verifiedOffers.some((offer) => sameRequirement(offer.requirement, requirement)))
|
|
794
|
+
: accepted;
|
|
795
|
+
return {
|
|
796
|
+
required: { ...required, accepts: eligible },
|
|
797
|
+
verifiedOffers,
|
|
798
|
+
};
|
|
735
799
|
}
|
|
736
800
|
function supportedPaymentAsset(network) {
|
|
737
801
|
if (network === "eip155:8453")
|
|
@@ -740,6 +804,83 @@ function supportedPaymentAsset(network) {
|
|
|
740
804
|
return USDC_SEPOLIA;
|
|
741
805
|
return null;
|
|
742
806
|
}
|
|
807
|
+
async function verifyDirectOffers(required, eligibleRequirements, requestUrl, verifyAtSeconds, runtime) {
|
|
808
|
+
let decoded;
|
|
809
|
+
try {
|
|
810
|
+
decoded = runtime.decodeSignedOffers(runtime.extractOffersFromPaymentRequired(required));
|
|
811
|
+
}
|
|
812
|
+
catch {
|
|
813
|
+
return [];
|
|
814
|
+
}
|
|
815
|
+
const verified = [];
|
|
816
|
+
for (const value of decoded) {
|
|
817
|
+
const offer = asRecord(value);
|
|
818
|
+
const signed = offer.signedOffer;
|
|
819
|
+
if (!runtime.isEIP712SignedOffer(signed))
|
|
820
|
+
continue;
|
|
821
|
+
const requirement = runtime.findAcceptsObjectFromSignedOffer(offer, eligibleRequirements);
|
|
822
|
+
if (!requirement)
|
|
823
|
+
continue;
|
|
824
|
+
try {
|
|
825
|
+
const checked = await runtime.verifyOfferSignatureEIP712(signed);
|
|
826
|
+
const payload = checked.payload;
|
|
827
|
+
const validUntil = safeInteger(payload.validUntil);
|
|
828
|
+
if (payload.version !== 1 ||
|
|
829
|
+
normalizedUrl(payload.resourceUrl) !== normalizedUrl(requestUrl) ||
|
|
830
|
+
payload.scheme !== requirement.scheme ||
|
|
831
|
+
payload.network !== requirement.network ||
|
|
832
|
+
!sameAddressOrText(payload.asset, requirement.asset) ||
|
|
833
|
+
payload.amount !== requirement.amount ||
|
|
834
|
+
!sameAddressOrText(payload.payTo, requirement.payTo) ||
|
|
835
|
+
validUntil === null ||
|
|
836
|
+
validUntil <= verifyAtSeconds ||
|
|
837
|
+
!sameAddressOrText(checked.signer, requirement.payTo)) {
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
verified.push({
|
|
841
|
+
signed,
|
|
842
|
+
decoded: offer,
|
|
843
|
+
requirement,
|
|
844
|
+
signer: checked.signer,
|
|
845
|
+
relationship: "direct",
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
catch {
|
|
849
|
+
// A malformed, invalid, delegated, or untrusted offer is ineligible.
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return verified;
|
|
853
|
+
}
|
|
854
|
+
function sameRequirement(left, right) {
|
|
855
|
+
return (left.scheme === right.scheme &&
|
|
856
|
+
left.network === right.network &&
|
|
857
|
+
sameAddressOrText(left.asset, right.asset) &&
|
|
858
|
+
left.amount === right.amount &&
|
|
859
|
+
sameAddressOrText(left.payTo, right.payTo));
|
|
860
|
+
}
|
|
861
|
+
function sameAddressOrText(left, right) {
|
|
862
|
+
if (typeof left !== "string" || typeof right !== "string")
|
|
863
|
+
return false;
|
|
864
|
+
return /^0x[0-9a-fA-F]{40}$/.test(left) &&
|
|
865
|
+
/^0x[0-9a-fA-F]{40}$/.test(right)
|
|
866
|
+
? left.toLowerCase() === right.toLowerCase()
|
|
867
|
+
: left === right;
|
|
868
|
+
}
|
|
869
|
+
function normalizedUrl(value) {
|
|
870
|
+
if (typeof value !== "string")
|
|
871
|
+
return null;
|
|
872
|
+
try {
|
|
873
|
+
return new URL(value).toString();
|
|
874
|
+
}
|
|
875
|
+
catch {
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
function safeInteger(value) {
|
|
880
|
+
return typeof value === "number" && Number.isSafeInteger(value)
|
|
881
|
+
? value
|
|
882
|
+
: null;
|
|
883
|
+
}
|
|
743
884
|
function atomicAmount(value) {
|
|
744
885
|
if (!/^\d+$/.test(value))
|
|
745
886
|
return null;
|
|
@@ -783,7 +924,8 @@ function isPaymentRequired(value) {
|
|
|
783
924
|
typeof accept.amount === "string" &&
|
|
784
925
|
typeof accept.payTo === "string"));
|
|
785
926
|
}
|
|
786
|
-
function receiptFromResponse(response,
|
|
927
|
+
async function receiptFromResponse(response, proof, url, requireReceipt, runtime, payerAddresses) {
|
|
928
|
+
const accepted = proof.accepted;
|
|
787
929
|
const header = response.headers.get("PAYMENT-RESPONSE") ?? response.headers.get("X-PAYMENT-RESPONSE");
|
|
788
930
|
if (!header) {
|
|
789
931
|
throw settlementError("The paid response omitted the standard PAYMENT-RESPONSE settlement receipt, so settlement cannot be reported faithfully.", "unknown", { response_status: response.status });
|
|
@@ -810,12 +952,148 @@ function receiptFromResponse(response, accepted, url) {
|
|
|
810
952
|
if (amount === null) {
|
|
811
953
|
throw settlementError("The selected payment amount cannot be represented as usd_micros.", "unknown");
|
|
812
954
|
}
|
|
955
|
+
const metadata = payResponseMetadata(response);
|
|
956
|
+
const settlementPayer = typeof record.payer === "string" && record.payer.length > 0
|
|
957
|
+
? record.payer
|
|
958
|
+
: null;
|
|
959
|
+
let receiptStatus = proof.offer
|
|
960
|
+
? "unavailable"
|
|
961
|
+
: "absent";
|
|
962
|
+
let signedReceipt = null;
|
|
963
|
+
let receiptIssuedAt = null;
|
|
964
|
+
let receiptSigner = null;
|
|
965
|
+
if (runtime) {
|
|
966
|
+
try {
|
|
967
|
+
signedReceipt = runtime.extractReceiptFromResponse(response) ?? null;
|
|
968
|
+
if (signedReceipt && !proof.offer) {
|
|
969
|
+
receiptStatus = "untrusted";
|
|
970
|
+
}
|
|
971
|
+
else if (signedReceipt && !runtime.isEIP712SignedReceipt(signedReceipt)) {
|
|
972
|
+
receiptStatus = "untrusted";
|
|
973
|
+
}
|
|
974
|
+
else if (signedReceipt && proof.offer) {
|
|
975
|
+
const checked = await runtime.verifyReceiptSignatureEIP712(signedReceipt);
|
|
976
|
+
const payload = checked.payload;
|
|
977
|
+
const issuedAt = safeInteger(payload.issuedAt);
|
|
978
|
+
const payerMatches = typeof payload.payer === "string" &&
|
|
979
|
+
[
|
|
980
|
+
...(settlementPayer ? [settlementPayer] : []),
|
|
981
|
+
...payerAddresses,
|
|
982
|
+
].some((payer) => sameAddressOrText(payload.payer, payer));
|
|
983
|
+
const transactionMatches = typeof payload.transaction === "string" &&
|
|
984
|
+
payload.transaction === record.transaction;
|
|
985
|
+
if (payload.version === 1 &&
|
|
986
|
+
normalizedUrl(payload.resourceUrl) === normalizedUrl(url) &&
|
|
987
|
+
payload.network === accepted.network &&
|
|
988
|
+
payerMatches &&
|
|
989
|
+
transactionMatches &&
|
|
990
|
+
issuedAt !== null &&
|
|
991
|
+
sameAddressOrText(checked.signer, proof.offer.signer) &&
|
|
992
|
+
sameAddressOrText(checked.signer, accepted.payTo)) {
|
|
993
|
+
receiptStatus = "verified";
|
|
994
|
+
receiptSigner = checked.signer;
|
|
995
|
+
receiptIssuedAt = new Date(issuedAt * 1000).toISOString();
|
|
996
|
+
}
|
|
997
|
+
else {
|
|
998
|
+
receiptStatus = "invalid";
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
catch {
|
|
1003
|
+
receiptStatus = signedReceipt ? "invalid" : receiptStatus;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
const fundsMoved = metadata.fundsMoved ?? true;
|
|
1007
|
+
const deliveryReplay = metadata.delivery === "replay" || metadata.deduplicated === true;
|
|
1008
|
+
const offerPayload = proof.offer?.decoded ?? null;
|
|
1009
|
+
const offerValidUntil = safeInteger(offerPayload?.validUntil);
|
|
1010
|
+
const noSignerAuthorizationExpiry = null;
|
|
813
1011
|
return {
|
|
814
1012
|
amount_usd_micros: amount,
|
|
815
1013
|
pay_to: accepted.payTo,
|
|
816
1014
|
network: record.network,
|
|
817
1015
|
tx_ref: record.transaction,
|
|
818
1016
|
url,
|
|
1017
|
+
paymentId: metadata.paymentId,
|
|
1018
|
+
amountUsdMicros: amount,
|
|
1019
|
+
asset: accepted.asset,
|
|
1020
|
+
payer: settlementPayer,
|
|
1021
|
+
payTo: accepted.payTo,
|
|
1022
|
+
transaction: record.transaction,
|
|
1023
|
+
resourceUrl: url,
|
|
1024
|
+
settlement: { status: "verified" },
|
|
1025
|
+
fundsMoved,
|
|
1026
|
+
deduplicated: metadata.deduplicated ?? false,
|
|
1027
|
+
delivery: {
|
|
1028
|
+
status: response.ok ? "fulfilled" : "failed",
|
|
1029
|
+
replay: deliveryReplay,
|
|
1030
|
+
},
|
|
1031
|
+
offer: {
|
|
1032
|
+
status: proof.offer ? "verified" : "absent",
|
|
1033
|
+
resourceUrl: typeof offerPayload?.resourceUrl === "string"
|
|
1034
|
+
? offerPayload.resourceUrl
|
|
1035
|
+
: null,
|
|
1036
|
+
validUntil: offerValidUntil === null
|
|
1037
|
+
? null
|
|
1038
|
+
: new Date(offerValidUntil * 1000).toISOString(),
|
|
1039
|
+
},
|
|
1040
|
+
merchantReceipt: {
|
|
1041
|
+
status: receiptStatus,
|
|
1042
|
+
claim: receiptStatus === "verified" ? "service_delivered" : null,
|
|
1043
|
+
issuedAt: receiptIssuedAt,
|
|
1044
|
+
},
|
|
1045
|
+
signerRelationship: {
|
|
1046
|
+
kind: receiptStatus === "verified"
|
|
1047
|
+
? proof.offer?.relationship ?? null
|
|
1048
|
+
: proof.offer
|
|
1049
|
+
? "unverified"
|
|
1050
|
+
: null,
|
|
1051
|
+
merchantRoot: proof.offer?.relationship === "direct" ? accepted.payTo : null,
|
|
1052
|
+
signer: receiptSigner ?? proof.offer?.signer ?? null,
|
|
1053
|
+
authorizationExpiresAt: noSignerAuthorizationExpiry,
|
|
1054
|
+
},
|
|
1055
|
+
policy: {
|
|
1056
|
+
requireReceipt,
|
|
1057
|
+
status: requireReceipt
|
|
1058
|
+
? receiptStatus === "verified"
|
|
1059
|
+
? "satisfied"
|
|
1060
|
+
: "unsatisfied"
|
|
1061
|
+
: "not_required",
|
|
1062
|
+
},
|
|
1063
|
+
evidence: {
|
|
1064
|
+
offer: proof.offer?.signed ?? null,
|
|
1065
|
+
merchantReceipt: signedReceipt,
|
|
1066
|
+
signerAuthorization: null,
|
|
1067
|
+
},
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
function receiptRecovery(url, response, idempotencyKey) {
|
|
1071
|
+
const recoverable = Boolean(idempotencyKey) &&
|
|
1072
|
+
isTrustedRun402PaymentUrl(url, { allowTestLocalhost: true }) &&
|
|
1073
|
+
response.headers.get("x-run402-merchant-evidence-state") ===
|
|
1074
|
+
"unavailable";
|
|
1075
|
+
if (recoverable) {
|
|
1076
|
+
return {
|
|
1077
|
+
safeToRetry: true,
|
|
1078
|
+
nextActions: [
|
|
1079
|
+
{
|
|
1080
|
+
type: "retry",
|
|
1081
|
+
request: "repeat_identical",
|
|
1082
|
+
reusePayer: true,
|
|
1083
|
+
reuseIdempotencyKey: true,
|
|
1084
|
+
why: "Repeat the identical request with the same payer and idempotency key to recover the original receipt without another settlement.",
|
|
1085
|
+
},
|
|
1086
|
+
],
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
return {
|
|
1090
|
+
safeToRetry: false,
|
|
1091
|
+
nextActions: [
|
|
1092
|
+
{
|
|
1093
|
+
type: "reconcile_payment",
|
|
1094
|
+
why: "Reconcile the existing settlement with the merchant; do not authorize a second payment.",
|
|
1095
|
+
},
|
|
1096
|
+
],
|
|
819
1097
|
};
|
|
820
1098
|
}
|
|
821
1099
|
async function upstreamFailure(response) {
|
|
@@ -917,7 +1195,7 @@ function challengeNetworks(response) {
|
|
|
917
1195
|
return [];
|
|
918
1196
|
}
|
|
919
1197
|
}
|
|
920
|
-
async function paymentRequestFingerprint(url, init, maxUsdMicros) {
|
|
1198
|
+
async function paymentRequestFingerprint(url, init, maxUsdMicros, requireReceipt) {
|
|
921
1199
|
let request;
|
|
922
1200
|
try {
|
|
923
1201
|
request = new Request(url, init);
|
|
@@ -943,7 +1221,14 @@ async function paymentRequestFingerprint(url, init, maxUsdMicros) {
|
|
|
943
1221
|
throw settlementError("The paid request body cannot be replayed safely.", false, {}, cause);
|
|
944
1222
|
}
|
|
945
1223
|
return createHash("sha256")
|
|
946
|
-
.update(JSON.stringify({
|
|
1224
|
+
.update(JSON.stringify({
|
|
1225
|
+
method: request.method,
|
|
1226
|
+
url: request.url,
|
|
1227
|
+
headers,
|
|
1228
|
+
bodyHash,
|
|
1229
|
+
maxUsdMicros,
|
|
1230
|
+
requireReceipt,
|
|
1231
|
+
}))
|
|
947
1232
|
.digest("hex");
|
|
948
1233
|
}
|
|
949
1234
|
function encodeBase64Json(value) {
|
|
@@ -1111,11 +1396,23 @@ export function createTrackedX402Fetch(wrapFetchWithPayment, client, opts = {})
|
|
|
1111
1396
|
: await opts.classifyPaymentResponse?.(response) ?? "ambiguous";
|
|
1112
1397
|
const completed = classification === "completed" || classification === "already_settled";
|
|
1113
1398
|
const failed = classification === "failed";
|
|
1399
|
+
const intentPending = classification === "intent_pending";
|
|
1400
|
+
const retryAfter = retryAfterSeconds(response);
|
|
1114
1401
|
writeRecordBestEffort(context, store, {
|
|
1115
|
-
state: completed ? "completed" : failed ? "failed" : "ambiguous",
|
|
1402
|
+
state: completed ? "completed" : failed ? "failed" : intentPending ? "intent_pending" : "ambiguous",
|
|
1116
1403
|
mutation_state: completed ? "completed" : failed ? "not_started" : "ambiguous",
|
|
1117
1404
|
response_status: response.status,
|
|
1405
|
+
...(intentPending ? {
|
|
1406
|
+
last_error_code: "PAYMENT_INTENT_PENDING",
|
|
1407
|
+
...(nonEmptyHeader(response, "x-run402-payment-id")
|
|
1408
|
+
? { payment_id: nonEmptyHeader(response, "x-run402-payment-id") }
|
|
1409
|
+
: {}),
|
|
1410
|
+
intent_state: "pending",
|
|
1411
|
+
...(retryAfter !== null ? { retry_after_seconds: retryAfter } : {}),
|
|
1412
|
+
} : {}),
|
|
1118
1413
|
});
|
|
1414
|
+
if (intentPending)
|
|
1415
|
+
return response;
|
|
1119
1416
|
if (!completed && !failed) {
|
|
1120
1417
|
throw new PaymentAttemptError({
|
|
1121
1418
|
code: "X402_PAYMENT_OUTCOME_AMBIGUOUS",
|
|
@@ -1225,6 +1522,9 @@ function claimIntent(context, store, now) {
|
|
|
1225
1522
|
method: context.request.method,
|
|
1226
1523
|
origin: context.request.origin,
|
|
1227
1524
|
path_sha256: context.request.path_sha256,
|
|
1525
|
+
...(context.request.caller_key_sha256
|
|
1526
|
+
? { caller_key_sha256: context.request.caller_key_sha256 }
|
|
1527
|
+
: {}),
|
|
1228
1528
|
created_at: context.createdAt,
|
|
1229
1529
|
updated_at: updatedAt,
|
|
1230
1530
|
};
|
|
@@ -1239,6 +1539,29 @@ function claimIntent(context, store, now) {
|
|
|
1239
1539
|
throw new AttemptJournalWriteError(cause);
|
|
1240
1540
|
}
|
|
1241
1541
|
}
|
|
1542
|
+
function trustedRun402ResponseOrigin(requestUrl, response) {
|
|
1543
|
+
if (!response.url || response.redirected)
|
|
1544
|
+
return false;
|
|
1545
|
+
try {
|
|
1546
|
+
const request = new URL(requestUrl);
|
|
1547
|
+
const returned = new URL(response.url);
|
|
1548
|
+
return request.origin === returned.origin && isTrustedRun402PaymentUrl(request);
|
|
1549
|
+
}
|
|
1550
|
+
catch {
|
|
1551
|
+
return false;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
function retryAfterSeconds(response) {
|
|
1555
|
+
const raw = response.headers.get("retry-after");
|
|
1556
|
+
if (!raw || !/^\d+$/.test(raw))
|
|
1557
|
+
return null;
|
|
1558
|
+
const seconds = Number(raw);
|
|
1559
|
+
return Number.isSafeInteger(seconds) ? seconds : null;
|
|
1560
|
+
}
|
|
1561
|
+
function nonEmptyHeader(response, name) {
|
|
1562
|
+
const value = response.headers.get(name)?.trim();
|
|
1563
|
+
return value ? value : null;
|
|
1564
|
+
}
|
|
1242
1565
|
function writeRecord(context, store, patch) {
|
|
1243
1566
|
if (!context.record)
|
|
1244
1567
|
throw new AttemptJournalWriteError("payment intent missing");
|