1use std::sync::atomic::{AtomicBool, Ordering};
4
5use compact_str::CompactString;
6use r402_protocol::payment::{PaymentRequirements, SettleResponse};
7use serde_json::Value;
8
9use crate::hooks::{
10 CancelReason, PaymentHookContext, VerifiedPaymentCanceledContext, WirePaymentPayload,
11};
12use crate::payment_flow::SettlePhase;
13use crate::resource::ResourceServer;
14use crate::settle::CompletedSettlement;
15
16impl ResourceServer {
17 pub async fn notify_verified_payment_canceled(
19 &self,
20 payload: &WirePaymentPayload,
21 requirements: &PaymentRequirements,
22 reason: CancelReason,
23 error: Option<&str>,
24 response_status: Option<u16>,
25 ) {
26 if self.hooks.is_empty() {
27 return;
28 }
29 let ctx = VerifiedPaymentCanceledContext {
30 payment: PaymentHookContext {
31 payload: payload.clone(),
32 requirements: requirements.clone(),
33 },
34 reason,
35 error: error.map(str::to_owned),
36 response_status,
37 settled_phases: Vec::new(),
38 };
39 for hook in &self.hooks {
40 hook.on_verified_payment_canceled(&ctx).await;
41 }
42 }
43
44 #[must_use]
46 pub fn cancellation_guard(
47 &self,
48 payload: WirePaymentPayload,
49 requirements: PaymentRequirements,
50 ) -> CancellationGuard {
51 CancellationGuard {
52 server: self.clone(),
53 payload,
54 requirements,
55 fired: AtomicBool::new(false),
56 settled_phases: Vec::new(),
57 before_handler: None,
58 }
59 }
60
61 async fn settle_canceled_payment(
62 &self,
63 ctx: &VerifiedPaymentCanceledContext,
64 ) -> Option<SettleResponse> {
65 if !ctx.settled_phases.contains(&SettlePhase::BeforeHandler) {
66 return None;
67 }
68 let scheme = self.registered_scheme(
69 ctx.payment.requirements.scheme.as_str(),
70 &ctx.payment.requirements.network,
71 )?;
72 let cancel_requirements = scheme.settle_on_cancel(ctx).await?;
73 match self
74 .settle_payment(
75 &ctx.payment.payload,
76 &cancel_requirements,
77 None,
78 SettlePhase::Cancel,
79 ctx.payment
80 .payload
81 .resource
82 .as_ref()
83 .map(|r| r.url.as_str()),
84 None,
85 )
86 .await
87 {
88 Ok(response) => Some(response),
89 Err(error) => SettleResponse::from_facilitator_error(
90 &error,
91 ctx.payment.requirements.network.to_string(),
92 "",
93 ),
94 }
95 }
96}
97
98#[derive(Debug)]
100pub struct CancellationGuard {
101 server: ResourceServer,
102 payload: WirePaymentPayload,
103 requirements: PaymentRequirements,
104 fired: AtomicBool,
105 settled_phases: Vec<SettlePhase>,
106 before_handler: Option<CompletedSettlement>,
107}
108
109impl CancellationGuard {
110 #[must_use]
112 pub fn with_settled_phases(mut self, phases: impl IntoIterator<Item = SettlePhase>) -> Self {
113 self.settled_phases = phases.into_iter().collect();
114 self
115 }
116
117 #[must_use]
119 pub fn with_before_handler(mut self, settlement: CompletedSettlement) -> Self {
120 self.before_handler = Some(settlement);
121 self
122 }
123
124 #[must_use]
126 pub const fn settled_phases(&self) -> &[SettlePhase] {
127 self.settled_phases.as_slice()
128 }
129
130 #[must_use]
132 pub const fn before_handler(&self) -> Option<&CompletedSettlement> {
133 self.before_handler.as_ref()
134 }
135
136 pub async fn cancel(
142 &self,
143 reason: CancelReason,
144 error: Option<&str>,
145 response_status: Option<u16>,
146 ) -> Option<SettleResponse> {
147 if self
148 .fired
149 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
150 .is_err()
151 {
152 return None;
153 }
154 let ctx = VerifiedPaymentCanceledContext {
155 payment: PaymentHookContext {
156 payload: self.payload.clone(),
157 requirements: self.requirements.clone(),
158 },
159 reason,
160 error: error.map(str::to_owned),
161 response_status,
162 settled_phases: self.settled_phases.clone(),
163 };
164 for hook in &self.server.hooks {
165 hook.on_verified_payment_canceled(&ctx).await;
166 }
167 self.server.settle_canceled_payment(&ctx).await
168 }
169
170 #[must_use]
172 pub fn has_fired(&self) -> bool {
173 self.fired.load(Ordering::SeqCst)
174 }
175}
176
177#[must_use]
182pub fn build_failure_path_settlement_response(
183 cancel_settlement: Option<&SettleResponse>,
184 before_handler: Option<&CompletedSettlement>,
185 payment_payload: Option<&WirePaymentPayload>,
186) -> Option<SettleResponse> {
187 if let Some(cancel) = cancel_settlement {
188 if cancel.is_success() {
189 return Some(cancel.clone());
190 }
191 return Some(build_failed_cancel_receipt(
192 cancel,
193 before_handler,
194 payment_payload,
195 ));
196 }
197 before_handler.map(|completed| completed.result.clone())
198}
199
200fn build_failed_cancel_receipt(
202 cancel: &SettleResponse,
203 before_handler: Option<&CompletedSettlement>,
204 payment_payload: Option<&WirePaymentPayload>,
205) -> SettleResponse {
206 let SettleResponse::Failure {
207 reason,
208 message,
209 payer,
210 network,
211 extensions,
212 extension_responses,
213 extra: cancel_extra,
214 ..
215 } = cancel
216 else {
217 return cancel.clone();
218 };
219
220 let mut extra_map = match cancel_extra {
221 Some(Value::Object(map)) => map.clone(),
222 _ => serde_json::Map::new(),
223 };
224 if let Some(before) = before_handler {
225 extra_map.insert(
226 "depositTransaction".into(),
227 Value::String(settle_response_transaction(&before.result).to_owned()),
228 );
229 extra_map.insert(
230 "depositAmount".into(),
231 Value::String(
232 settle_response_amount(&before.result)
233 .unwrap_or("")
234 .to_owned(),
235 ),
236 );
237 }
238 if let Some(payload) = payment_payload
239 && let Some(channel_id) = payload
240 .payload
241 .get("channelId")
242 .and_then(Value::as_str)
243 .filter(|id| !id.is_empty())
244 {
245 extra_map.insert("channelId".into(), Value::String(channel_id.to_owned()));
246 }
247 let merged_extra = if extra_map.is_empty() {
248 None
249 } else {
250 Some(Value::Object(extra_map))
251 };
252 SettleResponse::Failure {
253 reason: reason.clone(),
254 message: message.clone(),
255 payer: payer.clone(),
256 transaction: CompactString::default(),
257 network: network.clone(),
258 extensions: extensions.clone(),
259 extension_responses: extension_responses.clone(),
260 extra: merged_extra,
261 }
262}
263
264fn settle_response_transaction(response: &SettleResponse) -> &str {
265 match response {
266 SettleResponse::Success { transaction, .. }
267 | SettleResponse::Failure { transaction, .. } => transaction.as_str(),
268 _ => "",
269 }
270}
271
272fn settle_response_amount(response: &SettleResponse) -> Option<&str> {
273 match response {
274 SettleResponse::Success { amount, .. } => amount.as_deref(),
275 SettleResponse::Failure { .. } | _ => None,
276 }
277}