99 "net/http"
1010
1111 "github.com/mark3labs/x402-go"
12+ "github.com/mark3labs/x402-go/facilitator"
1213)
1314
1415// X402Handler wraps an MCP HTTP handler and adds x402 payment verification
@@ -139,45 +140,7 @@ func (h *X402Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
139140 return
140141 }
141142
142- // Payment verified - settle if not verify-only mode
143- var settleResp * x402.SettlementResponse
144- if ! h .config .VerifyOnly {
145- settleCtx , settleCancel := context .WithTimeout (r .Context (), x402 .DefaultTimeouts .SettleTimeout )
146- defer settleCancel ()
147-
148- settleResp , err = h .facilitator .Settle (settleCtx , payment , * requirement )
149- if err != nil {
150- if h .config .Verbose {
151- fmt .Printf ("Payment settlement failed: %v\n " , err )
152- }
153- // Return error with settlement response in error data
154- errorData := map [string ]interface {}{
155- "x402/payment-response" : map [string ]interface {}{
156- "success" : false ,
157- "network" : payment .Network ,
158- "payer" : verifyResp .Payer ,
159- "errorReason" : err .Error (),
160- },
161- }
162- h .writeError (w , jsonrpcReq .ID , - 32603 , fmt .Sprintf ("Settlement failed: %v" , err ), errorData )
163- return
164- }
165-
166- if ! settleResp .Success {
167- if h .config .Verbose {
168- fmt .Printf ("Payment settlement unsuccessful: %s\n " , settleResp .ErrorReason )
169- }
170- // Return error with settlement response in error data
171- errorData := map [string ]interface {}{
172- "x402/payment-response" : settleResp ,
173- }
174- h .writeError (w , jsonrpcReq .ID , - 32603 , fmt .Sprintf ("Settlement unsuccessful: %s" , settleResp .ErrorReason ), errorData )
175- return
176- }
177- }
178-
179- // Payment successful - forward request and inject settlement response in result
180- h .forwardWithSettlementResponse (w , r , bodyBytes , jsonrpcReq .ID , settleResp )
143+ h .forwardAndSettle (w , r , bodyBytes , jsonrpcReq .ID , payment , requirement , verifyResp )
181144}
182145
183146// checkPaymentRequired checks if a tool requires payment
@@ -244,8 +207,8 @@ func (h *X402Handler) sendPaymentRequiredError(w http.ResponseWriter, id interfa
244207 h .writeError (w , id , 402 , "Payment required" , errorData )
245208}
246209
247- // forwardWithSettlementResponse forwards the request and injects settlement response in result._meta
248- func (h * X402Handler ) forwardWithSettlementResponse (w http.ResponseWriter , r * http.Request , requestBody []byte , requestID interface {}, settleResp * x402.SettlementResponse ) {
210+ // forwardAndSettle executes the mcpHandler and on success, settles the payment and injects settlement response in result._meta
211+ func (h * X402Handler ) forwardAndSettle (w http.ResponseWriter , r * http.Request , requestBody []byte , requestID interface {}, payment * x402.PaymentPayload , requirement * x402. PaymentRequirement , verifyResp * facilitator. VerifyResponse ) {
249212 // Create a response recorder to capture the MCP handler's response
250213 recorder := & responseRecorder {
251214 headerMap : make (http.Header ),
@@ -267,24 +230,95 @@ func (h *X402Handler) forwardWithSettlementResponse(w http.ResponseWriter, r *ht
267230 }
268231
269232 if err := json .Unmarshal (recorder .body .Bytes (), & jsonrpcResp ); err != nil {
233+ if h .config .Verbose {
234+ fmt .Printf ("Failed to parse MCP response, skipping settlement: %v\n " , err )
235+ }
270236 // If we can't parse response, just forward it as-is
237+ for k , v := range recorder .headerMap {
238+ w .Header ()[k ] = v
239+ }
240+ w .WriteHeader (recorder .statusCode )
241+ _ , _ = w .Write (recorder .body .Bytes ())
242+ return
243+ }
244+
245+ if jsonrpcResp .Error != nil {
246+ if h .config .Verbose {
247+ fmt .Println ("Execution failed. Payment will not be settled." )
248+ }
249+ for k , v := range recorder .headerMap {
250+ w .Header ()[k ] = v
251+ }
271252 w .WriteHeader (recorder .statusCode )
272253 _ , _ = w .Write (recorder .body .Bytes ())
273254 return
274255 }
275256
276- // Only inject settlement response if there's a result (not an error)
277- if jsonrpcResp .Error == nil && jsonrpcResp .Result != nil && settleResp != nil {
257+ var settleResp * x402.SettlementResponse
258+ // Settle if not verify-only mode
259+ if ! h .config .VerifyOnly {
260+ if h .config .Verbose {
261+ fmt .Println ("Execution successful. Settling payment." )
262+ }
263+ settleCtx , settleCancel := context .WithTimeout (r .Context (), x402 .DefaultTimeouts .SettleTimeout )
264+ defer settleCancel ()
265+
266+ var err error
267+ settleResp , err = h .facilitator .Settle (settleCtx , payment , * requirement )
268+ if err != nil || settleResp == nil || ! settleResp .Success {
269+ reason := "unknown reason"
270+ if err != nil {
271+ reason = err .Error ()
272+ } else if settleResp != nil {
273+ reason = settleResp .ErrorReason
274+ }
275+
276+ errorMsg := fmt .Sprintf ("Settlement failed: %v" , reason )
277+ if h .config .Verbose {
278+ fmt .Println (errorMsg )
279+ }
280+ payer := ""
281+ if verifyResp != nil {
282+ payer = verifyResp .Payer
283+ }
284+ errorData := map [string ]interface {}{
285+ "x402/payment-response" : x402.SettlementResponse {
286+ Success : false ,
287+ Network : payment .Network ,
288+ Payer : payer ,
289+ ErrorReason : reason ,
290+ },
291+ }
292+ h .writeError (w , requestID , - 32603 , errorMsg , errorData )
293+ return
294+ } else if h .config .Verbose {
295+ fmt .Printf ("Payment successful: %s\n " , settleResp .Transaction )
296+ }
297+ }
298+
299+ if jsonrpcResp .Result != nil {
278300 var result map [string ]interface {}
279301 if err := json .Unmarshal (jsonrpcResp .Result , & result ); err == nil {
280- // Get or create _meta
281302 meta , ok := result ["_meta" ].(map [string ]interface {})
282303 if ! ok {
283304 meta = make (map [string ]interface {})
284305 }
285306
286307 // Add settlement response
287- meta ["x402/payment-response" ] = settleResp
308+ if settleResp != nil {
309+ meta ["x402/payment-response" ] = settleResp
310+ } else {
311+ payer := ""
312+ if verifyResp != nil {
313+ payer = verifyResp .Payer
314+ }
315+ // In verify-only mode: Success=false indicates settlement was skipped (not attempted), not that it failed.
316+ meta ["x402/payment-response" ] = x402.SettlementResponse {
317+ Success : false ,
318+ Network : payment .Network ,
319+ Payer : payer ,
320+ }
321+ }
288322 result ["_meta" ] = meta
289323
290324 // Re-marshal result
0 commit comments