Skip to main content

cdk_common/
payment.rs

1//! CDK Mint Lightning
2
3use std::convert::Infallible;
4use std::pin::Pin;
5
6use async_trait::async_trait;
7use cashu::util::hex;
8use cashu::{Bolt11Invoice, MeltOptions};
9#[cfg(feature = "prometheus")]
10use cdk_prometheus::METRICS;
11use futures::Stream;
12use lightning::offers::offer::Offer;
13use lightning_invoice::ParseOrSemanticError;
14use serde::{Deserialize, Serialize};
15use serde_json::Value;
16use thiserror::Error;
17
18use crate::mint::MeltPaymentRequest;
19use crate::nuts::{CurrencyUnit, MeltQuoteState};
20use crate::Amount;
21
22/// CDK Lightning Error
23#[derive(Debug, Error)]
24pub enum Error {
25    /// Invoice already paid
26    #[error("Invoice already paid")]
27    InvoiceAlreadyPaid,
28    /// Invoice pay pending
29    #[error("Invoice pay is pending")]
30    InvoicePaymentPending,
31    /// Unsupported unit
32    #[error("Unsupported unit")]
33    UnsupportedUnit,
34    /// Unsupported payment option
35    #[error("Unsupported payment option")]
36    UnsupportedPaymentOption,
37    /// Payment state is unknown
38    #[error("Payment state is unknown")]
39    UnknownPaymentState,
40    /// Amount mismatch
41    #[error("Amount is not what is expected")]
42    AmountMismatch,
43    /// Lightning Error
44    #[error(transparent)]
45    Lightning(Box<dyn std::error::Error + Send + Sync>),
46    /// Serde Error
47    #[error(transparent)]
48    Serde(#[from] serde_json::Error),
49    /// AnyHow Error
50    #[error(transparent)]
51    Anyhow(#[from] anyhow::Error),
52    /// Parse Error
53    #[error(transparent)]
54    Parse(#[from] ParseOrSemanticError),
55    /// Amount Error
56    #[error(transparent)]
57    Amount(#[from] crate::amount::Error),
58    /// NUT04 Error
59    #[error(transparent)]
60    NUT04(#[from] crate::nuts::nut04::Error),
61    /// NUT05 Error
62    #[error(transparent)]
63    NUT05(#[from] crate::nuts::nut05::Error),
64    /// NUT23 Error
65    #[error(transparent)]
66    NUT23(#[from] crate::nuts::nut23::Error),
67    /// Hex error
68    #[error("Hex error")]
69    Hex(#[from] hex::Error),
70    /// Invalid hash
71    #[error("Invalid hash")]
72    InvalidHash,
73    /// Custom
74    #[error("`{0}`")]
75    Custom(String),
76}
77
78impl From<Infallible> for Error {
79    fn from(_: Infallible) -> Self {
80        unreachable!("Infallible cannot be constructed")
81    }
82}
83
84/// Payment identifier types
85#[derive(Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
86#[serde(tag = "type", content = "value")]
87pub enum PaymentIdentifier {
88    /// Label identifier
89    Label(String),
90    /// Offer ID identifier
91    OfferId(String),
92    /// Payment hash identifier
93    PaymentHash([u8; 32]),
94    /// Bolt12 payment hash
95    Bolt12PaymentHash([u8; 32]),
96    /// Payment id
97    PaymentId([u8; 32]),
98    /// Custom Payment ID
99    CustomId(String),
100}
101
102impl PaymentIdentifier {
103    /// Create new [`PaymentIdentifier`]
104    pub fn new(kind: &str, identifier: &str) -> Result<Self, Error> {
105        match kind.to_lowercase().as_str() {
106            "label" => Ok(Self::Label(identifier.to_string())),
107            "offer_id" => Ok(Self::OfferId(identifier.to_string())),
108            "payment_hash" => Ok(Self::PaymentHash(
109                hex::decode(identifier)?
110                    .try_into()
111                    .map_err(|_| Error::InvalidHash)?,
112            )),
113            "bolt12_payment_hash" => Ok(Self::Bolt12PaymentHash(
114                hex::decode(identifier)?
115                    .try_into()
116                    .map_err(|_| Error::InvalidHash)?,
117            )),
118            "custom" => Ok(Self::CustomId(identifier.to_string())),
119            "payment_id" => Ok(Self::PaymentId(
120                hex::decode(identifier)?
121                    .try_into()
122                    .map_err(|_| Error::InvalidHash)?,
123            )),
124            _ => Err(Error::UnsupportedPaymentOption),
125        }
126    }
127
128    /// Payment id kind
129    pub fn kind(&self) -> String {
130        match self {
131            Self::Label(_) => "label".to_string(),
132            Self::OfferId(_) => "offer_id".to_string(),
133            Self::PaymentHash(_) => "payment_hash".to_string(),
134            Self::Bolt12PaymentHash(_) => "bolt12_payment_hash".to_string(),
135            Self::PaymentId(_) => "payment_id".to_string(),
136            Self::CustomId(_) => "custom".to_string(),
137        }
138    }
139}
140
141impl std::fmt::Display for PaymentIdentifier {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        match self {
144            Self::Label(l) => write!(f, "{l}"),
145            Self::OfferId(o) => write!(f, "{o}"),
146            Self::PaymentHash(h) => write!(f, "{}", hex::encode(h)),
147            Self::Bolt12PaymentHash(h) => write!(f, "{}", hex::encode(h)),
148            Self::PaymentId(h) => write!(f, "{}", hex::encode(h)),
149            Self::CustomId(c) => write!(f, "{c}"),
150        }
151    }
152}
153
154impl std::fmt::Debug for PaymentIdentifier {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        match self {
157            PaymentIdentifier::PaymentHash(h) => write!(f, "PaymentHash({})", hex::encode(h)),
158            PaymentIdentifier::Bolt12PaymentHash(h) => {
159                write!(f, "Bolt12PaymentHash({})", hex::encode(h))
160            }
161            PaymentIdentifier::PaymentId(h) => write!(f, "PaymentId({})", hex::encode(h)),
162            PaymentIdentifier::Label(s) => write!(f, "Label({})", s),
163            PaymentIdentifier::OfferId(s) => write!(f, "OfferId({})", s),
164            PaymentIdentifier::CustomId(s) => write!(f, "CustomId({})", s),
165        }
166    }
167}
168
169/// Options for creating a BOLT11 incoming payment request
170#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
171pub struct Bolt11IncomingPaymentOptions {
172    /// Optional description for the payment request
173    pub description: Option<String>,
174    /// Amount for the payment request in sats
175    pub amount: Amount,
176    /// Optional expiry time as Unix timestamp in seconds
177    pub unix_expiry: Option<u64>,
178}
179
180/// Options for creating a BOLT12 incoming payment request
181#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
182pub struct Bolt12IncomingPaymentOptions {
183    /// Optional description for the payment request
184    pub description: Option<String>,
185    /// Optional amount for the payment request in sats
186    pub amount: Option<Amount>,
187    /// Optional expiry time as Unix timestamp in seconds
188    pub unix_expiry: Option<u64>,
189}
190
191/// Options for creating a custom incoming payment request
192#[derive(Debug, Clone, PartialEq, Eq, Hash)]
193pub struct CustomIncomingPaymentOptions {
194    /// Payment method name (e.g., "paypal", "venmo")
195    pub method: String,
196    /// Optional description for the payment request
197    pub description: Option<String>,
198    /// Amount for the payment request
199    pub amount: Amount,
200    /// Optional expiry time as Unix timestamp in seconds
201    pub unix_expiry: Option<u64>,
202    /// Extra payment-method-specific fields as JSON string
203    ///
204    /// These fields are passed through to the payment processor for
205    /// method-specific validation (e.g., ehash share).
206    pub extra_json: Option<String>,
207}
208
209/// Options for creating an incoming payment request
210#[derive(Debug, Clone, PartialEq, Eq, Hash)]
211pub enum IncomingPaymentOptions {
212    /// BOLT11 payment request options
213    Bolt11(Bolt11IncomingPaymentOptions),
214    /// BOLT12 payment request options
215    Bolt12(Box<Bolt12IncomingPaymentOptions>),
216    /// Custom payment method options
217    Custom(Box<CustomIncomingPaymentOptions>),
218}
219
220/// Options for BOLT11 outgoing payments
221#[derive(Debug, Clone, PartialEq, Eq, Hash)]
222pub struct Bolt11OutgoingPaymentOptions {
223    /// Bolt11
224    pub bolt11: Bolt11Invoice,
225    /// Maximum fee amount allowed for the payment
226    pub max_fee_amount: Option<Amount>,
227    /// Optional timeout in seconds
228    pub timeout_secs: Option<u64>,
229    /// Melt options
230    pub melt_options: Option<MeltOptions>,
231}
232
233/// Options for BOLT12 outgoing payments
234#[derive(Debug, Clone, PartialEq, Eq, Hash)]
235pub struct Bolt12OutgoingPaymentOptions {
236    /// Offer
237    pub offer: Offer,
238    /// Maximum fee amount allowed for the payment
239    pub max_fee_amount: Option<Amount>,
240    /// Optional timeout in seconds
241    pub timeout_secs: Option<u64>,
242    /// Melt options
243    pub melt_options: Option<MeltOptions>,
244}
245
246/// Options for custom outgoing payments
247#[derive(Debug, Clone, PartialEq, Eq, Hash)]
248pub struct CustomOutgoingPaymentOptions {
249    /// Payment method name
250    pub method: String,
251    /// Payment request string (method-specific format)
252    pub request: String,
253    /// Maximum fee amount allowed for the payment
254    pub max_fee_amount: Option<Amount>,
255    /// Optional timeout in seconds
256    pub timeout_secs: Option<u64>,
257    /// Melt options
258    pub melt_options: Option<MeltOptions>,
259    /// Extra payment-method-specific fields as JSON string
260    ///
261    /// These fields are passed through to the payment processor for
262    /// method-specific validation.
263    pub extra_json: Option<String>,
264}
265
266/// Options for creating an outgoing payment
267#[derive(Debug, Clone, PartialEq, Eq, Hash)]
268pub enum OutgoingPaymentOptions {
269    /// BOLT11 payment options
270    Bolt11(Box<Bolt11OutgoingPaymentOptions>),
271    /// BOLT12 payment options
272    Bolt12(Box<Bolt12OutgoingPaymentOptions>),
273    /// Custom payment method options
274    Custom(Box<CustomOutgoingPaymentOptions>),
275}
276
277impl TryFrom<crate::mint::MeltQuote> for OutgoingPaymentOptions {
278    type Error = Error;
279
280    fn try_from(melt_quote: crate::mint::MeltQuote) -> Result<Self, Self::Error> {
281        let fee_reserve = melt_quote.fee_reserve();
282        match &melt_quote.request {
283            MeltPaymentRequest::Bolt11 { bolt11 } => Ok(OutgoingPaymentOptions::Bolt11(Box::new(
284                Bolt11OutgoingPaymentOptions {
285                    max_fee_amount: Some(fee_reserve.to_owned().into()),
286                    timeout_secs: None,
287                    bolt11: bolt11.clone(),
288                    melt_options: melt_quote.options,
289                },
290            ))),
291            MeltPaymentRequest::Bolt12 { offer } => {
292                let melt_options = match melt_quote.options {
293                    None => None,
294                    Some(MeltOptions::Mpp { mpp: _ }) => return Err(Error::UnsupportedUnit),
295                    Some(options) => Some(options),
296                };
297
298                Ok(OutgoingPaymentOptions::Bolt12(Box::new(
299                    Bolt12OutgoingPaymentOptions {
300                        max_fee_amount: Some(fee_reserve.clone().into()),
301                        timeout_secs: None,
302                        offer: *offer.clone(),
303                        melt_options,
304                    },
305                )))
306            }
307            MeltPaymentRequest::Custom { method, request } => Ok(OutgoingPaymentOptions::Custom(
308                Box::new(CustomOutgoingPaymentOptions {
309                    method: method.to_string(),
310                    request: request.to_string(),
311                    max_fee_amount: Some(melt_quote.fee_reserve().into()),
312                    timeout_secs: None,
313                    melt_options: melt_quote.options,
314                    extra_json: None,
315                }),
316            )),
317        }
318    }
319}
320
321/// Mint payment trait
322#[async_trait]
323pub trait MintPayment {
324    /// Mint Lightning Error
325    type Err: Into<Error> + From<Error>;
326
327    /// Start the payment processor
328    /// Called when the mint starts up to initialize the payment processor
329    async fn start(&self) -> Result<(), Self::Err> {
330        // Default implementation - do nothing
331        Ok(())
332    }
333
334    /// Stop the payment processor
335    /// Called when the mint shuts down to gracefully stop the payment processor
336    async fn stop(&self) -> Result<(), Self::Err> {
337        // Default implementation - do nothing
338        Ok(())
339    }
340
341    /// Base Settings
342    async fn get_settings(&self) -> Result<SettingsResponse, Self::Err>;
343
344    /// Create a new invoice
345    async fn create_incoming_payment_request(
346        &self,
347        unit: &CurrencyUnit,
348        options: IncomingPaymentOptions,
349    ) -> Result<CreateIncomingPaymentResponse, Self::Err>;
350
351    /// Get payment quote
352    /// Used to get fee and amount required for a payment request
353    async fn get_payment_quote(
354        &self,
355        unit: &CurrencyUnit,
356        options: OutgoingPaymentOptions,
357    ) -> Result<PaymentQuoteResponse, Self::Err>;
358
359    /// Pay request
360    async fn make_payment(
361        &self,
362        unit: &CurrencyUnit,
363        options: OutgoingPaymentOptions,
364    ) -> Result<MakePaymentResponse, Self::Err>;
365
366    /// Listen for invoices to be paid to the mint
367    /// Returns a stream of request_lookup_id once invoices are paid
368    async fn wait_payment_event(
369        &self,
370    ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err>;
371
372    /// Is wait invoice active
373    fn is_wait_invoice_active(&self) -> bool;
374
375    /// Cancel wait invoice
376    fn cancel_wait_invoice(&self);
377
378    /// Check the status of an incoming payment
379    async fn check_incoming_payment_status(
380        &self,
381        payment_identifier: &PaymentIdentifier,
382    ) -> Result<Vec<WaitPaymentResponse>, Self::Err>;
383
384    /// Check the status of an outgoing payment
385    async fn check_outgoing_payment(
386        &self,
387        payment_identifier: &PaymentIdentifier,
388    ) -> Result<MakePaymentResponse, Self::Err>;
389}
390
391/// An event emitted which should be handled by the mint
392#[derive(Debug, Clone, Hash)]
393pub enum Event {
394    /// A payment has been received.
395    PaymentReceived(WaitPaymentResponse),
396}
397
398impl Default for Event {
399    fn default() -> Self {
400        // We use this as a sentinel value for no-op events
401        // The actual processing will filter these out
402        Event::PaymentReceived(WaitPaymentResponse {
403            payment_identifier: PaymentIdentifier::CustomId("default".to_string()),
404            payment_amount: Amount::new(0, CurrencyUnit::Msat),
405            payment_id: "default".to_string(),
406        })
407    }
408}
409
410/// Wait any invoice response
411#[derive(Debug, Clone, Hash)]
412pub struct WaitPaymentResponse {
413    /// Request look up id
414    /// Id that relates the quote and payment request
415    pub payment_identifier: PaymentIdentifier,
416    /// Payment amount (typed with unit for compile-time safety)
417    pub payment_amount: Amount<CurrencyUnit>,
418    /// Unique id of payment
419    // Payment hash
420    pub payment_id: String,
421}
422
423impl WaitPaymentResponse {
424    /// Get the currency unit
425    pub fn unit(&self) -> &CurrencyUnit {
426        self.payment_amount.unit()
427    }
428}
429
430/// Create incoming payment response
431#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
432pub struct CreateIncomingPaymentResponse {
433    /// Id that is used to look up the payment from the ln backend
434    pub request_lookup_id: PaymentIdentifier,
435    /// Payment request
436    pub request: String,
437    /// Unix Expiry of Invoice
438    pub expiry: Option<u64>,
439    /// Extra payment-method-specific fields
440    ///
441    /// These fields are flattened into the JSON representation, allowing
442    /// custom payment methods to include additional data without nesting.
443    #[serde(flatten, default)]
444    pub extra_json: Option<serde_json::Value>,
445}
446
447/// Payment response
448#[derive(Debug, Clone, Hash, PartialEq, Eq)]
449pub struct MakePaymentResponse {
450    /// Payment hash
451    pub payment_lookup_id: PaymentIdentifier,
452    /// Payment proof
453    pub payment_proof: Option<String>,
454    /// Status
455    pub status: MeltQuoteState,
456    /// Total Amount Spent (typed with unit for compile-time safety)
457    pub total_spent: Amount<CurrencyUnit>,
458}
459
460impl MakePaymentResponse {
461    /// Get the currency unit
462    pub fn unit(&self) -> &CurrencyUnit {
463        self.total_spent.unit()
464    }
465}
466
467/// Payment quote response
468#[derive(Debug, Clone, Hash, PartialEq, Eq)]
469pub struct PaymentQuoteResponse {
470    /// Request look up id
471    pub request_lookup_id: Option<PaymentIdentifier>,
472    /// Amount (typed with unit for compile-time safety)
473    pub amount: Amount<CurrencyUnit>,
474    /// Fee required for melt (typed with unit for compile-time safety)
475    pub fee: Amount<CurrencyUnit>,
476    /// Status
477    pub state: MeltQuoteState,
478}
479
480impl PaymentQuoteResponse {
481    /// Get the currency unit
482    pub fn unit(&self) -> &CurrencyUnit {
483        self.amount.unit()
484    }
485}
486
487/// BOLT11 settings
488#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
489pub struct Bolt11Settings {
490    /// Multi-part payment (MPP) supported
491    pub mpp: bool,
492    /// Amountless invoice support
493    pub amountless: bool,
494    /// Invoice description supported
495    pub invoice_description: bool,
496}
497
498/// BOLT12 settings
499#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
500pub struct Bolt12Settings {
501    /// Amountless offer support
502    pub amountless: bool,
503}
504
505/// Payment processor settings response
506/// Mirrors the proto SettingsResponse structure
507#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
508pub struct SettingsResponse {
509    /// Base unit of backend
510    pub unit: String,
511    /// BOLT11 settings (None if not supported)
512    pub bolt11: Option<Bolt11Settings>,
513    /// BOLT12 settings (None if not supported)
514    pub bolt12: Option<Bolt12Settings>,
515    /// Custom payment methods settings (method name -> settings data)
516    #[serde(default)]
517    pub custom: std::collections::HashMap<String, String>,
518}
519
520impl From<SettingsResponse> for Value {
521    fn from(value: SettingsResponse) -> Self {
522        serde_json::to_value(value).unwrap_or(Value::Null)
523    }
524}
525
526impl TryFrom<Value> for SettingsResponse {
527    type Error = crate::error::Error;
528
529    fn try_from(value: Value) -> Result<Self, Self::Error> {
530        serde_json::from_value(value).map_err(|err| err.into())
531    }
532}
533
534/// Metrics wrapper for MintPayment implementations
535///
536/// This wrapper implements the Decorator pattern to collect metrics on all
537/// MintPayment trait methods. It wraps any existing MintPayment implementation
538/// and automatically records timing and operation metrics.
539#[derive(Debug, Clone)]
540#[cfg(feature = "prometheus")]
541pub struct MetricsMintPayment<T> {
542    inner: T,
543}
544#[cfg(feature = "prometheus")]
545impl<T> MetricsMintPayment<T>
546where
547    T: MintPayment,
548{
549    /// Create a new metrics wrapper around a MintPayment implementation
550    pub fn new(inner: T) -> Self {
551        Self { inner }
552    }
553
554    /// Get reference to the underlying implementation
555    pub fn inner(&self) -> &T {
556        &self.inner
557    }
558
559    /// Consume the wrapper and return the inner implementation
560    pub fn into_inner(self) -> T {
561        self.inner
562    }
563}
564
565#[async_trait]
566#[cfg(feature = "prometheus")]
567impl<T> MintPayment for MetricsMintPayment<T>
568where
569    T: MintPayment + Send + Sync,
570{
571    type Err = T::Err;
572
573    async fn get_settings(&self) -> Result<SettingsResponse, Self::Err> {
574        let start = std::time::Instant::now();
575        METRICS.inc_in_flight_requests("get_settings");
576
577        let result = self.inner.get_settings().await;
578
579        let duration = start.elapsed().as_secs_f64();
580        METRICS.record_mint_operation_histogram("get_settings", result.is_ok(), duration);
581        METRICS.dec_in_flight_requests("get_settings");
582
583        result
584    }
585
586    async fn create_incoming_payment_request(
587        &self,
588        unit: &CurrencyUnit,
589        options: IncomingPaymentOptions,
590    ) -> Result<CreateIncomingPaymentResponse, Self::Err> {
591        let start = std::time::Instant::now();
592        METRICS.inc_in_flight_requests("create_incoming_payment_request");
593
594        let result = self
595            .inner
596            .create_incoming_payment_request(unit, options)
597            .await;
598
599        let duration = start.elapsed().as_secs_f64();
600        METRICS.record_mint_operation_histogram(
601            "create_incoming_payment_request",
602            result.is_ok(),
603            duration,
604        );
605        METRICS.dec_in_flight_requests("create_incoming_payment_request");
606
607        result
608    }
609
610    async fn get_payment_quote(
611        &self,
612        unit: &CurrencyUnit,
613        options: OutgoingPaymentOptions,
614    ) -> Result<PaymentQuoteResponse, Self::Err> {
615        let start = std::time::Instant::now();
616        METRICS.inc_in_flight_requests("get_payment_quote");
617
618        let result = self.inner.get_payment_quote(unit, options).await;
619
620        let duration = start.elapsed().as_secs_f64();
621        let success = result.is_ok();
622
623        if let Ok(ref quote) = result {
624            let amount: f64 = quote.amount.value() as f64;
625            let fee: f64 = quote.fee.value() as f64;
626            METRICS.record_lightning_payment(amount, fee);
627        }
628
629        METRICS.record_mint_operation_histogram("get_payment_quote", success, duration);
630        METRICS.dec_in_flight_requests("get_payment_quote");
631
632        result
633    }
634    async fn wait_payment_event(
635        &self,
636    ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err> {
637        let start = std::time::Instant::now();
638        METRICS.inc_in_flight_requests("wait_payment_event");
639
640        let result = self.inner.wait_payment_event().await;
641
642        let duration = start.elapsed().as_secs_f64();
643        let success = result.is_ok();
644
645        METRICS.record_mint_operation_histogram("wait_payment_event", success, duration);
646        METRICS.dec_in_flight_requests("wait_payment_event");
647
648        result
649    }
650
651    async fn make_payment(
652        &self,
653        unit: &CurrencyUnit,
654        options: OutgoingPaymentOptions,
655    ) -> Result<MakePaymentResponse, Self::Err> {
656        let start = std::time::Instant::now();
657        METRICS.inc_in_flight_requests("make_payment");
658
659        let result = self.inner.make_payment(unit, options).await;
660
661        let duration = start.elapsed().as_secs_f64();
662        let success = result.is_ok();
663
664        METRICS.record_mint_operation_histogram("make_payment", success, duration);
665        METRICS.dec_in_flight_requests("make_payment");
666
667        result
668    }
669
670    fn is_wait_invoice_active(&self) -> bool {
671        self.inner.is_wait_invoice_active()
672    }
673
674    fn cancel_wait_invoice(&self) {
675        self.inner.cancel_wait_invoice()
676    }
677
678    async fn check_incoming_payment_status(
679        &self,
680        payment_identifier: &PaymentIdentifier,
681    ) -> Result<Vec<WaitPaymentResponse>, Self::Err> {
682        let start = std::time::Instant::now();
683        METRICS.inc_in_flight_requests("check_incoming_payment_status");
684
685        let result = self
686            .inner
687            .check_incoming_payment_status(payment_identifier)
688            .await;
689
690        let duration = start.elapsed().as_secs_f64();
691        METRICS.record_mint_operation_histogram(
692            "check_incoming_payment_status",
693            result.is_ok(),
694            duration,
695        );
696        METRICS.dec_in_flight_requests("check_incoming_payment_status");
697
698        result
699    }
700
701    async fn check_outgoing_payment(
702        &self,
703        payment_identifier: &PaymentIdentifier,
704    ) -> Result<MakePaymentResponse, Self::Err> {
705        let start = std::time::Instant::now();
706        METRICS.inc_in_flight_requests("check_outgoing_payment");
707
708        let result = self.inner.check_outgoing_payment(payment_identifier).await;
709
710        let duration = start.elapsed().as_secs_f64();
711        let success = result.is_ok();
712
713        METRICS.record_mint_operation_histogram("check_outgoing_payment", success, duration);
714        METRICS.dec_in_flight_requests("check_outgoing_payment");
715
716        result
717    }
718}
719
720/// Type alias for Mint Payment trait
721pub type DynMintPayment = std::sync::Arc<dyn MintPayment<Err = Error> + Send + Sync>;