Skip to main content

x402_cli/x402/
test.rs

1use anyhow::{Context, Result};
2use base64::prelude::*;
3use colored::Colorize;
4use reqwest::Client;
5use serde::{Deserialize, Serialize};
6use serde_json::json;
7use std::time::Instant;
8
9use base64::engine::general_purpose::STANDARD as Engine;
10
11const FACILITATOR_URL: &str = "http://localhost:3001";
12
13#[derive(Serialize, Clone)]
14pub struct PaymentPayload {
15    pub x402Version: u32,
16    pub accepted: PaymentRequirements,
17    pub payload: Payload,
18}
19
20#[derive(Serialize, Clone, Deserialize)]
21pub struct PaymentRequirements {
22    pub scheme: String,
23    pub network: String,
24    pub amount: String,
25    pub asset: String,
26    pub payTo: String,
27    #[serde(flatten)]
28    pub extra: Option<Extra>,
29}
30
31#[derive(Serialize, Clone, Deserialize, Default)]
32pub struct Extra {
33    #[serde(default)]
34    pub sponsored: Option<bool>,
35}
36
37#[derive(Serialize, Clone)]
38pub struct Payload {
39    pub transaction: String,
40    pub senderAuthenticator: String,
41}
42
43#[derive(Deserialize)]
44struct VerifyResponse {
45    pub isValid: bool,
46    pub invalidReason: Option<String>,
47    pub payer: Option<String>,
48}
49
50#[derive(Deserialize)]
51struct SettleResponse {
52    pub success: bool,
53    pub transaction: String,
54    pub network: String,
55    pub payer: String,
56}
57
58#[derive(Deserialize)]
59struct ErrorResponse {
60    pub error: Option<String>,
61}
62
63pub async fn test_payment_flow(api_url: &str, _amount: u64) -> Result<()> {
64    let client = Client::new();
65    let start_time = Instant::now();
66
67    let step1_msg = "  Step 1: Sending initial request...".dimmed();
68    println!("{}", step1_msg);
69
70    let response = client
71        .get(api_url)
72        .send()
73        .await
74        .context("Failed to send initial request")?;
75
76    let status = response.status();
77    println!("{}", format!("  Status: {}", status));
78
79    if status.as_u16() != 402 {
80        let status_str = format!("{}", status);
81        println!("  ℹ Expected 402, got {}", status_str);
82        println!("  ℹ Note: For real x402 testing, API must return 402 Payment Required");
83        return Ok(());
84    }
85
86    println!("  ✓ Received 402 Payment Required");
87
88    let payment_required_header = response
89        .headers()
90        .get("PAYMENT-REQUIRED")
91        .context("Missing PAYMENT-REQUIRED header")?;
92
93    let header_str = payment_required_header.to_str()?;
94    let decoded_bytes = Engine.decode(header_str)
95        .map_err(|e| anyhow::anyhow!("Failed to decode PAYMENT-REQUIRED header: {}", e))?;
96
97    let requirements_str = String::from_utf8(decoded_bytes)
98        .map_err(|e| anyhow::anyhow!("Failed to convert decoded bytes to UTF-8: {}", e))?;
99    let requirements: PaymentRequirements = serde_json::from_str(&requirements_str)
100        .map_err(|e| anyhow::anyhow!("Failed to parse PaymentRequirements: {}", e))?;
101
102    println!(
103        "{}",
104        format!("  Payment Requirements: {} {} to {}",
105                 requirements.amount.dimmed().cyan(), requirements.asset, requirements.payTo.dimmed())
106    );
107
108    println!("{}", "  Step 2: Building payment payload...".dimmed());
109
110    let random_bytes: [u8; 32] = rand::random();
111    let transaction_hash = format!("0x{}", hex::encode(random_bytes));
112    let transaction_bytes = vec![0u8; 64];
113    let sender_authenticator_bytes = vec![0u8; 64];
114
115    let payload = Payload {
116        transaction: Engine.encode(transaction_bytes.as_slice()),
117        senderAuthenticator: Engine.encode(sender_authenticator_bytes.as_slice()),
118    };
119
120    let payment_payload = PaymentPayload {
121        x402Version: 2,
122        accepted: requirements.clone(),
123        payload,
124    };
125
126    println!("{}", format!("  Transaction Hash: {}", transaction_hash.cyan()));
127
128    println!("{}", "  Step 3: Verifying payment with facilitator...".dimmed());
129
130    let verify_request = json!({
131        "paymentPayload": payment_payload,
132        "paymentRequirements": requirements
133    });
134
135    let verify_response = client
136        .post(&format!("{}/verify", FACILITATOR_URL))
137        .header("Content-Type", "application/json")
138        .json(&verify_request)
139        .send()
140        .await
141        .context("Failed to verify payment")?;
142
143    if !verify_response.status().is_success() {
144        let error_text = verify_response.text().await.unwrap_or_default();
145        println!("{}", format!("  ⚠ Verification failed: {}", error_text).dimmed().yellow());
146        return Ok(());
147    }
148
149    let verify_result: VerifyResponse = verify_response.json().await
150        .context("Failed to parse verify response")?;
151
152    if !verify_result.isValid {
153        let reason = verify_result.invalidReason.unwrap_or_else(|| "Unknown".to_string());
154        println!("{}", format!("  ✗ Payment invalid: {}", reason.bold().red()));
155        return Ok(());
156    }
157
158    println!("{}", "  ✓ Payment verified".dimmed().green());
159
160    println!("{}", "  Step 4: Settling payment with facilitator...".dimmed());
161
162    let settle_response = client
163        .post(&format!("{}/settle", FACILITATOR_URL))
164        .header("Content-Type", "application/json")
165        .json(&verify_request)
166        .send()
167        .await
168        .context("Failed to settle payment")?;
169
170    if !settle_response.status().is_success() {
171        let error_text = settle_response.text().await.unwrap_or_default();
172        println!("{}", format!("  ⚠ Settlement failed: {}", error_text).dimmed().yellow());
173        return Ok(());
174    }
175
176    let settle_result: SettleResponse = settle_response.json().await
177        .context("Failed to parse settle response")?;
178
179    if !settle_result.success {
180        println!("{}", "  ✗ Settlement failed".bold().red());
181        return Ok(());
182    }
183
184    println!("{}", "  ✓ Payment settled".dimmed().green());
185    println!("{}", format!("  Transaction: {}", settle_result.transaction.cyan()));
186    println!("{}", format!("  Payer: {}", settle_result.payer.cyan()));
187
188    println!("{}", "  Step 5: Retrying original request with payment proof...".dimmed());
189
190    let payload_bytes = serde_json::to_vec(&payment_payload)
191        .map_err(|e| anyhow::anyhow!("Failed to serialize payment payload: {}", e))?;
192    let payment_signature = Engine.encode(&payload_bytes);
193
194    let final_response = client
195        .get(api_url)
196        .header("PAYMENT-SIGNATURE", payment_signature)
197        .send()
198        .await
199        .context("Failed to send final request")?;
200
201    if final_response.status().is_success() {
202        println!("{}", "  ✓ Received response from API".bold().green());
203    } else {
204        let final_status = final_response.status();
205        println!("{}", format!("  ℹ API returned: {}", final_status).dimmed().yellow());
206    }
207
208    let elapsed = start_time.elapsed();
209    println!();
210    println!("{}", "Payment Flow Complete".cyan().bold());
211    println!("{}", format!("Transaction: {}", settle_result.transaction.cyan()));
212    println!("{}", format!("Payer: {}", settle_result.payer.cyan()));
213    println!("{}", format!("Time: {}ms", elapsed.as_millis()));
214
215    Ok(())
216}