1use std::fmt::{self, Debug, Formatter};
4use std::future::Future;
5use std::pin::Pin;
6
7use r402_protocol::{PaymentRequired, SettleResponse};
8
9use crate::register::PaymentClient;
10use crate::select::PaymentSelector;
11
12pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
14
15#[derive(Debug, Clone)]
17#[non_exhaustive]
18pub enum HookDecision {
19 Continue,
21 Abort {
23 reason: String,
25 message: String,
27 },
28}
29
30#[derive(Debug)]
32#[non_exhaustive]
33pub enum FailureRecovery<T> {
34 Propagate,
36 Recovered(T),
38}
39
40#[derive(Debug, Clone)]
42#[non_exhaustive]
43pub struct PaymentCreationContext {
44 pub payment_required: PaymentRequired,
46}
47
48impl PaymentCreationContext {
49 #[must_use]
51 pub const fn new(payment_required: PaymentRequired) -> Self {
52 Self { payment_required }
53 }
54}
55
56#[derive(Debug, Clone)]
58#[non_exhaustive]
59pub struct CreatedPayment {
60 pub signed_payload: String,
62 pub payment_required: PaymentRequired,
64}
65
66impl CreatedPayment {
67 #[must_use]
69 pub fn new(signed_payload: impl Into<String>, payment_required: PaymentRequired) -> Self {
70 Self {
71 signed_payload: signed_payload.into(),
72 payment_required,
73 }
74 }
75}
76
77#[derive(Debug, Clone)]
83#[non_exhaustive]
84pub struct PaymentResponseContext {
85 pub payment_required: PaymentRequired,
87 pub signed_payload: String,
89 pub settle_response: Option<SettleResponse>,
91 pub corrective_payment_required: Option<PaymentRequired>,
93}
94
95impl PaymentResponseContext {
96 #[must_use]
98 pub fn new(payment_required: PaymentRequired, signed_payload: impl Into<String>) -> Self {
99 Self {
100 payment_required,
101 signed_payload: signed_payload.into(),
102 settle_response: None,
103 corrective_payment_required: None,
104 }
105 }
106
107 #[must_use]
109 pub fn with_settle_response(mut self, settle: SettleResponse) -> Self {
110 self.settle_response = Some(settle);
111 self
112 }
113
114 #[must_use]
116 pub fn with_corrective_payment_required(mut self, required: PaymentRequired) -> Self {
117 self.corrective_payment_required = Some(required);
118 self
119 }
120}
121
122#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
124#[non_exhaustive]
125pub struct PaymentResponseResult {
126 pub recovered: bool,
128}
129
130impl PaymentResponseResult {
131 #[must_use]
133 pub const fn continue_() -> Self {
134 Self { recovered: false }
135 }
136
137 #[must_use]
139 pub const fn recovered() -> Self {
140 Self { recovered: true }
141 }
142}
143
144pub trait ClientHooks: Send + Sync {
148 fn before_payment_creation<'a>(
150 &'a self,
151 _ctx: &'a PaymentCreationContext,
152 ) -> impl Future<Output = HookDecision> + Send + 'a {
153 async { HookDecision::Continue }
154 }
155
156 fn after_payment_creation<'a>(
158 &'a self,
159 _ctx: &'a PaymentCreationContext,
160 _created: &'a CreatedPayment,
161 ) -> impl Future<Output = ()> + Send + 'a {
162 async {}
163 }
164
165 fn on_payment_creation_failure<'a>(
167 &'a self,
168 _ctx: &'a PaymentCreationContext,
169 _error: &'a str,
170 ) -> impl Future<Output = FailureRecovery<CreatedPayment>> + Send + 'a {
171 async { FailureRecovery::Propagate }
172 }
173
174 fn on_payment_response<'a>(
179 &'a self,
180 _ctx: &'a PaymentResponseContext,
181 ) -> impl Future<Output = PaymentResponseResult> + Send + 'a {
182 async { PaymentResponseResult::continue_() }
183 }
184}
185
186pub trait DynClientHooks: Send + Sync {
188 fn before_payment_creation<'a>(
190 &'a self,
191 ctx: &'a PaymentCreationContext,
192 ) -> Pin<Box<dyn Future<Output = HookDecision> + Send + 'a>>;
193
194 fn after_payment_creation<'a>(
196 &'a self,
197 ctx: &'a PaymentCreationContext,
198 created: &'a CreatedPayment,
199 ) -> BoxFuture<'a, ()>;
200
201 fn on_payment_creation_failure<'a>(
203 &'a self,
204 ctx: &'a PaymentCreationContext,
205 error: &'a str,
206 ) -> BoxFuture<'a, FailureRecovery<CreatedPayment>>;
207
208 fn on_payment_response<'a>(
210 &'a self,
211 ctx: &'a PaymentResponseContext,
212 ) -> Pin<Box<dyn Future<Output = PaymentResponseResult> + Send + 'a>>;
213}
214
215impl<T: ClientHooks + ?Sized> DynClientHooks for T {
216 fn before_payment_creation<'a>(
217 &'a self,
218 ctx: &'a PaymentCreationContext,
219 ) -> Pin<Box<dyn Future<Output = HookDecision> + Send + 'a>> {
220 Box::pin(<Self as ClientHooks>::before_payment_creation(self, ctx))
221 }
222
223 fn after_payment_creation<'a>(
224 &'a self,
225 ctx: &'a PaymentCreationContext,
226 created: &'a CreatedPayment,
227 ) -> BoxFuture<'a, ()> {
228 Box::pin(<Self as ClientHooks>::after_payment_creation(
229 self, ctx, created,
230 ))
231 }
232
233 fn on_payment_creation_failure<'a>(
234 &'a self,
235 ctx: &'a PaymentCreationContext,
236 error: &'a str,
237 ) -> BoxFuture<'a, FailureRecovery<CreatedPayment>> {
238 Box::pin(<Self as ClientHooks>::on_payment_creation_failure(
239 self, ctx, error,
240 ))
241 }
242
243 fn on_payment_response<'a>(
244 &'a self,
245 ctx: &'a PaymentResponseContext,
246 ) -> Pin<Box<dyn Future<Output = PaymentResponseResult> + Send + 'a>> {
247 Box::pin(<Self as ClientHooks>::on_payment_response(self, ctx))
248 }
249}
250
251impl Debug for dyn DynClientHooks {
252 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
253 f.write_str("DynClientHooks")
254 }
255}
256
257impl<S: PaymentSelector> PaymentClient<S> {
258 pub async fn handle_payment_response(
262 &self,
263 ctx: &PaymentResponseContext,
264 ) -> PaymentResponseResult {
265 let mut recovered = false;
266 for hook in &self.hooks {
267 let result = hook.on_payment_response(ctx).await;
268 if result.recovered {
269 recovered = true;
270 }
271 }
272 if recovered {
273 PaymentResponseResult::recovered()
274 } else {
275 PaymentResponseResult::continue_()
276 }
277 }
278}