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 BuildUnsignedTransferPackageRequest, ListPaymentsRequest, ListPaymentsResponse, Payment,
11 PaymentRequest, PrepareSendPaymentRequest, PrepareSendPaymentResponse,
12 PublishSignedTransferPackageRequest, PublishSignedTransferPackageResponse,
13 ReceivePaymentRequest, ReceivePaymentResponse, SendPaymentRequest, SendPaymentResponse,
14 UnsignedTransferPackage,
15 },
16 utils::payments::get_payment_with_conversion_details,
17};
18
19use super::BreezSdk;
20
21pub(in crate::sdk) mod client_signing;
22pub(in crate::sdk) mod conversion;
23mod polling;
24pub(in crate::sdk) mod prepare;
25mod receive;
26pub(in crate::sdk) mod send;
27pub(in crate::sdk) mod validation;
28
29#[cfg_attr(feature = "uniffi", uniffi::export(async_runtime = "tokio"))]
30#[allow(clippy::needless_pass_by_value)]
31impl BreezSdk {
32 pub async fn receive_payment(
33 &self,
34 request: ReceivePaymentRequest,
35 ) -> Result<ReceivePaymentResponse, SdkError> {
36 receive::receive_payment(self, request).await
37 }
38
39 pub async fn claim_htlc_payment(
40 &self,
41 request: ClaimHtlcPaymentRequest,
42 ) -> Result<ClaimHtlcPaymentResponse, SdkError> {
43 receive::claim_htlc_payment(self, request).await
44 }
45
46 pub async fn prepare_send_payment(
47 &self,
48 request: PrepareSendPaymentRequest,
49 ) -> Result<PrepareSendPaymentResponse, SdkError> {
50 if let PaymentRequest::CrossChain {
53 ref address,
54 ref route,
55 max_slippage_bps,
56 target_overpay_bps,
57 } = request.payment_request
58 {
59 let amount = request.amount.ok_or(SdkError::InvalidInput(
60 "Amount is required for cross-chain sends".to_string(),
61 ))?;
62 return prepare::cross_chain::prepare(
63 self,
64 address,
65 route,
66 amount,
67 request.token_identifier.clone(),
68 request.conversion_options.clone(),
69 request.fee_policy.unwrap_or_default(),
70 max_slippage_bps,
71 target_overpay_bps,
72 )
73 .await;
74 }
75 prepare::prepare(self, request).await
76 }
77
78 #[instrument(
79 level = "info",
80 target = "breez_sdk_core::send_payment",
81 skip_all,
82 fields(payment_id = tracing::field::Empty),
83 )]
84 pub async fn send_payment(
85 &self,
86 request: SendPaymentRequest,
87 ) -> Result<SendPaymentResponse, SdkError> {
88 self.maybe_ensure_spark_private_mode_initialized().await?;
89 if let Some(key) = request.idempotency_key.as_deref() {
90 tracing::Span::current().record("payment_id", key);
91 }
92 Box::pin(send::orchestrate_send(self, request, false, None)).await
93 }
94
95 pub async fn build_unsigned_transfer_package(
96 &self,
97 request: BuildUnsignedTransferPackageRequest,
98 ) -> Result<UnsignedTransferPackage, SdkError> {
99 Box::pin(client_signing::build_unsigned_transfer_package(
100 self,
101 &request.prepare_response,
102 request.options.as_ref(),
103 ))
104 .await
105 }
106
107 #[instrument(
108 level = "info",
109 target = "breez_sdk_core::publish_signed_transfer_package",
110 skip_all
111 )]
112 pub async fn publish_signed_transfer_package(
113 &self,
114 request: PublishSignedTransferPackageRequest,
115 ) -> Result<PublishSignedTransferPackageResponse, SdkError> {
116 self.maybe_ensure_spark_private_mode_initialized().await?;
117 Box::pin(send::publish_signed_transfer_package(
118 self,
119 &request.signed_package,
120 ))
121 .await
122 }
123
124 pub async fn fetch_conversion_limits(
125 &self,
126 request: FetchConversionLimitsRequest,
127 ) -> Result<FetchConversionLimitsResponse, SdkError> {
128 self.token_converter
129 .fetch_limits(&request)
130 .await
131 .map_err(Into::into)
132 }
133
134 pub async fn refund_pending_conversions(
145 &self,
146 ) -> Result<RefundPendingConversionsResponse, SdkError> {
147 self.token_converter
148 .refund_pending()
149 .await
150 .map_err(Into::into)
151 }
152
153 pub async fn list_payments(
167 &self,
168 request: ListPaymentsRequest,
169 ) -> Result<ListPaymentsResponse, SdkError> {
170 use crate::utils::conversions::extract_conversion_info;
171 use crate::utils::payments::build_conversions;
172
173 let mut payments = self.storage.list_payments(request.into()).await?;
174
175 let parent_ids: Vec<String> = payments
177 .iter()
178 .filter(|p| p.conversion_details.is_some())
179 .map(|p| p.id.clone())
180 .collect();
181
182 let related_payments_map = if parent_ids.is_empty() {
183 std::collections::HashMap::default()
184 } else {
185 self.storage.get_payments_by_parent_ids(parent_ids).await?
186 };
187
188 for payment in &mut payments {
189 let has_conversion_details = payment.conversion_details.is_some();
190 let has_crosschain_info = extract_conversion_info(payment.details.clone())
191 .is_some_and(|info| !matches!(info, crate::ConversionInfo::Amm { .. }));
192
193 if !has_conversion_details && !has_crosschain_info {
194 continue;
195 }
196
197 let child_payments = if has_conversion_details {
198 related_payments_map.get(&payment.id).map(Vec::as_slice)
199 } else {
200 None
201 };
202
203 let conversions = build_conversions(payment, child_payments);
204
205 if !conversions.is_empty() {
206 if let Some(ref mut cd) = payment.conversion_details {
207 cd.conversions = conversions;
208 } else {
209 let status = extract_conversion_info(payment.details.clone())
210 .map_or(crate::ConversionStatus::Completed, |info| {
211 info.status().clone()
212 });
213 payment.conversion_details = Some(crate::models::ConversionDetails {
214 status,
215 conversions,
216 });
217 }
218 }
219 }
220
221 Ok(ListPaymentsResponse { payments })
222 }
223
224 pub async fn get_payment(
225 &self,
226 request: GetPaymentRequest,
227 ) -> Result<GetPaymentResponse, SdkError> {
228 let payment =
229 get_payment_with_conversion_details(request.payment_id, self.storage.clone()).await?;
230
231 Ok(GetPaymentResponse { payment })
232 }
233}
234
235impl BreezSdk {
237 pub(crate) async fn receive_bolt11_invoice(
238 &self,
239 description: String,
240 amount_sats: Option<u64>,
241 expiry_secs: Option<u32>,
242 payment_hash: Option<String>,
243 ) -> Result<ReceivePaymentResponse, SdkError> {
244 receive::receive_bolt11_invoice(self, description, amount_sats, expiry_secs, payment_hash)
245 .await
246 }
247
248 pub(crate) async fn receive_bolt11_invoice_inner(
249 &self,
250 description: String,
251 amount_sats: Option<u64>,
252 expiry_secs: Option<u32>,
253 payment_hash: Option<String>,
254 ) -> Result<LightningReceivePayment, SdkError> {
255 receive::receive_bolt11_invoice_inner(
256 self,
257 description,
258 amount_sats,
259 expiry_secs,
260 payment_hash,
261 )
262 .await
263 }
264
265 pub(crate) async fn wait_for_incoming_payment(
266 &self,
267 identifier: WaitForPaymentIdentifier,
268 completion_timeout_secs: u32,
269 ) -> Result<Payment, SdkError> {
270 polling::wait_for_incoming_payment(self, identifier, completion_timeout_secs).await
271 }
272
273 pub(crate) async fn finalize_payment(&self, payment: Payment) -> bool {
274 polling::finalize_payment(self, payment).await
275 }
276}