1use r402_protocol::{ChainIdPattern, ClientError, PaymentRequired, PaymentRequirements};
4
5use crate::candidate::PaymentCandidate;
6use crate::register::PaymentClient;
7
8pub trait PaymentSelector: Send + Sync {
10 fn select<'a>(&self, candidates: &[&'a PaymentCandidate]) -> Option<&'a PaymentCandidate>;
12}
13
14#[derive(Debug, Clone, Copy, Default)]
16pub struct FirstMatch;
17
18impl PaymentSelector for FirstMatch {
19 fn select<'a>(&self, candidates: &[&'a PaymentCandidate]) -> Option<&'a PaymentCandidate> {
20 candidates.first().copied()
21 }
22}
23
24#[derive(Debug, Default)]
26pub struct PreferChain(Vec<ChainIdPattern>);
27
28impl PreferChain {
29 #[must_use]
31 pub fn new<P: Into<Vec<ChainIdPattern>>>(patterns: P) -> Self {
32 Self(patterns.into())
33 }
34
35 #[must_use]
37 pub fn or_chain<P: Into<Vec<ChainIdPattern>>>(mut self, patterns: P) -> Self {
38 self.0.extend(patterns.into());
39 self
40 }
41}
42
43impl PaymentSelector for PreferChain {
44 fn select<'a>(&self, candidates: &[&'a PaymentCandidate]) -> Option<&'a PaymentCandidate> {
45 for pattern in &self.0 {
46 if let Some(hit) = candidates.iter().find(|c| pattern.matches(&c.chain_id)) {
47 return Some(*hit);
48 }
49 }
50 candidates.first().copied()
51 }
52}
53
54#[derive(Debug, Clone, Copy)]
56pub struct MaxAmount(pub u128);
57
58impl PaymentSelector for MaxAmount {
59 fn select<'a>(&self, candidates: &[&'a PaymentCandidate]) -> Option<&'a PaymentCandidate> {
60 candidates
61 .iter()
62 .find(|c| c.amount.parse::<u128>().is_ok_and(|a| a <= self.0))
63 .copied()
64 }
65}
66
67impl<S: PaymentSelector> PaymentClient<S> {
68 #[must_use]
70 pub fn candidates(&self, payment_required: &PaymentRequired) -> Vec<PaymentCandidate> {
71 let mut out = Vec::new();
72 for client in &self.schemes {
73 out.extend(client.accept(payment_required));
74 }
75 out
76 }
77
78 pub fn select_candidate<'a>(
87 &self,
88 candidates: &'a [PaymentCandidate],
89 ) -> Result<&'a PaymentCandidate, ClientError> {
90 let recognized: Vec<&PaymentCandidate> = candidates
91 .iter()
92 .filter(|candidate| is_recognized_payment_flow(&candidate.requirements))
93 .collect();
94 if recognized.is_empty() {
95 return Err(if candidates.is_empty() {
96 ClientError::NoMatchingPaymentOption
97 } else {
98 ClientError::UnrecognizedPaymentFlow
99 });
100 }
101
102 let mut filtered = self.apply_spend_controls(recognized)?;
103 for policy in &self.policies {
104 filtered = policy.apply(filtered);
105 if filtered.is_empty() {
106 return Err(ClientError::NoMatchingPaymentOption);
107 }
108 }
109
110 let preferred = prefer_authorization(filtered);
111 self.selector
112 .select(&preferred)
113 .ok_or(ClientError::NoMatchingPaymentOption)
114 }
115}
116
117fn prefer_authorization(candidates: Vec<&PaymentCandidate>) -> Vec<&PaymentCandidate> {
118 let authorization: Vec<&PaymentCandidate> = candidates
119 .iter()
120 .copied()
121 .filter(|candidate| is_authorization_payment_flow(&candidate.requirements))
122 .collect();
123 if authorization.is_empty() {
124 candidates
125 } else {
126 authorization
127 }
128}
129
130#[derive(Clone, Copy)]
131enum WirePaymentFlow {
132 Absent,
133 Authorization,
134 Upfront,
135 Escrow,
136 Unknown,
137}
138
139fn wire_payment_flow(requirements: &PaymentRequirements) -> WirePaymentFlow {
140 let Some(flow) = requirements
141 .extra
142 .as_ref()
143 .and_then(|extra| extra.get("paymentFlow"))
144 else {
145 return WirePaymentFlow::Absent;
146 };
147 if flow.is_null() {
148 return WirePaymentFlow::Absent;
149 }
150 match flow.as_str() {
151 Some("authorization") => WirePaymentFlow::Authorization,
152 Some("upfront") => WirePaymentFlow::Upfront,
153 Some("escrow") => WirePaymentFlow::Escrow,
154 _ => WirePaymentFlow::Unknown,
155 }
156}
157
158fn is_recognized_payment_flow(requirements: &PaymentRequirements) -> bool {
159 !matches!(wire_payment_flow(requirements), WirePaymentFlow::Unknown)
160}
161
162fn is_authorization_payment_flow(requirements: &PaymentRequirements) -> bool {
163 matches!(
164 wire_payment_flow(requirements),
165 WirePaymentFlow::Absent | WirePaymentFlow::Authorization
166 )
167}