1mod extra;
4
5use std::collections::HashMap;
6use std::fmt::{self, Display, Formatter};
7use std::str::FromStr;
8
9pub use extra::{
10 apply_payment_flow_wire_extra, extra_payment_flow, is_authorization_payment_flow,
11 is_recognized_payment_flow,
12};
13use extra::{extra_string, requested_flow};
14use r402_protocol::payment::PaymentRequirements;
15use serde::{Deserialize, Serialize};
16
17pub const SDK_DEFAULT_ASSET_TRANSFER_METHOD: &str = "default";
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
24#[serde(rename_all = "lowercase")]
25pub enum PaymentFlowName {
26 Authorization,
28 Upfront,
30 Escrow,
32}
33
34impl PaymentFlowName {
35 pub const ALL: [Self; 3] = [Self::Authorization, Self::Upfront, Self::Escrow];
37
38 #[must_use]
40 pub const fn as_str(self) -> &'static str {
41 match self {
42 Self::Authorization => "authorization",
43 Self::Upfront => "upfront",
44 Self::Escrow => "escrow",
45 }
46 }
47
48 #[must_use]
50 pub const fn phases(self) -> PaymentFlowPhases {
51 match self {
52 Self::Authorization => PaymentFlowPhases {
53 verify_before_handler: true,
54 settle_before_handler: false,
55 settle_after_handler: true,
56 },
57 Self::Upfront => PaymentFlowPhases {
58 verify_before_handler: false,
59 settle_before_handler: true,
60 settle_after_handler: false,
61 },
62 Self::Escrow => PaymentFlowPhases {
63 verify_before_handler: false,
64 settle_before_handler: true,
65 settle_after_handler: true,
66 },
67 }
68 }
69}
70
71impl Display for PaymentFlowName {
72 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
73 f.write_str(self.as_str())
74 }
75}
76
77impl FromStr for PaymentFlowName {
78 type Err = PaymentFlowError;
79
80 fn from_str(s: &str) -> Result<Self, Self::Err> {
81 match s {
82 "authorization" => Ok(Self::Authorization),
83 "upfront" => Ok(Self::Upfront),
84 "escrow" => Ok(Self::Escrow),
85 other => Err(PaymentFlowError::UnknownPaymentFlow {
86 flow: other.to_owned(),
87 }),
88 }
89 }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
94#[serde(rename_all = "kebab-case")]
95pub enum SettlePhase {
96 BeforeHandler,
98 AfterHandler,
100 Cancel,
102}
103
104impl SettlePhase {
105 #[must_use]
107 pub const fn as_str(self) -> &'static str {
108 match self {
109 Self::BeforeHandler => "before-handler",
110 Self::AfterHandler => "after-handler",
111 Self::Cancel => "cancel",
112 }
113 }
114}
115
116impl Display for SettlePhase {
117 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
118 f.write_str(self.as_str())
119 }
120}
121
122impl FromStr for SettlePhase {
123 type Err = PaymentFlowError;
124
125 fn from_str(s: &str) -> Result<Self, Self::Err> {
126 match s {
127 "before-handler" => Ok(Self::BeforeHandler),
128 "after-handler" => Ok(Self::AfterHandler),
129 "cancel" => Ok(Self::Cancel),
130 other => Err(PaymentFlowError::UnknownSettlePhase {
131 phase: other.to_owned(),
132 }),
133 }
134 }
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
139#[allow(
140 clippy::struct_excessive_bools,
141 reason = "PAYMENT_FLOWS phase flags are a closed triple"
142)]
143pub struct PaymentFlowPhases {
144 pub verify_before_handler: bool,
146 pub settle_before_handler: bool,
148 pub settle_after_handler: bool,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq)]
156pub struct PaymentFlowConfig {
157 pub supported: Vec<PaymentFlowName>,
159 pub default: PaymentFlowName,
161}
162
163impl PaymentFlowConfig {
164 #[must_use]
166 pub const fn new(supported: Vec<PaymentFlowName>, default: PaymentFlowName) -> Self {
167 Self { supported, default }
168 }
169
170 #[must_use]
172 pub fn authorization_and_upfront() -> Self {
173 Self::new(
174 vec![PaymentFlowName::Authorization, PaymentFlowName::Upfront],
175 PaymentFlowName::Authorization,
176 )
177 }
178
179 #[must_use]
181 pub fn authorization_only() -> Self {
182 Self::new(
183 vec![PaymentFlowName::Authorization],
184 PaymentFlowName::Authorization,
185 )
186 }
187}
188
189#[derive(Debug, Clone, Copy)]
191pub struct PaymentFlowScheme<'a> {
192 pub scheme: &'a str,
194 pub default_asset_transfer_method: &'a str,
196 pub payment_flows: &'a HashMap<String, PaymentFlowConfig>,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
202pub struct ResolvedPaymentFlow {
203 pub asset_transfer_method: String,
205 pub payment_flow: PaymentFlowName,
207}
208
209pub const PAYMENT_FLOWS: [(PaymentFlowName, PaymentFlowPhases); 3] = [
211 (
212 PaymentFlowName::Authorization,
213 PaymentFlowName::Authorization.phases(),
214 ),
215 (PaymentFlowName::Upfront, PaymentFlowName::Upfront.phases()),
216 (PaymentFlowName::Escrow, PaymentFlowName::Escrow.phases()),
217];
218
219#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
221pub enum PaymentFlowError {
222 #[error(
224 "[x402] Scheme \"{scheme}\" does not support assetTransferMethod \"{asset_transfer_method}\". Supported: {supported}."
225 )]
226 UnsupportedAssetTransferMethod {
227 scheme: String,
229 asset_transfer_method: String,
231 supported: String,
233 },
234 #[error(
236 "[x402] Scheme \"{scheme}\" paymentFlows[\"{asset_transfer_method}\"].default is not in supported."
237 )]
238 DefaultNotInSupported {
239 scheme: String,
241 asset_transfer_method: String,
243 },
244 #[error(
246 "[x402] Scheme \"{scheme}\" assetTransferMethod \"{asset_transfer_method}\" does not support paymentFlow \"{requested}\". Supported: {supported} (default: {default})."
247 )]
248 UnsupportedPaymentFlow {
249 scheme: String,
251 asset_transfer_method: String,
253 requested: String,
255 supported: String,
257 default: String,
259 },
260 #[error(
262 "[x402] Unknown payment flow \"{flow}\". Expected one of: authorization, upfront, escrow."
263 )]
264 UnknownPaymentFlow {
265 flow: String,
267 },
268 #[error(
270 "[x402] Unknown settle phase \"{phase}\". Expected one of: before-handler, after-handler, cancel."
271 )]
272 UnknownSettlePhase {
273 phase: String,
275 },
276 #[error("[x402] No server implementation registered for scheme: {scheme}, network: {network}")]
278 UnregisteredScheme {
279 scheme: String,
281 network: String,
283 },
284}
285
286#[must_use]
288pub const fn resolve_payment_flow_phases(flow: PaymentFlowName) -> PaymentFlowPhases {
289 flow.phases()
290}
291
292pub fn resolve_payment_flow(
301 scheme: &PaymentFlowScheme<'_>,
302 requirements: &PaymentRequirements,
303) -> Result<ResolvedPaymentFlow, PaymentFlowError> {
304 let atm = extra_string(requirements, "assetTransferMethod")
305 .unwrap_or(scheme.default_asset_transfer_method);
306
307 let Some(config) = scheme.payment_flows.get(atm) else {
308 return Err(PaymentFlowError::UnsupportedAssetTransferMethod {
309 scheme: scheme.scheme.to_owned(),
310 asset_transfer_method: atm.to_owned(),
311 supported: join_sorted_keys(scheme.payment_flows),
312 });
313 };
314
315 if !config.supported.contains(&config.default) {
316 return Err(PaymentFlowError::DefaultNotInSupported {
317 scheme: scheme.scheme.to_owned(),
318 asset_transfer_method: atm.to_owned(),
319 });
320 }
321
322 let flow = match requested_flow(requirements) {
323 None => config.default,
324 Some(label) => match PaymentFlowName::from_str(&label) {
325 Ok(name) if config.supported.contains(&name) => name,
326 Ok(name) => {
327 return Err(unsupported_flow(scheme, atm, name.as_str(), config));
328 }
329 Err(_) => return Err(unsupported_flow(scheme, atm, &label, config)),
330 },
331 };
332
333 Ok(ResolvedPaymentFlow {
334 asset_transfer_method: atm.to_owned(),
335 payment_flow: flow,
336 })
337}
338
339fn unsupported_flow(
340 scheme: &PaymentFlowScheme<'_>,
341 atm: &str,
342 requested: &str,
343 config: &PaymentFlowConfig,
344) -> PaymentFlowError {
345 PaymentFlowError::UnsupportedPaymentFlow {
346 scheme: scheme.scheme.to_owned(),
347 asset_transfer_method: atm.to_owned(),
348 requested: requested.to_owned(),
349 supported: join_names(&config.supported),
350 default: config.default.as_str().to_owned(),
351 }
352}
353
354fn join_names(names: &[PaymentFlowName]) -> String {
355 names
356 .iter()
357 .map(|name| name.as_str())
358 .collect::<Vec<_>>()
359 .join(", ")
360}
361
362fn join_sorted_keys(flows: &HashMap<String, PaymentFlowConfig>) -> String {
363 let mut keys: Vec<&str> = flows.keys().map(String::as_str).collect();
364 keys.sort_unstable();
365 keys.join(", ")
366}