mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-20 14:14:49 +01:00
feat: check if invoice already paid on melt
feat: check if internal invoice already settled
This commit is contained in:
@@ -213,6 +213,12 @@ pub async fn post_melt_bolt11(
|
|||||||
|
|
||||||
let (preimage, amount_spent_quote_unit) = match mint_quote {
|
let (preimage, amount_spent_quote_unit) = match mint_quote {
|
||||||
Some(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;
|
let mut mint_quote = mint_quote;
|
||||||
|
|
||||||
if mint_quote.amount > inputs_amount_quote_unit {
|
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);
|
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));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ use thiserror::Error;
|
|||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
/// Invoice amount not defined
|
||||||
|
#[error("Unknown invoice amount")]
|
||||||
|
UnknownInvoiceAmount,
|
||||||
/// Wrong CLN response
|
/// Wrong CLN response
|
||||||
#[error("Wrong cln response")]
|
#[error("Wrong cln response")]
|
||||||
WrongClnResponse,
|
WrongClnResponse,
|
||||||
/// Unknown invoice
|
/// Unknown invoice
|
||||||
#[error("Unknown invoice")]
|
#[error("Unknown invoice")]
|
||||||
UnknownInvoice,
|
UnknownInvoice,
|
||||||
/// Invoice amount not defined
|
|
||||||
#[error("Unknown invoice amount")]
|
|
||||||
UnknownInvoiceAmount,
|
|
||||||
/// Cln Error
|
/// Cln Error
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Cln(#[from] cln_rpc::Error),
|
Cln(#[from] cln_rpc::Error),
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ use cdk::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuoteS
|
|||||||
use cdk::util::{hex, unix_time};
|
use cdk::util::{hex, unix_time};
|
||||||
use cdk::{mint, Bolt11Invoice};
|
use cdk::{mint, Bolt11Invoice};
|
||||||
use cln_rpc::model::requests::{
|
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::model::Request;
|
||||||
use cln_rpc::primitives::{Amount as CLN_Amount, AmountOrAny};
|
use cln_rpc::primitives::{Amount as CLN_Amount, AmountOrAny};
|
||||||
use error::Error;
|
use error::Error;
|
||||||
@@ -131,6 +133,22 @@ impl MintLightning for Cln {
|
|||||||
max_fee_msats: Option<u64>,
|
max_fee_msats: Option<u64>,
|
||||||
) -> Result<PayInvoiceResponse, Self::Err> {
|
) -> Result<PayInvoiceResponse, Self::Err> {
|
||||||
let mut cln_client = self.cln_client.lock().await;
|
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
|
let cln_response = cln_client
|
||||||
.call(Request::Pay(PayRequest {
|
.call(Request::Pay(PayRequest {
|
||||||
bolt11: melt_quote.request.to_string(),
|
bolt11: melt_quote.request.to_string(),
|
||||||
@@ -190,7 +208,7 @@ impl MintLightning for Cln {
|
|||||||
.call(cln_rpc::Request::Invoice(InvoiceRequest {
|
.call(cln_rpc::Request::Invoice(InvoiceRequest {
|
||||||
amount_msat,
|
amount_msat,
|
||||||
description,
|
description,
|
||||||
label: Uuid::new_v4().to_string(),
|
label: label.clone(),
|
||||||
expiry: Some(unix_expiry - time_now),
|
expiry: Some(unix_expiry - time_now),
|
||||||
fallbacks: None,
|
fallbacks: None,
|
||||||
preimage: None,
|
preimage: None,
|
||||||
@@ -293,3 +311,38 @@ fn cln_invoice_status_to_mint_state(status: ListinvoicesInvoicesStatus) -> MintQ
|
|||||||
ListinvoicesInvoicesStatus::EXPIRED => MintQuoteState::Unpaid,
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -202,11 +202,17 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
/// Update mint quote when called for a paid invoice
|
/// Update mint quote when called for a paid invoice
|
||||||
async fn handle_paid_invoice(mint: Arc<Mint>, request_lookup_id: &str) -> Result<()> {
|
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
|
if let Ok(Some(mint_quote)) = mint
|
||||||
.localstore
|
.localstore
|
||||||
.get_mint_quote_by_request_lookup_id(request_lookup_id)
|
.get_mint_quote_by_request_lookup_id(request_lookup_id)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
tracing::debug!(
|
||||||
|
"Quote {} paid by lookup id {}",
|
||||||
|
mint_quote.id,
|
||||||
|
request_lookup_id
|
||||||
|
);
|
||||||
mint.localstore
|
mint.localstore
|
||||||
.update_mint_quote_state(&mint_quote.id, cdk::nuts::MintQuoteState::Paid)
|
.update_mint_quote_state(&mint_quote.id, cdk::nuts::MintQuoteState::Paid)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ use crate::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuot
|
|||||||
/// CDK Lightning Error
|
/// CDK Lightning Error
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
/// Invoice already paid
|
||||||
|
#[error("Invoice already paid")]
|
||||||
|
InvoiceAlreadyPaid,
|
||||||
|
/// Invoice pay pending
|
||||||
|
#[error("Invoice pay is pending")]
|
||||||
|
InvoicePaymentPending,
|
||||||
/// Lightning Error
|
/// Lightning Error
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Lightning(Box<dyn std::error::Error + Send + Sync>),
|
Lightning(Box<dyn std::error::Error + Send + Sync>),
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ pub enum Error {
|
|||||||
/// Melt Request is not valid
|
/// Melt Request is not valid
|
||||||
#[error("Melt request is not valid")]
|
#[error("Melt request is not valid")]
|
||||||
MeltRequestInvalid,
|
MeltRequestInvalid,
|
||||||
|
/// Invoice already paid
|
||||||
|
#[error("Request already paid")]
|
||||||
|
RequestAlreadyPaid,
|
||||||
/// Amount is not what expected
|
/// Amount is not what expected
|
||||||
#[error("Amount miss match")]
|
#[error("Amount miss match")]
|
||||||
Amount,
|
Amount,
|
||||||
|
|||||||
@@ -124,7 +124,21 @@ impl Mint {
|
|||||||
expiry: u64,
|
expiry: u64,
|
||||||
ln_lookup: String,
|
ln_lookup: String,
|
||||||
) -> Result<MintQuote, Error> {
|
) -> 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?;
|
self.localstore.add_mint_quote(quote.clone()).await?;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user