Skip to content

Commit a81046d

Browse files
committed
payments: Better error message if paying ourselves
At least twice now, our users have reported that they encountered "We've already tried paying this invoice" when in fact they were trying to pay themselves... This improves the error message to distinguish between paying someone else's invoice twice vs trying to pay oneself. - Adds `PaymentsManager::get_payment`. - Removes `PaymentsManager::contains_payment_id` which requires a `finalized` `HashSet` which grows indefinitely
1 parent 8d4e2d7 commit a81046d

File tree

2 files changed

+43
-18
lines changed

2 files changed

+43
-18
lines changed

lexe-ln/src/command.rs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ use lexe_api::{
4646
Empty,
4747
invoice::LxInvoice,
4848
offer::{LxOffer, MaxQuantity},
49-
payments::LxPaymentId,
49+
payments::{LxPaymentId, PaymentDirection},
5050
},
5151
vfs::{REVOCABLE_CLIENTS_FILE_ID, Vfs},
5252
};
@@ -1287,13 +1287,23 @@ where
12871287
{
12881288
let invoice = req.invoice;
12891289

1290-
// Fail expired invoices early.
1290+
// Fail early if invoice is expired.
12911291
ensure!(!invoice.is_expired(), "Invoice has expired");
12921292

1293-
// Fail invoice double-payment early.
1293+
// Fail early if we already tried paying this invoice,
1294+
// or we are trying to pay ourselves (yes, users actually do this).
12941295
let payment_id = LxPaymentId::Lightning(invoice.payment_hash());
1295-
if payments_manager.contains_payment_id(&payment_id).await {
1296-
bail!("We've already tried paying this invoice");
1296+
let maybe_existing_payment = payments_manager
1297+
.get_payment(payment_id)
1298+
.await
1299+
.context("Couldn't check for existing payment")?;
1300+
if let Some(existing_payment) = maybe_existing_payment {
1301+
match existing_payment.direction() {
1302+
PaymentDirection::Outbound =>
1303+
return Err(anyhow!("We've already tried paying this invoice")),
1304+
// Yes, users actually hit this case...
1305+
PaymentDirection::Inbound => bail!("We cannot pay ourselves"),
1306+
}
12971307
}
12981308

12991309
// Compute the amount; handle amountless invoices.
@@ -1378,7 +1388,7 @@ where
13781388
{
13791389
let offer = req.offer;
13801390

1381-
// Fail expired offers early.
1391+
// Fail early if offer is expired.
13821392
ensure!(!offer.is_expired(), "Offer has expired");
13831393

13841394
// We only support paying BTC-denominated offers at the moment.
@@ -1387,11 +1397,17 @@ where
13871397
"Fiat-denominated offers are not supported yet"
13881398
);
13891399

1390-
// Fail offer double-payment early.
1400+
// Fail early if we already tried paying with this client ID.
13911401
let payment_id = LxPaymentId::OfferSend(req.cid);
1392-
if payments_manager.contains_payment_id(&payment_id).await {
1393-
bail!("We've already tried paying this offer");
1394-
}
1402+
let maybe_existing_payment = payments_manager
1403+
.get_payment(payment_id)
1404+
.await
1405+
.context("Couldn't check for existing payment")?;
1406+
ensure!(
1407+
maybe_existing_payment.is_none(),
1408+
"Detected duplicate attempt trying to pay this offer. \
1409+
Please refresh and try again."
1410+
);
13951411

13961412
// TODO(phlip9): support user choosing quantity. For now just assume
13971413
// quantity=1, but in a way that works.

lexe-ln/src/payments/manager.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -290,10 +290,23 @@ impl<CM: LexeChannelManager<PS>, PS: LexePersister> PaymentsManager<CM, PS> {
290290
Ok(())
291291
}
292292

293-
/// Returns true if we already have a payment with the given [`LxPaymentId`]
294-
/// registered.
295-
pub async fn contains_payment_id(&self, id: &LxPaymentId) -> bool {
296-
self.data.lock().await.contains_payment_id(id)
293+
/// Get a [`Payment`] by its [`LxPaymentId`].
294+
/// Returns the in-memory payment if cached, fetches from the DB otherwise.
295+
pub async fn get_payment(
296+
&self,
297+
id: LxPaymentId,
298+
) -> anyhow::Result<Option<Payment>> {
299+
{
300+
let locked_data = self.data.lock().await;
301+
if let Some(payment) = locked_data.pending.get(&id).cloned() {
302+
return Ok(Some(payment));
303+
}
304+
305+
// TODO(max): Maybe cache finalized payments that were fetched?
306+
// Then we could early return here as well.
307+
}
308+
309+
self.persister.get_payment_by_id(id).await
297310
}
298311

299312
/// Attempt to update the personal note on a payment.
@@ -839,10 +852,6 @@ impl PaymentsData {
839852
}
840853
}
841854

842-
fn contains_payment_id(&self, id: &LxPaymentId) -> bool {
843-
self.pending.contains_key(id) || self.finalized.contains(id)
844-
}
845-
846855
fn check_new_payment(
847856
&self,
848857
payment: Payment,

0 commit comments

Comments
 (0)