r402_http/client/hooks.rs
1//! Lifecycle hooks for the x402 client payment creation pipeline.
2//!
3//! Hooks allow applications to intercept and customize the payment
4//! creation lifecycle. This mirrors the Go SDK's `client_hooks.go` design,
5//! using the same `HookDecision` / `FailureRecovery` enums as the
6//! server-side hooks for a consistent API.
7//!
8//! ## Hook Lifecycle
9//!
10//! 1. **`before_payment_creation`** — Run before payment creation; can abort it.
11//! 2. **Payment signing executes**
12//! 3. **`after_payment_creation`** (on success) — Observes the result.
13//! 4. **`on_payment_creation_failure`** (on error) — Can recover with substitute headers.
14//!
15//! ## Usage
16//!
17//! Implement [`ClientHooks`] with only the hooks you need — all methods
18//! have default no-op implementations.
19
20use http::HeaderMap;
21use r402::facilitator::BoxFuture;
22use r402::hooks::{FailureRecovery, HookDecision};
23use r402::proto;
24
25/// Context passed to client payment creation lifecycle hooks.
26#[derive(Debug, Clone)]
27pub struct PaymentCreationContext {
28 /// The parsed payment requirements from the 402 response.
29 pub payment_required: proto::PaymentRequired,
30}
31
32/// Lifecycle hooks for client-side payment creation.
33///
34/// All methods have default no-op implementations. Override only the hooks you
35/// need. This trait is dyn-compatible for use in heterogeneous hook lists.
36///
37/// The hook lifecycle mirrors [`r402::hooks::FacilitatorHooks`]:
38///
39/// 1. **`before_payment_creation`** — Can abort with [`HookDecision::Abort`].
40/// 2. **Payment signing executes**
41/// 3. **`after_payment_creation`** (on success) — Observes the signed headers.
42/// 4. **`on_payment_creation_failure`** (on error) — Can recover with [`FailureRecovery::Recovered`].
43pub trait ClientHooks: Send + Sync {
44 /// Called before payment creation.
45 ///
46 /// If any hook returns [`HookDecision::Abort`], payment creation is skipped
47 /// and the original 402 response is returned to the caller.
48 fn before_payment_creation<'a>(
49 &'a self,
50 _ctx: &'a PaymentCreationContext,
51 ) -> BoxFuture<'a, HookDecision> {
52 Box::pin(async { HookDecision::Continue })
53 }
54
55 /// Called after successful payment creation.
56 ///
57 /// Receives the signed payment headers. Cannot affect the outcome.
58 fn after_payment_creation<'a>(
59 &'a self,
60 _ctx: &'a PaymentCreationContext,
61 _headers: &'a HeaderMap,
62 ) -> BoxFuture<'a, ()> {
63 Box::pin(async {})
64 }
65
66 /// Called when payment creation fails.
67 ///
68 /// If a hook returns [`FailureRecovery::Recovered`], the provided headers
69 /// replace the error.
70 fn on_payment_creation_failure<'a>(
71 &'a self,
72 _ctx: &'a PaymentCreationContext,
73 _error: &'a str,
74 ) -> BoxFuture<'a, FailureRecovery<HeaderMap>> {
75 Box::pin(async { FailureRecovery::Propagate })
76 }
77}