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, MeltQuote};
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, 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<CurrencyUnit>,
176    /// Optional expiry time as Unix timestamp in seconds
177    pub unix_expiry: Option<u64>,
178}
179
180impl Default for Bolt11IncomingPaymentOptions {
181    fn default() -> Self {
182        Self {
183            description: None,
184            amount: Amount::new(0, CurrencyUnit::Sat),
185            unix_expiry: None,
186        }
187    }
188}
189
190/// Options for creating a BOLT12 incoming payment request
191#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
192pub struct Bolt12IncomingPaymentOptions {
193    /// Optional description for the payment request
194    pub description: Option<String>,
195    /// Optional amount for the payment request in sats
196    pub amount: Option<Amount<CurrencyUnit>>,
197    /// Optional expiry time as Unix timestamp in seconds
198    pub unix_expiry: Option<u64>,
199}
200
201/// Options for creating a custom incoming payment request
202#[derive(Debug, Clone, PartialEq, Eq, Hash)]
203pub struct CustomIncomingPaymentOptions {
204    /// Payment method name (e.g., "paypal", "venmo")
205    pub method: String,
206    /// Optional description for the payment request
207    pub description: Option<String>,
208    /// Amount for the payment request
209    pub amount: Amount<CurrencyUnit>,
210    /// Optional expiry time as Unix timestamp in seconds
211    pub unix_expiry: Option<u64>,
212    /// Extra payment-method-specific fields as JSON string
213    ///
214    /// These fields are passed through to the payment processor for
215    /// method-specific validation (e.g., ehash share).
216    pub extra_json: Option<String>,
217}
218
219/// Options for creating an incoming payment request
220#[derive(Debug, Clone, PartialEq, Eq, Hash)]
221pub enum IncomingPaymentOptions {
222    /// BOLT11 payment request options
223    Bolt11(Bolt11IncomingPaymentOptions),
224    /// BOLT12 payment request options
225    Bolt12(Box<Bolt12IncomingPaymentOptions>),
226    /// Custom payment method options
227    Custom(Box<CustomIncomingPaymentOptions>),
228}
229
230/// Options for BOLT11 outgoing payments
231#[derive(Debug, Clone, PartialEq, Eq, Hash)]
232pub struct Bolt11OutgoingPaymentOptions {
233    /// Bolt11
234    pub bolt11: Bolt11Invoice,
235    /// Maximum fee amount allowed for the payment
236    pub max_fee_amount: Option<Amount<CurrencyUnit>>,
237    /// Optional timeout in seconds
238    pub timeout_secs: Option<u64>,
239    /// Melt options
240    pub melt_options: Option<MeltOptions>,
241}
242
243/// Options for BOLT12 outgoing payments
244#[derive(Debug, Clone, PartialEq, Eq, Hash)]
245pub struct Bolt12OutgoingPaymentOptions {
246    /// Offer
247    pub offer: Offer,
248    /// Maximum fee amount allowed for the payment
249    pub max_fee_amount: Option<Amount<CurrencyUnit>>,
250    /// Optional timeout in seconds
251    pub timeout_secs: Option<u64>,
252    /// Melt options
253    pub melt_options: Option<MeltOptions>,
254}
255
256/// Options for custom outgoing payments
257#[derive(Debug, Clone, PartialEq, Eq, Hash)]
258pub struct CustomOutgoingPaymentOptions {
259    /// Payment method name
260    pub method: String,
261    /// Payment request string (method-specific format)
262    pub request: String,
263    /// Maximum fee amount allowed for the payment
264    pub max_fee_amount: Option<Amount<CurrencyUnit>>,
265    /// Optional timeout in seconds
266    pub timeout_secs: Option<u64>,
267    /// Melt options
268    pub melt_options: Option<MeltOptions>,
269    /// Extra payment-method-specific fields as JSON string
270    ///
271    /// These fields are passed through to the payment processor for
272    /// method-specific validation.
273    pub extra_json: Option<String>,
274}
275
276/// Options for creating an outgoing payment
277#[derive(Debug, Clone, PartialEq, Eq, Hash)]
278pub enum OutgoingPaymentOptions {
279    /// BOLT11 payment options
280    Bolt11(Box<Bolt11OutgoingPaymentOptions>),
281    /// BOLT12 payment options
282    Bolt12(Box<Bolt12OutgoingPaymentOptions>),
283    /// Custom payment method options
284    Custom(Box<CustomOutgoingPaymentOptions>),
285}
286
287impl OutgoingPaymentOptions {
288    /// Creates payment options from a melt quote
289    pub fn from_melt_quote_with_fee(
290        melt_quote: MeltQuote,
291    ) -> Result<OutgoingPaymentOptions, Error> {
292        let fee_reserve = melt_quote.fee_reserve();
293        match &melt_quote.request {
294            MeltPaymentRequest::Bolt11 { bolt11 } => Ok(OutgoingPaymentOptions::Bolt11(Box::new(
295                Bolt11OutgoingPaymentOptions {
296                    max_fee_amount: Some(fee_reserve),
297                    timeout_secs: None,
298                    bolt11: bolt11.clone(),
299                    melt_options: melt_quote.options,
300                },
301            ))),
302            MeltPaymentRequest::Bolt12 { offer } => {
303                let melt_options = match melt_quote.options {
304                    Some(MeltOptions::Mpp { mpp: _ }) => return Err(Error::UnsupportedUnit),
305                    Some(options) => Some(options),
306                    _ => None,
307                };
308
309                Ok(OutgoingPaymentOptions::Bolt12(Box::new(
310                    Bolt12OutgoingPaymentOptions {
311                        max_fee_amount: Some(fee_reserve),
312                        timeout_secs: None,
313                        offer: *offer.clone(),
314                        melt_options,
315                    },
316                )))
317            }
318            MeltPaymentRequest::Custom { method, request } => Ok(OutgoingPaymentOptions::Custom(
319                Box::new(CustomOutgoingPaymentOptions {
320                    method: method.to_string(),
321                    request: request.to_string(),
322                    max_fee_amount: Some(fee_reserve),
323                    timeout_secs: None,
324                    melt_options: melt_quote.options,
325                    extra_json: None,
326                }),
327            )),
328        }
329    }
330}
331
332/// Mint payment trait
333#[async_trait]
334pub trait MintPayment {
335    /// Mint Lightning Error
336    type Err: Into<Error> + From<Error>;
337
338    /// Start the payment processor
339    /// Called when the mint starts up to initialize the payment processor
340    async fn start(&self) -> Result<(), Self::Err> {
341        // Default implementation - do nothing
342        Ok(())
343    }
344
345    /// Stop the payment processor
346    /// Called when the mint shuts down to gracefully stop the payment processor
347    async fn stop(&self) -> Result<(), Self::Err> {
348        // Default implementation - do nothing
349        Ok(())
350    }
351
352    /// Base Settings
353    async fn get_settings(&self) -> Result<SettingsResponse, Self::Err>;
354
355    /// Create a new invoice
356    async fn create_incoming_payment_request(
357        &self,
358        options: IncomingPaymentOptions,
359    ) -> Result<CreateIncomingPaymentResponse, Self::Err>;
360
361    /// Get payment quote
362    /// Used to get fee and amount required for a payment request
363    async fn get_payment_quote(
364        &self,
365        unit: &CurrencyUnit,
366        options: OutgoingPaymentOptions,
367    ) -> Result<PaymentQuoteResponse, Self::Err>;
368
369    /// Pay request
370    async fn make_payment(
371        &self,
372        unit: &CurrencyUnit,
373        options: OutgoingPaymentOptions,
374    ) -> Result<MakePaymentResponse, Self::Err>;
375
376    /// Listen for invoices to be paid to the mint
377    /// Returns a stream of request_lookup_id once invoices are paid
378    async fn wait_payment_event(
379        &self,
380    ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err>;
381
382    /// Is wait invoice active
383    fn is_wait_invoice_active(&self) -> bool;
384
385    /// Cancel wait invoice
386    fn cancel_wait_invoice(&self);
387
388    /// Check the status of an incoming payment
389    async fn check_incoming_payment_status(
390        &self,
391        payment_identifier: &PaymentIdentifier,
392    ) -> Result<Vec<WaitPaymentResponse>, Self::Err>;
393
394    /// Check the status of an outgoing payment
395    async fn check_outgoing_payment(
396        &self,
397        payment_identifier: &PaymentIdentifier,
398    ) -> Result<MakePaymentResponse, Self::Err>;
399}
400
401/// An event emitted which should be handled by the mint
402#[derive(Debug, Clone, Hash)]
403pub enum Event {
404    /// A payment has been received.
405    PaymentReceived(WaitPaymentResponse),
406}
407
408impl Default for Event {
409    fn default() -> Self {
410        // We use this as a sentinel value for no-op events
411        // The actual processing will filter these out
412        Event::PaymentReceived(WaitPaymentResponse {
413            payment_identifier: PaymentIdentifier::CustomId("default".to_string()),
414            payment_amount: Amount::new(0, CurrencyUnit::Msat),
415            payment_id: "default".to_string(),
416        })
417    }
418}
419
420/// Wait any invoice response
421#[derive(Debug, Clone, Hash)]
422pub struct WaitPaymentResponse {
423    /// Request look up id
424    /// Id that relates the quote and payment request
425    pub payment_identifier: PaymentIdentifier,
426    /// Payment amount (typed with unit for compile-time safety)
427    pub payment_amount: Amount<CurrencyUnit>,
428    /// Unique id of payment
429    // Payment hash
430    pub payment_id: String,
431}
432
433impl WaitPaymentResponse {
434    /// Get the currency unit
435    pub fn unit(&self) -> &CurrencyUnit {
436        self.payment_amount.unit()
437    }
438}
439
440/// Create incoming payment response
441#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
442pub struct CreateIncomingPaymentResponse {
443    /// Id that is used to look up the payment from the ln backend
444    pub request_lookup_id: PaymentIdentifier,
445    /// Payment request
446    pub request: String,
447    /// Unix Expiry of Invoice
448    pub expiry: Option<u64>,
449    /// Extra payment-method-specific fields
450    ///
451    /// These fields are flattened into the JSON representation, allowing
452    /// custom payment methods to include additional data without nesting.
453    #[serde(flatten, default)]
454    pub extra_json: Option<serde_json::Value>,
455}
456
457/// Payment response
458#[derive(Debug, Clone, Hash, PartialEq, Eq)]
459pub struct MakePaymentResponse {
460    /// Payment hash
461    pub payment_lookup_id: PaymentIdentifier,
462    /// Payment proof
463    pub payment_proof: Option<String>,
464    /// Status
465    pub status: MeltQuoteState,
466    /// Total Amount Spent (typed with unit for compile-time safety)
467    pub total_spent: Amount<CurrencyUnit>,
468}
469
470impl MakePaymentResponse {
471    /// Get the currency unit
472    pub fn unit(&self) -> &CurrencyUnit {
473        self.total_spent.unit()
474    }
475}
476
477/// Payment quote response
478#[derive(Debug, Clone, Hash, PartialEq, Eq)]
479pub struct PaymentQuoteResponse {
480    /// Request look up id
481    pub request_lookup_id: Option<PaymentIdentifier>,
482    /// Amount (typed with unit for compile-time safety)
483    pub amount: Amount<CurrencyUnit>,
484    /// Fee required for melt (typed with unit for compile-time safety)
485    pub fee: Amount<CurrencyUnit>,
486    /// Status
487    pub state: MeltQuoteState,
488}
489
490impl PaymentQuoteResponse {
491    /// Get the currency unit
492    pub fn unit(&self) -> &CurrencyUnit {
493        self.amount.unit()
494    }
495}
496
497/// BOLT11 settings
498#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
499pub struct Bolt11Settings {
500    /// Multi-part payment (MPP) supported
501    pub mpp: bool,
502    /// Amountless invoice support
503    pub amountless: bool,
504    /// Invoice description supported
505    pub invoice_description: bool,
506}
507
508/// BOLT12 settings
509#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
510pub struct Bolt12Settings {
511    /// Amountless offer support
512    pub amountless: bool,
513}
514
515/// Payment processor settings response
516/// Mirrors the proto SettingsResponse structure
517#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
518pub struct SettingsResponse {
519    /// Base unit of backend
520    pub unit: String,
521    /// BOLT11 settings (None if not supported)
522    pub bolt11: Option<Bolt11Settings>,
523    /// BOLT12 settings (None if not supported)
524    pub bolt12: Option<Bolt12Settings>,
525    /// Custom payment methods settings (method name -> settings data)
526    #[serde(default)]
527    pub custom: std::collections::HashMap<String, String>,
528}
529
530impl From<SettingsResponse> for Value {
531    fn from(value: SettingsResponse) -> Self {
532        serde_json::to_value(value).unwrap_or(Value::Null)
533    }
534}
535
536impl TryFrom<Value> for SettingsResponse {
537    type Error = crate::error::Error;
538
539    fn try_from(value: Value) -> Result<Self, Self::Error> {
540        serde_json::from_value(value).map_err(|err| err.into())
541    }
542}
543
544/// Metrics wrapper for MintPayment implementations
545///
546/// This wrapper implements the Decorator pattern to collect metrics on all
547/// MintPayment trait methods. It wraps any existing MintPayment implementation
548/// and automatically records timing and operation metrics.
549#[derive(Debug, Clone)]
550#[cfg(feature = "prometheus")]
551pub struct MetricsMintPayment<T> {
552    inner: T,
553}
554#[cfg(feature = "prometheus")]
555impl<T> MetricsMintPayment<T>
556where
557    T: MintPayment,
558{
559    /// Create a new metrics wrapper around a MintPayment implementation
560    pub fn new(inner: T) -> Self {
561        Self { inner }
562    }
563
564    /// Get reference to the underlying implementation
565    pub fn inner(&self) -> &T {
566        &self.inner
567    }
568
569    /// Consume the wrapper and return the inner implementation
570    pub fn into_inner(self) -> T {
571        self.inner
572    }
573}
574
575#[async_trait]
576#[cfg(feature = "prometheus")]
577impl<T> MintPayment for MetricsMintPayment<T>
578where
579    T: MintPayment + Send + Sync,
580{
581    type Err = T::Err;
582
583    async fn get_settings(&self) -> Result<SettingsResponse, Self::Err> {
584        let start = std::time::Instant::now();
585        METRICS.inc_in_flight_requests("get_settings");
586
587        let result = self.inner.get_settings().await;
588
589        let duration = start.elapsed().as_secs_f64();
590        METRICS.record_mint_operation_histogram("get_settings", result.is_ok(), duration);
591        METRICS.dec_in_flight_requests("get_settings");
592
593        result
594    }
595
596    async fn create_incoming_payment_request(
597        &self,
598        options: IncomingPaymentOptions,
599    ) -> Result<CreateIncomingPaymentResponse, Self::Err> {
600        let start = std::time::Instant::now();
601        METRICS.inc_in_flight_requests("create_incoming_payment_request");
602
603        let result = self.inner.create_incoming_payment_request(options).await;
604
605        let duration = start.elapsed().as_secs_f64();
606        METRICS.record_mint_operation_histogram(
607            "create_incoming_payment_request",
608            result.is_ok(),
609            duration,
610        );
611        METRICS.dec_in_flight_requests("create_incoming_payment_request");
612
613        result
614    }
615
616    async fn get_payment_quote(
617        &self,
618        unit: &CurrencyUnit,
619        options: OutgoingPaymentOptions,
620    ) -> Result<PaymentQuoteResponse, Self::Err> {
621        let start = std::time::Instant::now();
622        METRICS.inc_in_flight_requests("get_payment_quote");
623
624        let result = self.inner.get_payment_quote(unit, options).await;
625
626        let duration = start.elapsed().as_secs_f64();
627        let success = result.is_ok();
628
629        if let Ok(ref quote) = result {
630            let amount: f64 = quote.amount.value() as f64;
631            let fee: f64 = quote.fee.value() as f64;
632            METRICS.record_lightning_payment(amount, fee);
633        }
634
635        METRICS.record_mint_operation_histogram("get_payment_quote", success, duration);
636        METRICS.dec_in_flight_requests("get_payment_quote");
637
638        result
639    }
640    async fn wait_payment_event(
641        &self,
642    ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err> {
643        let start = std::time::Instant::now();
644        METRICS.inc_in_flight_requests("wait_payment_event");
645
646        let result = self.inner.wait_payment_event().await;
647
648        let duration = start.elapsed().as_secs_f64();
649        let success = result.is_ok();
650
651        METRICS.record_mint_operation_histogram("wait_payment_event", success, duration);
652        METRICS.dec_in_flight_requests("wait_payment_event");
653
654        result
655    }
656
657    async fn make_payment(
658        &self,
659        unit: &CurrencyUnit,
660        options: OutgoingPaymentOptions,
661    ) -> Result<MakePaymentResponse, Self::Err> {
662        let start = std::time::Instant::now();
663        METRICS.inc_in_flight_requests("make_payment");
664
665        let result = self.inner.make_payment(unit, options).await;
666
667        let duration = start.elapsed().as_secs_f64();
668        let success = result.is_ok();
669
670        METRICS.record_mint_operation_histogram("make_payment", success, duration);
671        METRICS.dec_in_flight_requests("make_payment");
672
673        result
674    }
675
676    fn is_wait_invoice_active(&self) -> bool {
677        self.inner.is_wait_invoice_active()
678    }
679
680    fn cancel_wait_invoice(&self) {
681        self.inner.cancel_wait_invoice()
682    }
683
684    async fn check_incoming_payment_status(
685        &self,
686        payment_identifier: &PaymentIdentifier,
687    ) -> Result<Vec<WaitPaymentResponse>, Self::Err> {
688        let start = std::time::Instant::now();
689        METRICS.inc_in_flight_requests("check_incoming_payment_status");
690
691        let result = self
692            .inner
693            .check_incoming_payment_status(payment_identifier)
694            .await;
695
696        let duration = start.elapsed().as_secs_f64();
697        METRICS.record_mint_operation_histogram(
698            "check_incoming_payment_status",
699            result.is_ok(),
700            duration,
701        );
702        METRICS.dec_in_flight_requests("check_incoming_payment_status");
703
704        result
705    }
706
707    async fn check_outgoing_payment(
708        &self,
709        payment_identifier: &PaymentIdentifier,
710    ) -> Result<MakePaymentResponse, Self::Err> {
711        let start = std::time::Instant::now();
712        METRICS.inc_in_flight_requests("check_outgoing_payment");
713
714        let result = self.inner.check_outgoing_payment(payment_identifier).await;
715
716        let duration = start.elapsed().as_secs_f64();
717        let success = result.is_ok();
718
719        METRICS.record_mint_operation_histogram("check_outgoing_payment", success, duration);
720        METRICS.dec_in_flight_requests("check_outgoing_payment");
721
722        result
723    }
724}
725
726/// Type alias for Mint Payment trait
727pub type DynMintPayment = std::sync::Arc<dyn MintPayment<Err = Error> + Send + Sync>;