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 ErrorResponse {
25 pub fn payment_required(
27 resource: PaymentResource,
28 accepts: Accepts,
29 extensions: Record<Extension>,
30 ) -> ErrorResponse {
31 let payment_required = PaymentRequired {
32 x402_version: X402V2,
33 error: "PAYMENT-SIGNATURE header is required".to_string(),
34 resource,
35 accepts,
36 extensions,
37 };
38
39 let header = Base64EncodedHeader::try_from(payment_required.clone()).unwrap_or(
40 Base64EncodedHeader("Failed to encode base64 PaymentRequired payload".to_string()),
41 );
42
43 ErrorResponse {
44 status: StatusCode::PAYMENT_REQUIRED,
45 header: ErrorResponseHeader::PaymentRequired(header),
46 body: Box::new(payment_required),
47 }
48 }
49
50 pub fn invalid_payment(
52 reason: impl Display,
53 resource: PaymentResource,
54 accepts: Accepts,
55 extensions: Record<Extension>,
56 ) -> ErrorResponse {
57 let payment_required = PaymentRequired {
58 x402_version: X402V2,
59 error: reason.to_string(),
60 resource,
61 accepts,
62 extensions,
63 };
64
65 let header = Base64EncodedHeader::try_from(payment_required.clone()).unwrap_or(
66 Base64EncodedHeader("Failed to encode base64 PaymentRequired payload".to_string()),
67 );
68
69 ErrorResponse {
70 status: StatusCode::BAD_REQUEST,
71 header: ErrorResponseHeader::PaymentResponse(header),
72 body: Box::new(payment_required),
73 }
74 }
75
76 pub fn payment_failed(
78 reason: impl Display,
79 resource: PaymentResource,
80 accepts: Accepts,
81 extensions: Record<Extension>,
82 ) -> ErrorResponse {
83 let payment_required = PaymentRequired {
84 x402_version: X402V2,
85 error: reason.to_string(),
86 resource,
87 accepts,
88 extensions,
89 };
90
91 let header = Base64EncodedHeader::try_from(payment_required.clone()).unwrap_or(
92 Base64EncodedHeader("Failed to encode base64 PaymentRequired payload".to_string()),
93 );
94
95 ErrorResponse {
96 status: StatusCode::PAYMENT_REQUIRED,
97 header: ErrorResponseHeader::PaymentResponse(header),
98 body: Box::new(payment_required),
99 }
100 }
101
102 pub fn server_error(
104 reason: impl Display,
105 resource: PaymentResource,
106 accepts: Accepts,
107 extensions: Record<Extension>,
108 ) -> ErrorResponse {
109 let payment_required = PaymentRequired {
110 x402_version: X402V2,
111 error: reason.to_string(),
112 resource,
113 accepts,
114 extensions,
115 };
116
117 let header = Base64EncodedHeader::try_from(payment_required.clone()).unwrap_or(
118 Base64EncodedHeader("Failed to encode base64 PaymentRequired payload".to_string()),
119 );
120
121 ErrorResponse {
122 status: StatusCode::INTERNAL_SERVER_ERROR,
123 header: ErrorResponseHeader::PaymentResponse(header),
124 body: Box::new(payment_required),
125 }
126 }
127}
128
129#[derive(Debug, Clone)]
131pub enum ErrorResponseHeader {
132 PaymentRequired(Base64EncodedHeader),
134 PaymentResponse(Base64EncodedHeader),
136}
137
138impl ErrorResponseHeader {
139 pub fn header_value(self) -> Option<(HeaderName, HeaderValue)> {
143 match self {
144 ErrorResponseHeader::PaymentRequired(Base64EncodedHeader(s)) => {
145 HeaderValue::from_str(&s)
146 .ok()
147 .map(|v| (HeaderName::from_static("payment-required"), v))
148 }
149 ErrorResponseHeader::PaymentResponse(Base64EncodedHeader(s)) => {
150 HeaderValue::from_str(&s)
151 .ok()
152 .map(|v| (HeaderName::from_static("payment-response"), v))
153 }
154 }
155 }
156}
157
158#[cfg(feature = "axum")]
159impl axum::response::IntoResponse for ErrorResponse {
160 fn into_response(self) -> axum::response::Response {
161 let mut response = (self.status, axum::extract::Json(self.body)).into_response();
162 if let Some((name, val)) = self.header.header_value() {
163 response.headers_mut().insert(name, val);
164 }
165 response
166 }
167}