breez_sdk_spark/sdk/payments/
mod.rs1use spark_wallet::LightningReceivePayment;
2use tracing::instrument;
3
4use crate::{
5 ClaimHtlcPaymentRequest, ClaimHtlcPaymentResponse, FetchConversionLimitsRequest,
6 FetchConversionLimitsResponse, GetPaymentRequest, GetPaymentResponse,
7 RefundPendingConversionsResponse, WaitForPaymentIdentifier,
8 error::SdkError,
9 models::{
10 BuildUnsignedBatchPackageRequest, BuildUnsignedTransferPackageRequest, ListPaymentsRequest,
11 ListPaymentsResponse, Payment, PaymentRequest, PrepareSendBatchRequest,
12 PrepareSendBatchResponse, PrepareSendPaymentRequest, PrepareSendPaymentResponse,
13 PublishSignedTransferPackageRequest, PublishSignedTransferPackageResponse,
14 ReceivePaymentRequest, ReceivePaymentResponse, SendBatchRequest, SendBatchResponse,
15 SendPaymentRequest, SendPaymentResponse, UnsignedTransferPackage,
16 },
17 utils::payments::get_payment_with_conversion_details,
18};
19
20use super::BreezSdk;
21
22pub(in crate::sdk) mod client_signing;
23pub(in crate::sdk) mod conversion;
24mod polling;
25pub(in crate::sdk) mod prepare;
26mod receive;
27pub(in crate::sdk) mod send;
28pub(in crate::sdk) mod validation;
29
30#[cfg_attr(feature = "uniffi", uniffi::export(async_runtime = "tokio"))]
31#[allow(clippy::needless_pass_by_value)]
32impl BreezSdk {
33 pub async fn receive_payment(
34 &self,
35 request: ReceivePaymentRequest,
36 ) -> Result<ReceivePaymentResponse, SdkError> {
37 receive::receive_payment(self, request).await
38 }
39
40 pub async fn claim_htlc_payment(
41 &self,
42 request: ClaimHtlcPaymentRequest,
43 ) -> Result<ClaimHtlcPaymentResponse, SdkError> {
44 receive::claim_htlc_payment(self, request).await
45 }
46
47 pub async fn prepare_send_payment(
48 &self,
49 request: PrepareSendPaymentRequest,
50 ) -> Result<PrepareSendPaymentResponse, SdkError> {
51 if let PaymentRequest::CrossChain {
54 ref address,
55 ref route,
56 max_slippage_bps,
57 target_overpay_bps,
58 } = request.payment_request
59 {
60 let amount = request.amount.ok_or(SdkError::InvalidInput(
61 "Amount is required for cross-chain sends".to_string(),
62 ))?;
63 return prepare::cross_chain::prepare(
64 self,
65 address,
66 route,
67 amount,
68 request.token_identifier.clone(),
69 request.conversion_options.clone(),
70 request.fee_policy.unwrap_or_default(),
71 max_slippage_bps,
72 target_overpay_bps,
73 )
74 .await;
75 }
76 prepare::prepare(self, request).await
77 }
78
79 #[instrument(
80 level = "info",
81 target = "breez_sdk_core::send_payment",
82 skip_all,
83 fields(payment_id = tracing::field::Empty),
84 )]
85 pub async fn send_payment(
86 &self,
87 request: SendPaymentRequest,
88 ) -> Result<SendPaymentResponse, SdkError> {
89 self.maybe_ensure_spark_private_mode_initialized().await?;
90 if let Some(key) = request.idempotency_key.as_deref() {
91 tracing::Span::current().record("payment_id", key);
92 }
93 Box::pin(send::orchestrate_send(self, request, false, None)).await
94 }
95
96 pub async fn prepare_send_batch(
109 &self,
110 request: PrepareSendBatchRequest,
111 ) -> Result<PrepareSendBatchResponse, SdkError> {
112 prepare::batch::prepare(self, request).await
113 }
114
115 #[instrument(level = "info", target = "breez_sdk_core::send_batch", skip_all)]
124 pub async fn send_batch(
125 &self,
126 request: SendBatchRequest,
127 ) -> Result<SendBatchResponse, SdkError> {
128 self.maybe_ensure_spark_private_mode_initialized().await?;
129 Box::pin(send::batch::send(self, request)).await
130 }
131
132 pub async fn build_unsigned_batch_package(
138 &self,
139 request: BuildUnsignedBatchPackageRequest,
140 ) -> Result<UnsignedTransferPackage, SdkError> {
141 Box::pin(client_signing::build_unsigned_batch_package(
142 self,
143 &request.prepare_response,
144 ))
145 .await
146 }
147
148 pub async fn build_unsigned_transfer_package(
149 &self,
150 request: BuildUnsignedTransferPackageRequest,
151 ) -> Result<UnsignedTransferPackage, SdkError> {
152 Box::pin(client_signing::build_unsigned_transfer_package(
153 self,
154 &request.prepare_response,
155 request.options.as_ref(),
156 ))
157 .await
158 }
159
160 #[instrument(
161 level = "info",
162 target = "breez_sdk_core::publish_signed_transfer_package",
163 skip_all
164 )]
165 pub async fn publish_signed_transfer_package(
166 &self,
167 request: PublishSignedTransferPackageRequest,
168 ) -> Result<PublishSignedTransferPackageResponse, SdkError> {
169 self.maybe_ensure_spark_private_mode_initialized().await?;
170 Box::pin(send::publish_signed_transfer_package(
171 self,
172 &request.signed_package,
173 ))
174 .await
175 }
176
177 pub async fn fetch_conversion_limits(
178 &self,
179 request: FetchConversionLimitsRequest,
180 ) -> Result<FetchConversionLimitsResponse, SdkError> {
181 self.token_converter
182 .fetch_limits(&request)
183 .await
184 .map_err(Into::into)
185 }
186
187 pub async fn refund_pending_conversions(
198 &self,
199 ) -> Result<RefundPendingConversionsResponse, SdkError> {
200 self.token_converter
201 .refund_pending()
202 .await
203 .map_err(Into::into)
204 }
205
206 pub async fn list_payments(
220 &self,
221 request: ListPaymentsRequest,
222 ) -> Result<ListPaymentsResponse, SdkError> {
223 use crate::utils::conversions::extract_conversion_info;
224 use crate::utils::payments::build_conversions;
225
226 let mut payments = self.storage.list_payments(request.into()).await?;
227
228 let parent_ids: Vec<String> = payments
230 .iter()
231 .filter(|p| p.conversion_details.is_some())
232 .map(|p| p.id.clone())
233 .collect();
234
235 let related_payments_map = if parent_ids.is_empty() {
236 std::collections::HashMap::default()
237 } else {
238 self.storage.get_payments_by_parent_ids(parent_ids).await?
239 };
240
241 for payment in &mut payments {
242 let has_conversion_details = payment.conversion_details.is_some();
243 let has_crosschain_info = extract_conversion_info(payment.details.clone())
244 .is_some_and(|info| !matches!(info, crate::ConversionInfo::Amm { .. }));
245
246 if !has_conversion_details && !has_crosschain_info {
247 continue;
248 }
249
250 let child_payments = if has_conversion_details {
251 related_payments_map.get(&payment.id).map(Vec::as_slice)
252 } else {
253 None
254 };
255
256 let conversions = build_conversions(payment, child_payments);
257
258 if !conversions.is_empty() {
259 if let Some(ref mut cd) = payment.conversion_details {
260 cd.conversions = conversions;
261 } else {
262 let status = extract_conversion_info(payment.details.clone())
263 .map_or(crate::ConversionStatus::Completed, |info| {
264 info.status().clone()
265 });
266 payment.conversion_details = Some(crate::models::ConversionDetails {
267 status,
268 conversions,
269 });
270 }
271 }
272 }
273
274 Ok(ListPaymentsResponse { payments })
275 }
276
277 pub async fn get_payment(
278 &self,
279 request: GetPaymentRequest,
280 ) -> Result<GetPaymentResponse, SdkError> {
281 let payment =
282 get_payment_with_conversion_details(request.payment_id, self.storage.clone()).await?;
283
284 Ok(GetPaymentResponse { payment })
285 }
286}
287
288impl BreezSdk {
290 pub(crate) async fn receive_bolt11_invoice(
291 &self,
292 description: String,
293 amount_sats: Option<u64>,
294 expiry_secs: Option<u32>,
295 payment_hash: Option<String>,
296 receiver_identity_public_key: Option<String>,
297 ) -> Result<ReceivePaymentResponse, SdkError> {
298 receive::receive_bolt11_invoice(
299 self,
300 description,
301 amount_sats,
302 expiry_secs,
303 payment_hash,
304 receiver_identity_public_key,
305 )
306 .await
307 }
308
309 pub(crate) async fn receive_bolt11_invoice_inner(
310 &self,
311 description: String,
312 amount_sats: Option<u64>,
313 expiry_secs: Option<u32>,
314 payment_hash: Option<String>,
315 receiver_identity_public_key: Option<String>,
316 ) -> Result<LightningReceivePayment, SdkError> {
317 receive::receive_bolt11_invoice_inner(
318 self,
319 description,
320 amount_sats,
321 expiry_secs,
322 payment_hash,
323 receiver_identity_public_key,
324 )
325 .await
326 }
327
328 pub(crate) async fn wait_for_incoming_payment(
329 &self,
330 identifier: WaitForPaymentIdentifier,
331 completion_timeout_secs: u32,
332 ) -> Result<Payment, SdkError> {
333 polling::wait_for_incoming_payment(self, identifier, completion_timeout_secs).await
334 }
335
336 pub(crate) async fn finalize_payment(&self, payment: Payment) -> bool {
337 polling::finalize_payment(self, payment).await
338 }
339}