Sfoglia il codice sorgente

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.
thesimplekid 2 mesi fa
parent
commit
ecdc78135d

+ 52 - 55
crates/cdk-cln/src/lib.rs

@@ -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())),

+ 3 - 5
crates/cdk-common/src/mint.rs

@@ -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}"),
         }
     }
 }

+ 2 - 17
crates/cdk-common/src/payment.rs

@@ -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

+ 1 - 0
crates/cdk-fake-wallet/Cargo.toml

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

+ 4 - 7
crates/cdk-fake-wallet/src/lib.rs

@@ -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(),

+ 2 - 3
crates/cdk-lnbits/src/lib.rs

@@ -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(),
                 })
             }

+ 2 - 3
crates/cdk-lnd/src/lib.rs

@@ -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(),
                 })
             }

+ 0 - 1
crates/cdk-payment-processor/src/proto/client.rs

@@ -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),
                         },
                     )),

+ 3 - 25
crates/cdk-payment-processor/src/proto/mod.rs

@@ -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!(),
-            }),
         }
     }
 }

+ 1 - 12
crates/cdk-payment-processor/src/proto/payment_processor.proto

@@ -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;
 }
 

+ 0 - 2
crates/cdk-payment-processor/src/proto/server.rs

@@ -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),
                     }),
                 );

+ 2 - 0
crates/cdk-sql-common/src/mint/migrations.rs

@@ -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"#)),
 ];

+ 6 - 0
crates/cdk-sql-common/src/mint/migrations/postgres/2_remove_request_lookup_kind_constraints.sql

@@ -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;

+ 35 - 0
crates/cdk-sql-common/src/mint/migrations/sqlite/20250819200000_remove_request_lookup_kind_constraints.sql

@@ -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);

+ 21 - 28
crates/cdk-sql-common/src/mint/mod.rs

@@ -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,

+ 28 - 31
crates/cdk/src/mint/melt.rs

@@ -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);

+ 30 - 30
crates/cdk/src/mint/start_up_check.rs

@@ -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
-            );
+                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,
-            };
+                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
-                );
-            };
+                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?;