1use std::fmt::Display;
4
5use http::{HeaderName, HeaderValue, StatusCode};
6use x402_core::{
7 transport::{Accepts, PaymentRequired, PaymentResource},
8 types::{Base64EncodedHeader, Extension, Record, X402V2},
9};
10
11#[derive(Debug, Clone)]
13pub struct ErrorResponse {
14 pub status: StatusCode,
16 pub header: ErrorResponseHeader,
18 pub body: Box<PaymentRequired>,
22}
23
24impl Display for ErrorResponse {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 f.write_str("payment required")
27 }
28}
29
30impl ErrorResponse {
31 pub fn payment_required(
33 resource: PaymentResource,
34 accepts: Accepts,
35 extensions: Record<Extension>,
36 ) -> ErrorResponse {
37 let payment_required = PaymentRequired {
38 x402_version: X402V2,
39 error: "PAYMENT-SIGNATURE header is required".to_string(),
40 resource,
41 accepts,
42 extensions,
43 };
44
45 let header = Base64EncodedHeader::try_from(payment_required.clone()).unwrap_or(
46 Base64EncodedHeader("Failed to encode base64 PaymentRequired payload".to_string()),
47 );
48
49 ErrorResponse {
50 status: StatusCode::PAYMENT_REQUIRED,
51 header: ErrorResponseHeader::PaymentRequired(header),
52 body: Box::new(payment_required),
53 }
54 }
55
56 pub fn invalid_payment(
58 reason: impl Display,
59 resource: PaymentResource,
60 accepts: Accepts,
61 extensions: Record<Extension>,
62 ) -> ErrorResponse {
63 let payment_required = PaymentRequired {
64 x402_version: X402V2,
65 error: reason.to_string(),
66 resource,
67 accepts,
68 extensions,
69 };
70
71 let header = Base64EncodedHeader::try_from(payment_required.clone()).unwrap_or(
72 Base64EncodedHeader("Failed to encode base64 PaymentRequired payload".to_string()),
73 );
74
75 ErrorResponse {
76 status: StatusCode::BAD_REQUEST,
77 header: ErrorResponseHeader::PaymentResponse(header),
78 body: Box::new(payment_required),
79 }
80 }
81
82 pub fn payment_failed(
84 reason: impl Display,
85 resource: PaymentResource,
86 accepts: Accepts,
87 extensions: Record<Extension>,
88 ) -> ErrorResponse {
89 let payment_required = PaymentRequired {
90 x402_version: X402V2,
91 error: reason.to_string(),
92 resource,
93 accepts,
94 extensions,
95 };
96
97 let header = Base64EncodedHeader::try_from(payment_required.clone()).unwrap_or(
98 Base64EncodedHeader("Failed to encode base64 PaymentRequired payload".to_string()),
99 );
100
101 ErrorResponse {
102 status: StatusCode::PAYMENT_REQUIRED,
103 header: ErrorResponseHeader::PaymentResponse(header),
104 body: Box::new(payment_required),
105 }
106 }
107
108 pub fn server_error(
110 reason: impl Display,
111 resource: PaymentResource,
112 accepts: Accepts,
113 extensions: Record<Extension>,
114 ) -> ErrorResponse {
115 let payment_required = PaymentRequired {
116 x402_version: X402V2,
117 error: reason.to_string(),
118 resource,
119 accepts,
120 extensions,
121 };
122
123 let header = Base64EncodedHeader::try_from(payment_required.clone()).unwrap_or(
124 Base64EncodedHeader("Failed to encode base64 PaymentRequired payload".to_string()),
125 );
126
127 ErrorResponse {
128 status: StatusCode::INTERNAL_SERVER_ERROR,
129 header: ErrorResponseHeader::PaymentResponse(header),
130 body: Box::new(payment_required),
131 }
132 }
133}
134
135#[derive(Debug, Clone)]
137pub enum ErrorResponseHeader {
138 PaymentRequired(Base64EncodedHeader),
140 PaymentResponse(Base64EncodedHeader),
142}
143
144impl ErrorResponseHeader {
145 pub fn header_value(self) -> Option<(HeaderName, HeaderValue)> {
149 match self {
150 ErrorResponseHeader::PaymentRequired(Base64EncodedHeader(s)) => {
151 HeaderValue::from_str(&s)
152 .ok()
153 .map(|v| (HeaderName::from_static("payment-required"), v))
154 }
155 ErrorResponseHeader::PaymentResponse(Base64EncodedHeader(s)) => {
156 HeaderValue::from_str(&s)
157 .ok()
158 .map(|v| (HeaderName::from_static("payment-response"), v))
159 }
160 }
161 }
162}
163
164#[cfg(feature = "axum")]
165impl axum::response::IntoResponse for ErrorResponse {
166 fn into_response(self) -> axum::response::Response {
167 let mut response = (self.status, axum::extract::Json(self.body)).into_response();
168 if let Some((name, val)) = self.header.header_value() {
169 response.headers_mut().insert(name, val);
170 }
171 response
172 }
173}
174
175#[cfg(feature = "actix-web")]
176impl ErrorResponse {
177 fn actix_header(&self) -> (&'static str, &str) {
178 match &self.header {
179 ErrorResponseHeader::PaymentRequired(base64_encoded_header) => {
180 ("payment-required", &base64_encoded_header.0)
181 }
182 ErrorResponseHeader::PaymentResponse(base64_encoded_header) => {
183 ("payment-response", &base64_encoded_header.0)
184 }
185 }
186 }
187}
188
189#[cfg(feature = "actix-web")]
190impl actix_web::ResponseError for ErrorResponse {
191 fn status_code(&self) -> actix_web::http::StatusCode {
192 actix_web::http::StatusCode::from_u16(self.status.as_u16()).unwrap()
193 }
194
195 fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> {
196 actix_web::HttpResponseBuilder::new(self.status_code())
197 .insert_header(self.actix_header())
198 .json(&self.body)
199 }
200}