feat: check if invoice already paid on melt

feat: check if internal invoice already settled
This commit is contained in:
thesimplekid
2024-07-06 10:24:38 +01:00
parent 1b64e5d8fa
commit 46f8689b1f
7 changed files with 101 additions and 8 deletions

View File

@@ -213,6 +213,12 @@ pub async fn post_melt_bolt11(
let (preimage, amount_spent_quote_unit) = match mint_quote {
Some(mint_quote) => {
if mint_quote.state == MintQuoteState::Issued
|| mint_quote.state == MintQuoteState::Paid
{
return Err(into_response(Error::RequestAlreadyPaid));
}
let mut mint_quote = mint_quote;
if mint_quote.amount > inputs_amount_quote_unit {
@@ -320,7 +326,12 @@ pub async fn post_melt_bolt11(
tracing::error!("Could not reset melt quote state: {}", err);
}
return Err(into_response(Error::PaymentFailed));
let err = match err {
cdk::cdk_lightning::Error::InvoiceAlreadyPaid => Error::RequestAlreadyPaid,
_ => Error::PaymentFailed,
};
return Err(into_response(err));
}
};

View File

@@ -2,15 +2,15 @@ use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
/// Invoice amount not defined
#[error("Unknown invoice amount")]
UnknownInvoiceAmount,
/// Wrong CLN response
#[error("Wrong cln response")]
WrongClnResponse,
/// Unknown invoice
#[error("Unknown invoice")]
UnknownInvoice,
/// Invoice amount not defined
#[error("Unknown invoice amount")]
UnknownInvoiceAmount,
/// Cln Error
#[error(transparent)]
Cln(#[from] cln_rpc::Error),

View File

@@ -15,9 +15,11 @@ use cdk::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuoteS
use cdk::util::{hex, unix_time};
use cdk::{mint, Bolt11Invoice};
use cln_rpc::model::requests::{
InvoiceRequest, ListinvoicesRequest, PayRequest, WaitanyinvoiceRequest,
InvoiceRequest, ListinvoicesRequest, ListpaysRequest, PayRequest, WaitanyinvoiceRequest,
};
use cln_rpc::model::responses::{
ListinvoicesInvoicesStatus, ListpaysPaysStatus, PayStatus, WaitanyinvoiceResponse,
};
use cln_rpc::model::responses::{ListinvoicesInvoicesStatus, PayStatus, WaitanyinvoiceResponse};
use cln_rpc::model::Request;
use cln_rpc::primitives::{Amount as CLN_Amount, AmountOrAny};
use error::Error;
@@ -131,6 +133,22 @@ impl MintLightning for Cln {
max_fee_msats: Option<u64>,
) -> Result<PayInvoiceResponse, Self::Err> {
let mut cln_client = self.cln_client.lock().await;
let pay_state =
check_pay_invoice_status(&mut cln_client, melt_quote.request.to_string()).await?;
match pay_state {
MeltQuoteState::Paid => {
tracing::debug!("Melt attempted on invoice already paid");
return Err(Self::Err::InvoiceAlreadyPaid);
}
MeltQuoteState::Pending => {
tracing::debug!("Melt attempted on invoice already pending");
return Err(Self::Err::InvoicePaymentPending);
}
MeltQuoteState::Unpaid => (),
}
let cln_response = cln_client
.call(Request::Pay(PayRequest {
bolt11: melt_quote.request.to_string(),
@@ -190,7 +208,7 @@ impl MintLightning for Cln {
.call(cln_rpc::Request::Invoice(InvoiceRequest {
amount_msat,
description,
label: Uuid::new_v4().to_string(),
label: label.clone(),
expiry: Some(unix_expiry - time_now),
fallbacks: None,
preimage: None,
@@ -293,3 +311,38 @@ fn cln_invoice_status_to_mint_state(status: ListinvoicesInvoicesStatus) -> MintQ
ListinvoicesInvoicesStatus::EXPIRED => MintQuoteState::Unpaid,
}
}
async fn check_pay_invoice_status(
cln_client: &mut cln_rpc::ClnRpc,
bolt11: String,
) -> Result<MeltQuoteState, cdk_lightning::Error> {
let cln_response = cln_client
.call(Request::ListPays(ListpaysRequest {
bolt11: Some(bolt11),
payment_hash: None,
status: None,
}))
.await
.map_err(Error::from)?;
let state = match cln_response {
cln_rpc::Response::ListPays(pay_response) => {
let pay = pay_response.pays.first();
match pay {
Some(pay) => match pay.status {
ListpaysPaysStatus::COMPLETE => MeltQuoteState::Paid,
ListpaysPaysStatus::PENDING => MeltQuoteState::Pending,
ListpaysPaysStatus::FAILED => MeltQuoteState::Unpaid,
},
None => MeltQuoteState::Unpaid,
}
}
_ => {
tracing::warn!("CLN returned wrong response kind. When checking pay status");
return Err(cdk_lightning::Error::from(Error::WrongClnResponse));
}
};
Ok(state)
}

View File

@@ -202,11 +202,17 @@ async fn main() -> anyhow::Result<()> {
/// Update mint quote when called for a paid invoice
async fn handle_paid_invoice(mint: Arc<Mint>, request_lookup_id: &str) -> Result<()> {
tracing::debug!("Invoice with lookup id paid: {}", request_lookup_id);
if let Ok(Some(mint_quote)) = mint
.localstore
.get_mint_quote_by_request_lookup_id(request_lookup_id)
.await
{
tracing::debug!(
"Quote {} paid by lookup id {}",
mint_quote.id,
request_lookup_id
);
mint.localstore
.update_mint_quote_state(&mint_quote.id, cdk::nuts::MintQuoteState::Paid)
.await?;

View File

@@ -14,6 +14,12 @@ use crate::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuot
/// CDK Lightning Error
#[derive(Debug, Error)]
pub enum Error {
/// Invoice already paid
#[error("Invoice already paid")]
InvoiceAlreadyPaid,
/// Invoice pay pending
#[error("Invoice pay is pending")]
InvoicePaymentPending,
/// Lightning Error
#[error(transparent)]
Lightning(Box<dyn std::error::Error + Send + Sync>),

View File

@@ -30,6 +30,9 @@ pub enum Error {
/// Melt Request is not valid
#[error("Melt request is not valid")]
MeltRequestInvalid,
/// Invoice already paid
#[error("Request already paid")]
RequestAlreadyPaid,
/// Amount is not what expected
#[error("Amount miss match")]
Amount,

View File

@@ -124,7 +124,21 @@ impl Mint {
expiry: u64,
ln_lookup: String,
) -> Result<MintQuote, Error> {
let quote = MintQuote::new(mint_url, request, unit, amount, expiry, ln_lookup);
let quote = MintQuote::new(
mint_url,
request,
unit.clone(),
amount,
expiry,
ln_lookup.clone(),
);
tracing::debug!(
"New mint quote {} for {} {} with request id {}",
quote.id,
amount,
unit,
&ln_lookup
);
self.localstore.add_mint_quote(quote.clone()).await?;