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