@@ -6,7 +6,7 @@ import { resolveContractAbi } from "../contract/actions/resolve-abi.js";
66import { getContract } from "../contract/contract.js" ;
77import { isPermitSupported } from "../extensions/erc20/__generated__/IERC20Permit/write/permit.js" ;
88import { isTransferWithAuthorizationSupported } from "../extensions/erc20/__generated__/USDC/write/transferWithAuthorization.js" ;
9- import { decodePayment } from "./encode.js" ;
9+ import { decodePayment , encodePaymentRequired } from "./encode.js" ;
1010import {
1111 networkToCaip2ChainId ,
1212 type RequestedPaymentPayload ,
@@ -16,6 +16,8 @@ import {
1616 type ERC20TokenAmount ,
1717 type PaymentArgs ,
1818 type PaymentRequiredResult ,
19+ type PaymentRequiredResultV1 ,
20+ type PaymentRequiredResultV2 ,
1921 type SupportedSignatureType ,
2022 x402Version ,
2123} from "./types.js" ;
@@ -27,6 +29,50 @@ type GetPaymentRequirementsResult = {
2729 decodedPayment : RequestedPaymentPayload ;
2830} ;
2931
32+ /**
33+ * Formats a payment required response in x402 v2 format (header-based)
34+ */
35+ function formatPaymentRequiredResponseV2 (
36+ paymentRequirements : RequestedPaymentRequirements [ ] ,
37+ error : string ,
38+ resourceUrl : string ,
39+ ) : PaymentRequiredResultV2 {
40+ const paymentRequired = {
41+ x402Version : 2 ,
42+ error,
43+ accepts : paymentRequirements ,
44+ resource : { url : resourceUrl } ,
45+ } ;
46+
47+ return {
48+ status : 402 ,
49+ responseHeaders : {
50+ "PAYMENT-REQUIRED" : encodePaymentRequired ( paymentRequired ) ,
51+ } ,
52+ responseBody : { } as Record < string , never > ,
53+ } ;
54+ }
55+
56+ /**
57+ * Formats a payment required response in x402 v1 format (body-based)
58+ */
59+ function formatPaymentRequiredResponseV1 (
60+ paymentRequirements : RequestedPaymentRequirements [ ] ,
61+ error : string ,
62+ ) : PaymentRequiredResultV1 {
63+ return {
64+ status : 402 ,
65+ responseHeaders : {
66+ "Content-Type" : "application/json" ,
67+ } ,
68+ responseBody : {
69+ x402Version : 1 ,
70+ error,
71+ accepts : paymentRequirements ,
72+ } ,
73+ } ;
74+ }
75+
3076/**
3177 * Decodes a payment request and returns the payment requirements, selected payment requirements, and decoded payment
3278 * @param args
@@ -35,37 +81,36 @@ type GetPaymentRequirementsResult = {
3581export async function decodePaymentRequest (
3682 args : PaymentArgs ,
3783) : Promise < GetPaymentRequirementsResult | PaymentRequiredResult > {
38- const { facilitator, routeConfig = { } , paymentData } = args ;
84+ const { facilitator, routeConfig = { } , paymentData, resourceUrl } = args ;
3985 const { errorMessages } = routeConfig ;
4086
87+ // facilitator.accepts() returns v1 format from API - extract payment requirements
4188 const paymentRequirementsResult = await facilitator . accepts ( args ) ;
89+ const paymentRequirements = paymentRequirementsResult . responseBody . accepts ;
4290
43- // Check for payment header, if none, return the payment requirements
91+ // Check for payment header, if none, return the payment requirements in v2 format (default)
4492 if ( ! paymentData ) {
45- return paymentRequirementsResult ;
93+ return formatPaymentRequiredResponseV2 (
94+ paymentRequirements ,
95+ "Payment required" ,
96+ resourceUrl ,
97+ ) ;
4698 }
4799
48- const paymentRequirements = paymentRequirementsResult . responseBody . accepts ;
49-
50100 // decode b64 payment
51101 let decodedPayment : RequestedPaymentPayload ;
52102 try {
53103 decodedPayment = decodePayment ( paymentData ) ;
54- decodedPayment . x402Version = x402Version ;
104+ // Preserve version provided by the client, default to the current protocol version if missing
105+ decodedPayment . x402Version ??= x402Version ;
55106 } catch ( error ) {
56- return {
57- status : 402 ,
58- responseHeaders : {
59- "Content-Type" : "application/json" ,
60- } ,
61- responseBody : {
62- x402Version,
63- error :
64- errorMessages ?. invalidPayment ||
65- ( error instanceof Error ? error . message : "Invalid payment" ) ,
66- accepts : paymentRequirements ,
67- } ,
68- } ;
107+ // Decode error - default to v2 format since we can't determine client version
108+ return formatPaymentRequiredResponseV2 (
109+ paymentRequirements ,
110+ errorMessages ?. invalidPayment ||
111+ ( error instanceof Error ? error . message : "Invalid payment" ) ,
112+ resourceUrl ,
113+ ) ;
69114 }
70115
71116 const selectedPaymentRequirements = paymentRequirements . find (
@@ -75,19 +120,19 @@ export async function decodePaymentRequest(
75120 networkToCaip2ChainId ( decodedPayment . network ) ,
76121 ) ;
77122 if ( ! selectedPaymentRequirements ) {
78- return {
79- status : 402 ,
80- responseHeaders : {
81- "Content-Type" : "application/json" ,
82- } ,
83- responseBody : {
84- x402Version ,
85- error :
86- errorMessages ?. noMatchingRequirements ||
87- "Unable to find matching payment requirements" ,
88- accepts : paymentRequirements ,
89- } ,
90- } ;
123+ // Use the client's version for the response format
124+ const errorMessage =
125+ errorMessages ?. noMatchingRequirements ||
126+ "Unable to find matching payment requirements" ;
127+
128+ if ( decodedPayment . x402Version === 1 ) {
129+ return formatPaymentRequiredResponseV1 ( paymentRequirements , errorMessage ) ;
130+ }
131+ return formatPaymentRequiredResponseV2 (
132+ paymentRequirements ,
133+ errorMessage ,
134+ resourceUrl ,
135+ ) ;
91136 }
92137
93138 return {
0 commit comments