1use std::fmt::{self, Debug, Formatter};
4use std::sync::Arc;
5
6use r402_protocol::{ClientError, PaymentRequired};
7#[cfg(test)]
8use serde_json as _;
9#[cfg(test)]
10use tokio as _;
11
12use crate::candidate::SchemeClient;
13use crate::extension::{ClientExtension, DynClientExtension};
14use crate::hooks::{
15 ClientHooks, CreatedPayment, DynClientHooks, FailureRecovery, HookDecision,
16 PaymentCreationContext,
17};
18use crate::policy::PaymentPolicy;
19use crate::select::{FirstMatch, PaymentSelector};
20use crate::spend::SpendControls;
21
22pub struct PaymentClient<S = FirstMatch> {
24 pub(crate) schemes: Vec<Arc<dyn SchemeClient>>,
25 pub(crate) selector: S,
26 pub(crate) policies: Vec<Arc<dyn PaymentPolicy>>,
27 pub(crate) hooks: Vec<Arc<dyn DynClientHooks>>,
28 pub(crate) extensions: Vec<Arc<dyn DynClientExtension>>,
29 pub(crate) spend_controls: Option<SpendControls>,
30}
31
32impl PaymentClient<FirstMatch> {
33 #[must_use]
35 pub fn new() -> Self {
36 Self::default()
37 }
38}
39
40impl Default for PaymentClient<FirstMatch> {
41 fn default() -> Self {
42 Self {
43 schemes: Vec::new(),
44 selector: FirstMatch,
45 policies: Vec::new(),
46 hooks: Vec::new(),
47 extensions: Vec::new(),
48 spend_controls: Some(SpendControls::default()),
49 }
50 }
51}
52
53impl<S> Debug for PaymentClient<S> {
54 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
55 f.debug_struct("PaymentClient")
56 .field("schemes", &self.schemes.len())
57 .field("policies", &self.policies.len())
58 .field("hooks", &self.hooks.len())
59 .field("extensions", &self.extensions.len())
60 .finish_non_exhaustive()
61 }
62}
63
64impl<S> PaymentClient<S> {
65 #[must_use]
67 pub fn register(mut self, scheme: impl SchemeClient + 'static) -> Self {
68 self.schemes.push(Arc::new(scheme));
69 self
70 }
71
72 #[must_use]
74 pub fn with_selector<P: PaymentSelector>(self, selector: P) -> PaymentClient<P> {
75 PaymentClient {
76 schemes: self.schemes,
77 selector,
78 policies: self.policies,
79 hooks: self.hooks,
80 extensions: self.extensions,
81 spend_controls: self.spend_controls,
82 }
83 }
84
85 #[must_use]
87 pub fn with_spend_controls(mut self, controls: SpendControls) -> Self {
88 self.spend_controls = Some(controls);
89 self
90 }
91
92 #[must_use]
94 pub fn disable_spend_controls(mut self) -> Self {
95 self.spend_controls = None;
96 self
97 }
98
99 #[must_use]
101 pub fn with_policy(mut self, policy: impl PaymentPolicy + 'static) -> Self {
102 self.policies.push(Arc::new(policy));
103 self
104 }
105
106 #[must_use]
108 pub fn with_hook(mut self, hook: impl ClientHooks + 'static) -> Self {
109 self.hooks.push(Arc::new(hook));
110 self
111 }
112
113 #[must_use]
115 pub fn with_extension(mut self, extension: impl ClientExtension + 'static) -> Self {
116 self.extensions.push(Arc::new(extension));
117 self
118 }
119
120 #[must_use]
122 pub fn scheme_count(&self) -> usize {
123 self.schemes.len()
124 }
125
126 #[must_use]
128 pub fn hook_count(&self) -> usize {
129 self.hooks.len()
130 }
131
132 #[must_use]
134 pub fn extension_count(&self) -> usize {
135 self.extensions.len()
136 }
137}
138
139impl<S: PaymentSelector> PaymentClient<S> {
140 pub async fn create_payment(
146 &self,
147 payment_required: &PaymentRequired,
148 ) -> Result<CreatedPayment, ClientError> {
149 let ctx = PaymentCreationContext {
150 payment_required: payment_required.clone(),
151 };
152
153 if let Some(err) = self.run_before_creation(&ctx).await {
154 return Err(err);
155 }
156
157 match self.create_payment_inner(payment_required).await {
158 Ok(created) => {
159 self.run_after_creation(&ctx, &created).await;
160 Ok(created)
161 }
162 Err(err) => self.recover_creation(&ctx, err).await,
163 }
164 }
165
166 async fn run_before_creation(&self, ctx: &PaymentCreationContext) -> Option<ClientError> {
167 for hook in &self.hooks {
168 if let Some(err) =
169 Self::client_error_from_abort(hook.before_payment_creation(ctx).await)
170 {
171 return Some(err);
172 }
173 }
174 None
175 }
176
177 fn client_error_from_abort(decision: HookDecision) -> Option<ClientError> {
178 let HookDecision::Abort { reason, message } = decision else {
179 return None;
180 };
181 let detail = if message.is_empty() {
182 reason
183 } else {
184 format!("{reason}: {message}")
185 };
186 Some(ClientError::Parse(detail))
187 }
188
189 async fn run_after_creation(&self, ctx: &PaymentCreationContext, created: &CreatedPayment) {
190 for hook in &self.hooks {
191 hook.after_payment_creation(ctx, created).await;
192 }
193 }
194
195 async fn recover_creation(
196 &self,
197 ctx: &PaymentCreationContext,
198 err: ClientError,
199 ) -> Result<CreatedPayment, ClientError> {
200 let msg = err.to_string();
201 for hook in &self.hooks {
202 let recovery = hook.on_payment_creation_failure(ctx, &msg).await;
203 if let FailureRecovery::Recovered(created) = recovery {
204 return Ok(created);
205 }
206 }
207 Err(err)
208 }
209
210 async fn create_payment_inner(
211 &self,
212 payment_required: &PaymentRequired,
213 ) -> Result<CreatedPayment, ClientError> {
214 let candidates = self.candidates(payment_required);
215 let selected = self.select_candidate(&candidates)?;
216 let signed_payload = selected.sign().await?;
217 let signed_payload = self
218 .enrich_signed_payload(&signed_payload, payment_required)
219 .await?;
220 Ok(CreatedPayment::new(
221 signed_payload,
222 payment_required.clone(),
223 ))
224 }
225}