Skip to content

Commit

Permalink
Return pending payment after timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
dangeross committed Apr 30, 2024
1 parent 9806bfb commit fd46a2f
Show file tree
Hide file tree
Showing 13 changed files with 400 additions and 36 deletions.
2 changes: 2 additions & 0 deletions libs/sdk-bindings/src/breez_sdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ interface BreezEvent {
NewBlock(u32 block);
InvoicePaid(InvoicePaidDetails details);
Synced();
PaymentStarted(Payment details);
PaymentSucceed(Payment details);
PaymentFailed(PaymentFailedData details);
BackupStarted();
Expand Down Expand Up @@ -749,6 +750,7 @@ dictionary SendPaymentRequest {
string bolt11;
u64? amount_msat = null;
string? label = null;
u64? pending_timeout_sec = null;
};

dictionary SendSpontaneousPaymentRequest {
Expand Down
77 changes: 54 additions & 23 deletions libs/sdk-core/src/breez_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ pub enum BreezEvent {
},
/// Indicates that the local SDK state has just been sync-ed with the remote components
Synced,
/// Indicates that an outgoing payment has started
PaymentStarted {
details: Payment,
},
/// Indicates that an outgoing payment has been completed successfully
PaymentSucceed {
details: Payment,
Expand All @@ -116,6 +120,11 @@ pub enum BreezEvent {
details: SwapInfo,
},
}
impl BreezEvent {
pub(crate) fn payment_started(payment: Payment) -> Self {
Self::PaymentStarted { details: payment }
}
}

#[derive(Clone, Debug, PartialEq)]
pub struct BackupFailedData {
Expand Down Expand Up @@ -282,7 +291,7 @@ impl BreezServices {
/// Calling `send_payment` ensures that the payment is not already completed, if so it will result in an error.
/// If the invoice doesn't specify an amount, the amount is taken from the `amount_msat` arg.
pub async fn send_payment(
&self,
self: &Arc<BreezServices>,
req: SendPaymentRequest,
) -> Result<SendPaymentResponse, SendPaymentError> {
self.start_node().await?;
Expand Down Expand Up @@ -314,25 +323,39 @@ impl BreezServices {
{
Some(_) => Err(SendPaymentError::AlreadyPaid),
None => {
self.persist_pending_payment(&parsed_invoice, amount_msat, req.label.clone())?;
let payment_res = self
.node_api
.send_payment(
parsed_invoice.bolt11.clone(),
req.amount_msat,
req.label.clone(),
)
.map_err(Into::into)
.await;
let payment = self
.on_payment_completed(
parsed_invoice.payee_pubkey.clone(),
Some(parsed_invoice),
req.label,
payment_res,
)
let pending_payment =
self.persist_pending_payment(&parsed_invoice, amount_msat, req.label.clone())?;
self.notify_event_listeners(BreezEvent::payment_started(pending_payment.clone()))
.await?;
Ok(SendPaymentResponse { payment })

let pending_timeout_sec = req.pending_timeout_sec.unwrap_or(u64::MAX);
let (tx, rx) = std::sync::mpsc::channel();
let cloned = self.clone();
tokio::spawn(async move {
let payment_res = cloned
.node_api
.send_payment(
parsed_invoice.bolt11.clone(),
req.amount_msat,
req.label.clone(),
)
.map_err(Into::into)
.await;
let result = cloned
.on_payment_completed(
parsed_invoice.payee_pubkey.clone(),
Some(parsed_invoice),
req.label,
payment_res,
)
.await;
let _ = tx.send(result);
});

match rx.recv_timeout(Duration::from_secs(pending_timeout_sec)) {
Ok(result) => result.map(SendPaymentResponse::from_payment),
Err(_) => Ok(SendPaymentResponse::from_payment(pending_payment)),
}
}
}
}
Expand Down Expand Up @@ -367,7 +390,10 @@ impl BreezServices {
/// is made.
///
/// This method will return an [anyhow::Error] when any validation check fails.
pub async fn lnurl_pay(&self, req: LnUrlPayRequest) -> Result<LnUrlPayResult, LnUrlPayError> {
pub async fn lnurl_pay(
self: &Arc<BreezServices>,
req: LnUrlPayRequest,
) -> Result<LnUrlPayResult, LnUrlPayError> {
match validate_lnurl_pay(
req.amount_msat,
req.comment,
Expand All @@ -382,8 +408,8 @@ impl BreezServices {
ValidatedCallbackResponse::EndpointSuccess { data: cb } => {
let pay_req = SendPaymentRequest {
bolt11: cb.pr.clone(),
amount_msat: None,
label: req.payment_label,
..Default::default()
};
let invoice = parse_invoice(cb.pr.as_str())?;

Expand Down Expand Up @@ -1171,7 +1197,7 @@ impl BreezServices {
invoice: &LNInvoice,
amount_msat: u64,
label: Option<String>,
) -> Result<(), SendPaymentError> {
) -> Result<Payment, SendPaymentError> {
self.persister.insert_or_update_payments(
&[Payment {
id: invoice.payment_hash.clone(),
Expand Down Expand Up @@ -1218,7 +1244,12 @@ impl BreezServices {
attempted_error: None,
},
)?;
Ok(())

self.persister
.get_payment_by_hash(&invoice.payment_hash)?
.ok_or(SendPaymentError::Generic {
err: "Payment not found".to_string(),
})
}

async fn on_payment_completed(
Expand Down
3 changes: 3 additions & 0 deletions libs/sdk-core/src/bridge_generated.io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,7 @@ impl Wire2Api<SendPaymentRequest> for wire_SendPaymentRequest {
bolt11: self.bolt11.wire2api(),
amount_msat: self.amount_msat.wire2api(),
label: self.label.wire2api(),
pending_timeout_sec: self.pending_timeout_sec.wire2api(),
}
}
}
Expand Down Expand Up @@ -1390,6 +1391,7 @@ pub struct wire_SendPaymentRequest {
bolt11: *mut wire_uint_8_list,
amount_msat: *mut u64,
label: *mut wire_uint_8_list,
pending_timeout_sec: *mut u64,
}

#[repr(C)]
Expand Down Expand Up @@ -1997,6 +1999,7 @@ impl NewWithNullPtr for wire_SendPaymentRequest {
bolt11: core::ptr::null_mut(),
amount_msat: core::ptr::null_mut(),
label: core::ptr::null_mut(),
pending_timeout_sec: core::ptr::null_mut(),
}
}
}
Expand Down
15 changes: 9 additions & 6 deletions libs/sdk-core/src/bridge_generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1105,19 +1105,22 @@ impl support::IntoDart for BreezEvent {
vec![1.into_dart(), details.into_into_dart().into_dart()]
}
Self::Synced => vec![2.into_dart()],
Self::PaymentSucceed { details } => {
Self::PaymentStarted { details } => {
vec![3.into_dart(), details.into_into_dart().into_dart()]
}
Self::PaymentFailed { details } => {
Self::PaymentSucceed { details } => {
vec![4.into_dart(), details.into_into_dart().into_dart()]
}
Self::BackupStarted => vec![5.into_dart()],
Self::BackupSucceeded => vec![6.into_dart()],
Self::PaymentFailed { details } => {
vec![5.into_dart(), details.into_into_dart().into_dart()]
}
Self::BackupStarted => vec![6.into_dart()],
Self::BackupSucceeded => vec![7.into_dart()],
Self::BackupFailed { details } => {
vec![7.into_dart(), details.into_into_dart().into_dart()]
vec![8.into_dart(), details.into_into_dart().into_dart()]
}
Self::SwapUpdated { details } => {
vec![8.into_dart(), details.into_into_dart().into_dart()]
vec![9.into_dart(), details.into_into_dart().into_dart()]
}
}
.into_dart()
Expand Down
11 changes: 10 additions & 1 deletion libs/sdk-core/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ pub struct Config {
/// the folder should exist before starting the SDK.
pub working_dir: String,
pub network: Network,
/// Maps to the CLN `retry_for` config when paying invoices (`lightning-pay`)
pub payment_timeout_sec: u32,
pub default_lsp_id: Option<String>,
pub api_key: Option<String>,
Expand Down Expand Up @@ -871,14 +872,17 @@ pub struct ReceivePaymentResponse {
}

/// Represents a send payment request.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct SendPaymentRequest {
/// The bolt11 invoice
pub bolt11: String,
/// The amount to pay in millisatoshis. Should only be set when `bolt11` is a zero-amount invoice.
pub amount_msat: Option<u64>,
/// The external label or identifier of the [Payment]
pub label: Option<String>,
/// If set, a timeout in seconds, that [crate::BreezServices::send_payment] will return a
/// pending payment if not already finished.
pub pending_timeout_sec: Option<u64>,
}

/// Represents a TLV entry for a keysend payment.
Expand Down Expand Up @@ -908,6 +912,11 @@ pub struct SendSpontaneousPaymentRequest {
pub struct SendPaymentResponse {
pub payment: Payment,
}
impl SendPaymentResponse {
pub(crate) fn from_payment(payment: Payment) -> Self {
Self { payment }
}
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ReportPaymentFailureDetails {
Expand Down
1 change: 1 addition & 0 deletions libs/sdk-flutter/ios/Classes/bridge_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ typedef struct wire_SendPaymentRequest {
struct wire_uint_8_list *bolt11;
uint64_t *amount_msat;
struct wire_uint_8_list *label;
uint64_t *pending_timeout_sec;
} wire_SendPaymentRequest;

typedef struct wire_TlvEntry {
Expand Down
29 changes: 24 additions & 5 deletions libs/sdk-flutter/lib/bridge_generated.dart
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,11 @@ sealed class BreezEvent with _$BreezEvent {
/// Indicates that the local SDK state has just been sync-ed with the remote components
const factory BreezEvent.synced() = BreezEvent_Synced;

/// Indicates that an outgoing payment has started
const factory BreezEvent.paymentStarted({
required Payment details,
}) = BreezEvent_PaymentStarted;

/// Indicates that an outgoing payment has been completed successfully
const factory BreezEvent.paymentSucceed({
required Payment details,
Expand Down Expand Up @@ -520,6 +525,8 @@ class Config {
/// the folder should exist before starting the SDK.
final String workingDir;
final Network network;

/// Maps to the CLN `retry_for` config when paying invoices (`lightning-pay`)
final int paymentTimeoutSec;
final String? defaultLspId;
final String? apiKey;
Expand Down Expand Up @@ -1796,10 +1803,15 @@ class SendPaymentRequest {
/// The external label or identifier of the [Payment]
final String? label;

/// If set, a timeout in seconds, that [crate::BreezServices::send_payment] will return a
/// pending payment if not already finished.
final int? pendingTimeoutSec;

const SendPaymentRequest({
required this.bolt11,
this.amountMsat,
this.label,
this.pendingTimeoutSec,
});
}

Expand Down Expand Up @@ -3315,22 +3327,26 @@ class BreezSdkCoreImpl implements BreezSdkCore {
case 2:
return BreezEvent_Synced();
case 3:
return BreezEvent_PaymentSucceed(
return BreezEvent_PaymentStarted(
details: _wire2api_box_autoadd_payment(raw[1]),
);
case 4:
return BreezEvent_PaymentSucceed(
details: _wire2api_box_autoadd_payment(raw[1]),
);
case 5:
return BreezEvent_PaymentFailed(
details: _wire2api_box_autoadd_payment_failed_data(raw[1]),
);
case 5:
return BreezEvent_BackupStarted();
case 6:
return BreezEvent_BackupSucceeded();
return BreezEvent_BackupStarted();
case 7:
return BreezEvent_BackupSucceeded();
case 8:
return BreezEvent_BackupFailed(
details: _wire2api_box_autoadd_backup_failed_data(raw[1]),
);
case 8:
case 9:
return BreezEvent_SwapUpdated(
details: _wire2api_box_autoadd_swap_info(raw[1]),
);
Expand Down Expand Up @@ -5044,6 +5060,7 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase<BreezSdkCoreWire> {
wireObj.bolt11 = api2wire_String(apiObj.bolt11);
wireObj.amount_msat = api2wire_opt_box_autoadd_u64(apiObj.amountMsat);
wireObj.label = api2wire_opt_String(apiObj.label);
wireObj.pending_timeout_sec = api2wire_opt_box_autoadd_u64(apiObj.pendingTimeoutSec);
}

void _api_fill_to_wire_send_spontaneous_payment_request(
Expand Down Expand Up @@ -6590,6 +6607,8 @@ final class wire_SendPaymentRequest extends ffi.Struct {
external ffi.Pointer<ffi.Uint64> amount_msat;

external ffi.Pointer<wire_uint_8_list> label;

external ffi.Pointer<ffi.Uint64> pending_timeout_sec;
}

final class wire_TlvEntry extends ffi.Struct {
Expand Down
Loading

0 comments on commit fd46a2f

Please sign in to comment.