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}