refactor(cdk): defer BOLT12 invoice fetching to payment execution (#978)

* refactor(cdk): defer BOLT12 invoice fetching to payment execution

Move BOLT12 invoice generation from quote creation to payment time, make
request_lookup_id optional for offers, and simplify payment structures by
removing unnecessary boxing and intermediate storage of invoices.
This commit is contained in:
thesimplekid
2025-08-20 11:20:30 +01:00
committed by GitHub
parent 5c5075af71
commit ecdc78135d
17 changed files with 193 additions and 220 deletions

View File

@@ -307,13 +307,12 @@ impl MintPayment for Cln {
let fee = max(relative_fee_reserve, absolute_fee_reserve);
Ok(PaymentQuoteResponse {
request_lookup_id: PaymentIdentifier::PaymentHash(
request_lookup_id: Some(PaymentIdentifier::PaymentHash(
*bolt11_options.bolt11.payment_hash().as_ref(),
),
)),
amount,
fee: fee.into(),
state: MeltQuoteState::Unpaid,
options: None,
unit: unit.clone(),
})
}
@@ -341,52 +340,11 @@ impl MintPayment for Cln {
let absolute_fee_reserve: u64 = self.fee_reserve.min_fee_reserve.into();
let fee = max(relative_fee_reserve, absolute_fee_reserve);
let cln_response;
{
// Fetch invoice from offer
let mut cln_client = self.cln_client().await?;
cln_response = cln_client
.call_typed(&FetchinvoiceRequest {
amount_msat: Some(CLN_Amount::from_msat(amount_msat)),
payer_metadata: None,
payer_note: None,
quantity: None,
recurrence_counter: None,
recurrence_label: None,
recurrence_start: None,
timeout: None,
offer: offer.to_string(),
bip353: None,
})
.await
.map_err(|err| {
tracing::error!("Could not fetch invoice for offer: {:?}", err);
Error::ClnRpc(err)
})?;
}
let decode_response = self.decode_string(cln_response.invoice.clone()).await?;
let options = payment::PaymentQuoteOptions::Bolt12 {
invoice: Some(cln_response.invoice.into()),
};
Ok(PaymentQuoteResponse {
request_lookup_id: PaymentIdentifier::Bolt12PaymentHash(
hex::decode(
decode_response
.invoice_payment_hash
.ok_or(Error::UnknownInvoice)?,
)
.unwrap()
.try_into()
.map_err(|_| Error::InvalidHash)?,
),
request_lookup_id: None,
amount,
fee: fee.into(),
state: MeltQuoteState::Unpaid,
options: Some(options),
unit: unit.clone(),
})
}
@@ -403,7 +361,9 @@ impl MintPayment for Cln {
let mut partial_amount: Option<u64> = None;
let mut amount_msat: Option<u64> = None;
let invoice = match options {
let mut cln_client = self.cln_client().await?;
let invoice = match &options {
OutgoingPaymentOptions::Bolt11(bolt11_options) => {
let payment_identifier =
PaymentIdentifier::PaymentHash(*bolt11_options.bolt11.payment_hash().as_ref());
@@ -424,10 +384,42 @@ impl MintPayment for Cln {
bolt11_options.bolt11.to_string()
}
OutgoingPaymentOptions::Bolt12(bolt12_options) => {
let bolt12_invoice = bolt12_options.invoice.ok_or(Error::UnknownInvoice)?;
let decode_response = self
.decode_string(String::from_utf8(bolt12_invoice.clone()).map_err(Error::Utf8)?)
.await?;
let offer = &bolt12_options.offer;
let amount_msat: u64 = if let Some(amount) = bolt12_options.melt_options {
amount.amount_msat().into()
} else {
// Fall back to offer amount
let decode_response = self.decode_string(offer.to_string()).await?;
decode_response
.offer_amount_msat
.ok_or(Error::UnknownInvoiceAmount)?
.msat()
};
// Fetch invoice from offer
let cln_response = cln_client
.call_typed(&FetchinvoiceRequest {
amount_msat: Some(CLN_Amount::from_msat(amount_msat)),
payer_metadata: None,
payer_note: None,
quantity: None,
recurrence_counter: None,
recurrence_label: None,
recurrence_start: None,
timeout: None,
offer: offer.to_string(),
bip353: None,
})
.await
.map_err(|err| {
tracing::error!("Could not fetch invoice for offer: {:?}", err);
Error::ClnRpc(err)
})?;
let decode_response = self.decode_string(cln_response.invoice.clone()).await?;
let payment_identifier = PaymentIdentifier::Bolt12PaymentHash(
hex::decode(
@@ -443,12 +435,11 @@ impl MintPayment for Cln {
self.check_outgoing_unpaided(&payment_identifier).await?;
max_fee_msat = bolt12_options.max_fee_amount.map(|a| a.into());
String::from_utf8(bolt12_invoice).map_err(Error::Utf8)?
cln_response.invoice
}
};
let mut cln_client = self.cln_client().await?;
let cln_response = cln_client
.call_typed(&PayRequest {
bolt11: invoice,
@@ -475,8 +466,14 @@ impl MintPayment for Cln {
PayStatus::FAILED => MeltQuoteState::Failed,
};
let payment_identifier =
PaymentIdentifier::PaymentHash(*pay_response.payment_hash.as_ref());
let payment_identifier = match options {
OutgoingPaymentOptions::Bolt11(_) => {
PaymentIdentifier::PaymentHash(*pay_response.payment_hash.as_ref())
}
OutgoingPaymentOptions::Bolt12(_) => {
PaymentIdentifier::Bolt12PaymentHash(*pay_response.payment_hash.as_ref())
}
};
MakePaymentResponse {
payment_proof: Some(hex::encode(pay_response.payment_preimage.to_vec())),

View File

@@ -246,7 +246,7 @@ pub struct MeltQuote {
/// Payment preimage
pub payment_preimage: Option<String>,
/// Value used by ln backend to look up state of request
pub request_lookup_id: PaymentIdentifier,
pub request_lookup_id: Option<PaymentIdentifier>,
/// Payment options
///
/// Used for amountless invoices and MPP payments
@@ -270,7 +270,7 @@ impl MeltQuote {
amount: Amount,
fee_reserve: Amount,
expiry: u64,
request_lookup_id: PaymentIdentifier,
request_lookup_id: Option<PaymentIdentifier>,
options: Option<MeltOptions>,
payment_method: PaymentMethod,
) -> Self {
@@ -433,8 +433,6 @@ pub enum MeltPaymentRequest {
/// Offer
#[serde(with = "offer_serde")]
offer: Box<Offer>,
/// Invoice
invoice: Option<Vec<u8>>,
},
}
@@ -442,7 +440,7 @@ impl std::fmt::Display for MeltPaymentRequest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MeltPaymentRequest::Bolt11 { bolt11 } => write!(f, "{bolt11}"),
MeltPaymentRequest::Bolt12 { offer, invoice: _ } => write!(f, "{offer}"),
MeltPaymentRequest::Bolt12 { offer } => write!(f, "{offer}"),
}
}
}

View File

@@ -193,8 +193,6 @@ pub struct Bolt12OutgoingPaymentOptions {
pub max_fee_amount: Option<Amount>,
/// Optional timeout in seconds
pub timeout_secs: Option<u64>,
/// Bolt12 Invoice
pub invoice: Option<Vec<u8>>,
/// Melt options
pub melt_options: Option<MeltOptions>,
}
@@ -221,7 +219,7 @@ impl TryFrom<crate::mint::MeltQuote> for OutgoingPaymentOptions {
melt_options: melt_quote.options,
},
))),
MeltPaymentRequest::Bolt12 { offer, invoice } => {
MeltPaymentRequest::Bolt12 { offer } => {
let melt_options = match melt_quote.options {
None => None,
Some(MeltOptions::Mpp { mpp: _ }) => return Err(Error::UnsupportedUnit),
@@ -233,7 +231,6 @@ impl TryFrom<crate::mint::MeltQuote> for OutgoingPaymentOptions {
max_fee_amount: Some(melt_quote.fee_reserve),
timeout_secs: None,
offer: *offer,
invoice,
melt_options,
},
)))
@@ -343,7 +340,7 @@ pub struct MakePaymentResponse {
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct PaymentQuoteResponse {
/// Request look up id
pub request_lookup_id: PaymentIdentifier,
pub request_lookup_id: Option<PaymentIdentifier>,
/// Amount
pub amount: Amount,
/// Fee required for melt
@@ -352,18 +349,6 @@ pub struct PaymentQuoteResponse {
pub unit: CurrencyUnit,
/// Status
pub state: MeltQuoteState,
/// Payment Quote Options
pub options: Option<PaymentQuoteOptions>,
}
/// Payment quote options
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum PaymentQuoteOptions {
/// Bolt12 payment options
Bolt12 {
/// Bolt12 invoice
invoice: Option<Vec<u8>>,
},
}
/// Ln backend settings

View File

@@ -25,3 +25,4 @@ lightning-invoice.workspace = true
lightning.workspace = true
tokio-stream.workspace = true
reqwest.workspace = true
uuid.workspace = true

View File

@@ -43,6 +43,7 @@ use tokio::time;
use tokio_stream::wrappers::ReceiverStream;
use tokio_util::sync::CancellationToken;
use tracing::instrument;
use uuid::Uuid;
pub mod error;
@@ -354,7 +355,7 @@ impl MintPayment for FakeWallet {
};
let payment_id =
PaymentIdentifier::PaymentHash(*bolt11_options.bolt11.payment_hash().as_ref());
(amount_msat, payment_id)
(amount_msat, Some(payment_id))
}
OutgoingPaymentOptions::Bolt12(bolt12_options) => {
let offer = bolt12_options.offer;
@@ -369,10 +370,7 @@ impl MintPayment for FakeWallet {
_ => return Err(Error::UnknownInvoiceAmount.into()),
}
};
(
amount_msat,
PaymentIdentifier::OfferId(offer.id().to_string()),
)
(amount_msat, None)
}
};
@@ -390,7 +388,6 @@ impl MintPayment for FakeWallet {
amount,
fee: fee.into(),
state: MeltQuoteState::Unpaid,
options: None,
unit: unit.clone(),
})
}
@@ -471,7 +468,7 @@ impl MintPayment for FakeWallet {
Ok(MakePaymentResponse {
payment_proof: Some("".to_string()),
payment_lookup_id: PaymentIdentifier::OfferId(bolt12.id().to_string()),
payment_lookup_id: PaymentIdentifier::CustomId(Uuid::new_v4().to_string()),
status: MeltQuoteState::Paid,
total_spent: total_spent + 1.into(),
unit: unit.clone(),

View File

@@ -204,13 +204,12 @@ impl MintPayment for LNbits {
let fee = max(relative_fee_reserve, absolute_fee_reserve);
Ok(PaymentQuoteResponse {
request_lookup_id: PaymentIdentifier::PaymentHash(
request_lookup_id: Some(PaymentIdentifier::PaymentHash(
*bolt11_options.bolt11.payment_hash().as_ref(),
),
)),
amount,
fee: fee.into(),
state: MeltQuoteState::Unpaid,
options: None,
unit: unit.clone(),
})
}

View File

@@ -248,13 +248,12 @@ impl MintPayment for Lnd {
let fee = max(relative_fee_reserve, absolute_fee_reserve);
Ok(PaymentQuoteResponse {
request_lookup_id: PaymentIdentifier::PaymentHash(
request_lookup_id: Some(PaymentIdentifier::PaymentHash(
*bolt11_options.bolt11.payment_hash().as_ref(),
),
)),
amount,
fee: fee.into(),
state: MeltQuoteState::Unpaid,
options: None,
unit: unit.clone(),
})
}

View File

@@ -229,7 +229,6 @@ impl MintPayment for PaymentProcessorClient {
offer: opts.offer.to_string(),
max_fee_amount: opts.max_fee_amount.map(Into::into),
timeout_secs: opts.timeout_secs,
invoice: opts.invoice,
melt_options: opts.melt_options.map(Into::into),
},
)),

View File

@@ -137,22 +137,11 @@ impl TryFrom<CreatePaymentResponse> for CreateIncomingPaymentResponse {
impl From<cdk_common::payment::PaymentQuoteResponse> for PaymentQuoteResponse {
fn from(value: cdk_common::payment::PaymentQuoteResponse) -> Self {
Self {
request_identifier: Some(value.request_lookup_id.into()),
request_identifier: value.request_lookup_id.map(|i| i.into()),
amount: value.amount.into(),
fee: value.fee.into(),
unit: value.unit.to_string(),
state: QuoteState::from(value.state).into(),
melt_options: value.options.map(|opt| match opt {
cdk_common::payment::PaymentQuoteOptions::Bolt12 { invoice } => {
PaymentQuoteOptions {
melt_options: Some(payment_quote_options::MeltOptions::Bolt12(
Bolt12Options {
invoice: invoice.map(String::from_utf8).and_then(|r| r.ok()),
},
)),
}
}
}),
}
}
}
@@ -160,26 +149,15 @@ impl From<cdk_common::payment::PaymentQuoteResponse> for PaymentQuoteResponse {
impl From<PaymentQuoteResponse> for cdk_common::payment::PaymentQuoteResponse {
fn from(value: PaymentQuoteResponse) -> Self {
let state_val = value.state();
let request_identifier = value
.request_identifier
.expect("request identifier required");
let request_identifier = value.request_identifier;
Self {
request_lookup_id: request_identifier
.try_into()
.expect("valid request identifier"),
.map(|i| i.try_into().expect("valid request identifier")),
amount: value.amount.into(),
fee: value.fee.into(),
unit: CurrencyUnit::from_str(&value.unit).unwrap_or_default(),
state: state_val.into(),
options: value.melt_options.map(|opt| match opt.melt_options {
Some(payment_quote_options::MeltOptions::Bolt12(bolt12)) => {
cdk_common::payment::PaymentQuoteOptions::Bolt12 {
invoice: bolt12.invoice.as_deref().map(str::as_bytes).map(Vec::from),
}
}
None => unreachable!(),
}),
}
}
}

View File

@@ -105,23 +105,13 @@ enum QuoteState {
ISSUED = 5;
}
message Bolt12Options {
optional string invoice = 1;
}
message PaymentQuoteOptions {
oneof melt_options {
Bolt12Options bolt12 = 1;
}
}
message PaymentQuoteResponse {
PaymentIdentifier request_identifier = 1;
uint64 amount = 2;
uint64 fee = 3;
QuoteState state = 4;
optional PaymentQuoteOptions melt_options = 5;
string unit = 6;
string unit = 5;
}
message Bolt11OutgoingPaymentOptions {
@@ -135,7 +125,6 @@ message Bolt12OutgoingPaymentOptions {
string offer = 1;
optional uint64 max_fee_amount = 2;
optional uint64 timeout_secs = 3;
optional bytes invoice = 4;
optional MeltOptions melt_options = 5;
}

View File

@@ -250,7 +250,6 @@ impl CdkPaymentProcessor for PaymentProcessorServer {
offer: Offer::from_str(&request.request).unwrap(),
max_fee_amount: None,
timeout_secs: None,
invoice: None,
melt_options: request.options.map(Into::into),
},
))
@@ -308,7 +307,6 @@ impl CdkPaymentProcessor for PaymentProcessorServer {
offer,
max_fee_amount: opts.max_fee_amount.map(Into::into),
timeout_secs: opts.timeout_secs,
invoice: opts.invoice,
melt_options: opts.melt_options.map(Into::into),
}),
);

View File

@@ -2,6 +2,7 @@
/// Auto-generated by build.rs
pub static MIGRATIONS: &[(&str, &str, &str)] = &[
("postgres", "1_initial.sql", include_str!(r#"./migrations/postgres/1_initial.sql"#)),
("postgres", "2_remove_request_lookup_kind_constraints.sql", include_str!(r#"./migrations/postgres/2_remove_request_lookup_kind_constraints.sql"#)),
("sqlite", "1_fix_sqlx_migration.sql", include_str!(r#"./migrations/sqlite/1_fix_sqlx_migration.sql"#)),
("sqlite", "20240612124932_init.sql", include_str!(r#"./migrations/sqlite/20240612124932_init.sql"#)),
("sqlite", "20240618195700_quote_state.sql", include_str!(r#"./migrations/sqlite/20240618195700_quote_state.sql"#)),
@@ -25,4 +26,5 @@ pub static MIGRATIONS: &[(&str, &str, &str)] = &[
("sqlite", "20250626120251_rename_blind_message_y_to_b.sql", include_str!(r#"./migrations/sqlite/20250626120251_rename_blind_message_y_to_b.sql"#)),
("sqlite", "20250706101057_bolt12.sql", include_str!(r#"./migrations/sqlite/20250706101057_bolt12.sql"#)),
("sqlite", "20250812132015_drop_melt_request.sql", include_str!(r#"./migrations/sqlite/20250812132015_drop_melt_request.sql"#)),
("sqlite", "20250819200000_remove_request_lookup_kind_constraints.sql", include_str!(r#"./migrations/sqlite/20250819200000_remove_request_lookup_kind_constraints.sql"#)),
];

View File

@@ -0,0 +1,6 @@
-- Set existing NULL or empty request_lookup_id_kind values to 'payment_hash' in melt_quote
UPDATE melt_quote SET request_lookup_id_kind = 'payment_hash' WHERE request_lookup_id_kind IS NULL OR request_lookup_id_kind = '';
-- Remove NOT NULL constraint and default value from request_lookup_id_kind in melt_quote table
ALTER TABLE melt_quote ALTER COLUMN request_lookup_id_kind DROP NOT NULL;
ALTER TABLE melt_quote ALTER COLUMN request_lookup_id_kind DROP DEFAULT;

View File

@@ -0,0 +1,35 @@
-- Set existing NULL or empty request_lookup_id_kind values to 'payment_hash' in melt_quote
UPDATE melt_quote SET request_lookup_id_kind = 'payment_hash' WHERE request_lookup_id_kind IS NULL OR request_lookup_id_kind = '';
-- Remove NOT NULL constraint and default value from request_lookup_id_kind in melt_quote table
CREATE TABLE melt_quote_temp (
id TEXT PRIMARY KEY,
unit TEXT NOT NULL,
amount INTEGER NOT NULL,
request TEXT NOT NULL,
fee_reserve INTEGER NOT NULL,
expiry INTEGER NOT NULL,
state TEXT CHECK (
state IN ('UNPAID', 'PENDING', 'PAID')
) NOT NULL DEFAULT 'UNPAID',
payment_preimage TEXT,
request_lookup_id TEXT,
created_time INTEGER NOT NULL DEFAULT 0,
paid_time INTEGER,
payment_method TEXT NOT NULL DEFAULT 'bolt11',
options TEXT,
request_lookup_id_kind TEXT
);
INSERT INTO melt_quote_temp (id, unit, amount, request, fee_reserve, expiry, state, payment_preimage, request_lookup_id, created_time, paid_time, payment_method, options, request_lookup_id_kind)
SELECT id, unit, amount, request, fee_reserve, expiry, state, payment_preimage, request_lookup_id, created_time, paid_time, payment_method, options, request_lookup_id_kind
FROM melt_quote;
DROP TABLE melt_quote;
ALTER TABLE melt_quote_temp RENAME TO melt_quote;
-- Recreate indexes for melt_quote
CREATE INDEX IF NOT EXISTS melt_quote_state_index ON melt_quote(state);
CREATE UNIQUE INDEX IF NOT EXISTS unique_request_lookup_id_melt ON melt_quote(request_lookup_id);
CREATE INDEX IF NOT EXISTS idx_melt_quote_request_lookup_id_and_kind ON melt_quote(request_lookup_id, request_lookup_id_kind);

View File

@@ -739,24 +739,6 @@ VALUES (:quote_id, :amount, :timestamp);
async fn add_melt_quote(&mut self, quote: mint::MeltQuote) -> Result<(), Self::Err> {
// First try to find and replace any expired UNPAID quotes with the same request_lookup_id
let current_time = unix_time();
let row_affected = query(
r#"
DELETE FROM melt_quote
WHERE request_lookup_id = :request_lookup_id
AND state = :state
AND expiry < :current_time
"#,
)?
.bind("request_lookup_id", quote.request_lookup_id.to_string())
.bind("state", MeltQuoteState::Unpaid.to_string())
.bind("current_time", current_time as i64)
.execute(&self.inner)
.await?;
if row_affected > 0 {
tracing::info!("Received new melt quote for existing invoice with expired quote.");
}
// Now insert the new quote
query(
@@ -783,14 +765,20 @@ VALUES (:quote_id, :amount, :timestamp);
.bind("state", quote.state.to_string())
.bind("expiry", quote.expiry as i64)
.bind("payment_preimage", quote.payment_preimage)
.bind("request_lookup_id", quote.request_lookup_id.to_string())
.bind(
"request_lookup_id",
quote.request_lookup_id.as_ref().map(|id| id.to_string()),
)
.bind("created_time", quote.created_time as i64)
.bind("paid_time", quote.paid_time.map(|t| t as i64))
.bind(
"options",
quote.options.map(|o| serde_json::to_string(&o).ok()),
)
.bind("request_lookup_id_kind", quote.request_lookup_id.kind())
.bind(
"request_lookup_id_kind",
quote.request_lookup_id.map(|id| id.kind()),
)
.bind("payment_method", quote.payment_method.to_string())
.execute(&self.inner)
.await?;
@@ -1713,19 +1701,24 @@ fn sql_row_to_melt_quote(row: Vec<Column>) -> Result<mint::MeltQuote, Error> {
let unit = column_as_string!(unit);
let request = column_as_string!(request);
let mut request_lookup_id_kind = column_as_string!(request_lookup_id_kind);
let request_lookup_id_kind = column_as_nullable_string!(request_lookup_id_kind);
let request_lookup_id = column_as_nullable_string!(&request_lookup_id).unwrap_or_else(|| {
let request_lookup_id = column_as_nullable_string!(&request_lookup_id).or_else(|| {
Bolt11Invoice::from_str(&request)
.ok()
.map(|invoice| invoice.payment_hash().to_string())
.unwrap_or_else(|_| {
request_lookup_id_kind = "custom".to_string();
request.clone()
})
});
let request_lookup_id = PaymentIdentifier::new(&request_lookup_id_kind, &request_lookup_id)
.map_err(|_| ConversionError::MissingParameter("Payment id".to_string()))?;
let request_lookup_id = if let (Some(id_kind), Some(request_lookup_id)) =
(request_lookup_id_kind, request_lookup_id)
{
Some(
PaymentIdentifier::new(&id_kind, &request_lookup_id)
.map_err(|_| ConversionError::MissingParameter("Payment id".to_string()))?,
)
} else {
None
};
let request = match serde_json::from_str(&request) {
Ok(req) => req,

View File

@@ -9,7 +9,7 @@ use cdk_common::nut00::ProofsMethods;
use cdk_common::nut05::MeltMethodOptions;
use cdk_common::payment::{
Bolt11OutgoingPaymentOptions, Bolt12OutgoingPaymentOptions, OutgoingPaymentOptions,
PaymentQuoteOptions,
PaymentIdentifier,
};
use cdk_common::{MeltOptions, MeltQuoteBolt12Request};
use lightning::offers::offer::Offer;
@@ -202,7 +202,7 @@ impl Mint {
);
tracing::debug!(
"New {} melt quote {} for {} {} with request id {}",
"New {} melt quote {} for {} {} with request id {:?}",
quote.payment_method,
quote.id,
amount_quote_unit,
@@ -269,7 +269,6 @@ impl Mint {
max_fee_amount: None,
timeout_secs: None,
melt_options: *options,
invoice: None,
};
let payment_quote = ln
@@ -288,13 +287,8 @@ impl Mint {
Error::UnsupportedUnit
})?;
let invoice = payment_quote.options.and_then(|options| match options {
PaymentQuoteOptions::Bolt12 { invoice } => invoice,
});
let payment_request = MeltPaymentRequest::Bolt12 {
offer: Box::new(offer),
invoice,
};
let quote = MeltQuote::new(
@@ -309,7 +303,7 @@ impl Mint {
);
tracing::debug!(
"New {} melt quote {} for {} {} with request id {}",
"New {} melt quote {} for {} {} with request id {:?}",
quote.payment_method,
quote.id,
amount,
@@ -382,7 +376,7 @@ impl Mint {
.ok_or(Error::InvoiceAmountUndefined)?
.amount_msat(),
},
MeltPaymentRequest::Bolt12 { offer, invoice: _ } => match offer.amount() {
MeltPaymentRequest::Bolt12 { offer } => match offer.amount() {
Some(amount) => {
let (amount, currency) = match amount {
lightning::offers::offer::Amount::Bitcoin { amount_msats } => {
@@ -529,18 +523,15 @@ impl Mint {
use std::sync::Arc;
async fn check_payment_state(
ln: Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
melt_quote: &MeltQuote,
lookup_id: &PaymentIdentifier,
) -> anyhow::Result<MakePaymentResponse> {
match ln
.check_outgoing_payment(&melt_quote.request_lookup_id)
.await
{
match ln.check_outgoing_payment(lookup_id).await {
Ok(response) => Ok(response),
Err(check_err) => {
// If we cannot check the status of the payment we keep the proofs stuck as pending.
tracing::error!(
"Could not check the status of payment for {},. Proofs stuck as pending",
melt_quote.id
lookup_id
);
tracing::error!("Checking payment error: {}", check_err);
bail!("Could not check payment status")
@@ -616,12 +607,13 @@ impl Mint {
|| pay.status == MeltQuoteState::Failed =>
{
tracing::warn!("Got {} status when paying melt quote {} for {} {}. Checking with backend...", pay.status, quote.id, quote.amount, quote.unit);
let check_response =
if let Ok(ok) = check_payment_state(Arc::clone(ln), &quote).await {
ok
} else {
return Err(Error::Internal);
};
let check_response = if let Ok(ok) =
check_payment_state(Arc::clone(ln), &pay.payment_lookup_id).await
{
ok
} else {
return Err(Error::Internal);
};
if check_response.status == MeltQuoteState::Paid {
tracing::warn!("Pay invoice returned {} but check returned {}. Proofs stuck as pending", pay.status.to_string(), check_response.status.to_string());
@@ -644,8 +636,16 @@ impl Mint {
tracing::error!("Error returned attempting to pay: {} {}", quote.id, err);
let lookup_id = quote.request_lookup_id.as_ref().ok_or_else(|| {
tracing::error!(
"No payment id could not lookup payment for {} after error.",
quote.id
);
Error::Internal
})?;
let check_response =
if let Ok(ok) = check_payment_state(Arc::clone(ln), &quote).await {
if let Ok(ok) = check_payment_state(Arc::clone(ln), lookup_id).await {
ok
} else {
proof_writer.commit();
@@ -690,21 +690,18 @@ impl Mint {
let payment_lookup_id = pre.payment_lookup_id;
let mut tx = self.localstore.begin_transaction().await?;
if payment_lookup_id != quote.request_lookup_id {
if Some(payment_lookup_id.clone()).as_ref() != quote.request_lookup_id.as_ref() {
tracing::info!(
"Payment lookup id changed post payment from {} to {}",
quote.request_lookup_id,
"Payment lookup id changed post payment from {:?} to {}",
&quote.request_lookup_id,
payment_lookup_id
);
let mut melt_quote = quote;
melt_quote.request_lookup_id = payment_lookup_id;
melt_quote.request_lookup_id = Some(payment_lookup_id.clone());
if let Err(err) = tx
.update_melt_quote_request_lookup_id(
&melt_quote.id,
&melt_quote.request_lookup_id,
)
.update_melt_quote_request_lookup_id(&melt_quote.id, &payment_lookup_id)
.await
{
tracing::warn!("Could not update payment lookup id: {}", err);

View File

@@ -40,39 +40,39 @@ impl Mint {
}
};
let pay_invoice_response = ln_backend
.check_outgoing_payment(&pending_quote.request_lookup_id)
.await?;
if let Some(lookup_id) = pending_quote.request_lookup_id {
let pay_invoice_response = ln_backend.check_outgoing_payment(&lookup_id).await?;
tracing::warn!(
"There is no stored melt request for pending melt quote: {}",
pending_quote.id
);
let melt_quote_state = match pay_invoice_response.status {
MeltQuoteState::Unpaid => MeltQuoteState::Unpaid,
MeltQuoteState::Paid => MeltQuoteState::Paid,
MeltQuoteState::Pending => MeltQuoteState::Pending,
MeltQuoteState::Failed => MeltQuoteState::Unpaid,
MeltQuoteState::Unknown => MeltQuoteState::Unpaid,
};
if let Err(err) = tx
.update_melt_quote_state(
&pending_quote.id,
melt_quote_state,
pay_invoice_response.payment_proof,
)
.await
{
tracing::error!(
"Could not update quote {} to state {}, current state {}, {}",
pending_quote.id,
melt_quote_state,
pending_quote.state,
err
tracing::warn!(
"There is no stored melt request for pending melt quote: {}",
pending_quote.id
);
};
let melt_quote_state = match pay_invoice_response.status {
MeltQuoteState::Unpaid => MeltQuoteState::Unpaid,
MeltQuoteState::Paid => MeltQuoteState::Paid,
MeltQuoteState::Pending => MeltQuoteState::Pending,
MeltQuoteState::Failed => MeltQuoteState::Unpaid,
MeltQuoteState::Unknown => MeltQuoteState::Unpaid,
};
if let Err(err) = tx
.update_melt_quote_state(
&pending_quote.id,
melt_quote_state,
pay_invoice_response.payment_proof,
)
.await
{
tracing::error!(
"Could not update quote {} to state {}, current state {}, {}",
pending_quote.id,
melt_quote_state,
pending_quote.state,
err
);
};
}
}
tx.commit().await?;