1use 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::MintMetricGuard;
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::nut30::MeltQuoteOnchainFeeOption;
20use crate::nuts::{CurrencyUnit, MeltQuoteState};
21use crate::{Amount, QuoteId};
22
23#[derive(Debug, Error)]
25pub enum Error {
26 #[error("Invoice already paid")]
28 InvoiceAlreadyPaid,
29 #[error("Invoice pay is pending")]
31 InvoicePaymentPending,
32 #[error("Unsupported unit")]
34 UnsupportedUnit,
35 #[error("Unsupported payment option")]
37 UnsupportedPaymentOption,
38 #[error("Payment state is unknown")]
40 UnknownPaymentState,
41 #[error("Amount is not what is expected")]
43 AmountMismatch,
44 #[error("Invalid expiry")]
46 InvalidExpiry,
47 #[error(transparent)]
49 Lightning(Box<dyn std::error::Error + Send + Sync>),
50 #[error(transparent)]
52 Onchain(Box<dyn std::error::Error + Send + Sync>),
53 #[error(transparent)]
55 Serde(#[from] serde_json::Error),
56 #[error(transparent)]
58 Anyhow(#[from] anyhow::Error),
59 #[error(transparent)]
61 Parse(#[from] ParseOrSemanticError),
62 #[error(transparent)]
64 Amount(#[from] crate::amount::Error),
65 #[error(transparent)]
67 NUT04(#[from] crate::nuts::nut04::Error),
68 #[error(transparent)]
70 NUT05(#[from] crate::nuts::nut05::Error),
71 #[error(transparent)]
73 NUT23(#[from] crate::nuts::nut23::Error),
74 #[error("Hex error")]
76 Hex(#[from] hex::Error),
77 #[error("Invalid hash")]
79 InvalidHash,
80 #[error("`{0}`")]
82 Custom(String),
83}
84
85impl From<Infallible> for Error {
86 fn from(_: Infallible) -> Self {
87 unreachable!("Infallible cannot be constructed")
88 }
89}
90
91#[derive(Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
93#[serde(tag = "type", content = "value")]
94pub enum PaymentIdentifier {
95 Label(String),
97 OfferId(String),
99 PaymentHash([u8; 32]),
101 Bolt12PaymentHash([u8; 32]),
103 PaymentId([u8; 32]),
105 CustomId(String),
107 QuoteId(QuoteId),
109}
110
111impl PaymentIdentifier {
112 pub fn new(kind: &str, identifier: &str) -> Result<Self, Error> {
114 match kind.to_lowercase().as_str() {
115 "label" => Ok(Self::Label(identifier.to_string())),
116 "offer_id" => Ok(Self::OfferId(identifier.to_string())),
117 "payment_hash" => Ok(Self::PaymentHash(
118 hex::decode(identifier)?
119 .try_into()
120 .map_err(|_| Error::InvalidHash)?,
121 )),
122 "bolt12_payment_hash" => Ok(Self::Bolt12PaymentHash(
123 hex::decode(identifier)?
124 .try_into()
125 .map_err(|_| Error::InvalidHash)?,
126 )),
127 "custom" => Ok(Self::CustomId(identifier.to_string())),
128 "payment_id" => Ok(Self::PaymentId(
129 hex::decode(identifier)?
130 .try_into()
131 .map_err(|_| Error::InvalidHash)?,
132 )),
133 "quote_id" => {
134 Ok(Self::QuoteId(identifier.parse().map_err(|_| {
135 Error::Custom("Invalid QuoteId".to_string())
136 })?))
137 }
138 _ => Err(Error::UnsupportedPaymentOption),
139 }
140 }
141
142 pub fn kind(&self) -> String {
144 match self {
145 Self::Label(_) => "label".to_string(),
146 Self::OfferId(_) => "offer_id".to_string(),
147 Self::PaymentHash(_) => "payment_hash".to_string(),
148 Self::Bolt12PaymentHash(_) => "bolt12_payment_hash".to_string(),
149 Self::PaymentId(_) => "payment_id".to_string(),
150 Self::CustomId(_) => "custom".to_string(),
151 Self::QuoteId(_) => "quote_id".to_string(),
152 }
153 }
154}
155
156impl std::fmt::Display for PaymentIdentifier {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 match self {
159 Self::Label(l) => write!(f, "{l}"),
160 Self::OfferId(o) => write!(f, "{o}"),
161 Self::PaymentHash(h) => write!(f, "{}", hex::encode(h)),
162 Self::Bolt12PaymentHash(h) => write!(f, "{}", hex::encode(h)),
163 Self::PaymentId(h) => write!(f, "{}", hex::encode(h)),
164 Self::CustomId(c) => write!(f, "{c}"),
165 Self::QuoteId(q) => write!(f, "{q}"),
166 }
167 }
168}
169
170impl std::fmt::Debug for PaymentIdentifier {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 match self {
173 PaymentIdentifier::PaymentHash(h) => write!(f, "PaymentHash({})", hex::encode(h)),
174 PaymentIdentifier::Bolt12PaymentHash(h) => {
175 write!(f, "Bolt12PaymentHash({})", hex::encode(h))
176 }
177 PaymentIdentifier::PaymentId(h) => write!(f, "PaymentId({})", hex::encode(h)),
178 PaymentIdentifier::Label(s) => write!(f, "Label({})", s),
179 PaymentIdentifier::OfferId(s) => write!(f, "OfferId({})", s),
180 PaymentIdentifier::CustomId(s) => write!(f, "CustomId({})", s),
181 PaymentIdentifier::QuoteId(q) => write!(f, "QuoteId({})", q),
182 }
183 }
184}
185
186#[derive(Debug, Clone, PartialEq, Eq, Hash)]
188pub struct Bolt11IncomingPaymentOptions {
189 pub description: Option<String>,
191 pub amount: Amount<CurrencyUnit>,
193 pub unix_expiry: Option<u64>,
195}
196
197impl Default for Bolt11IncomingPaymentOptions {
198 fn default() -> Self {
199 Self {
200 description: None,
201 amount: Amount::new(0, CurrencyUnit::Sat),
202 unix_expiry: None,
203 }
204 }
205}
206
207#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
209pub struct Bolt12IncomingPaymentOptions {
210 pub description: Option<String>,
212 pub amount: Option<Amount<CurrencyUnit>>,
214 pub unix_expiry: Option<u64>,
216}
217
218#[derive(Debug, Clone, PartialEq, Eq, Hash)]
220pub struct CustomIncomingPaymentOptions {
221 pub method: String,
223 pub description: Option<String>,
225 pub amount: Amount<CurrencyUnit>,
227 pub unix_expiry: Option<u64>,
229 pub extra_json: Option<String>,
234}
235
236#[derive(Debug, Clone, PartialEq, Eq, Hash)]
238pub struct OnchainIncomingPaymentOptions {
239 pub quote_id: QuoteId,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq, Hash)]
245pub enum IncomingPaymentOptions {
246 Bolt11(Bolt11IncomingPaymentOptions),
248 Bolt12(Box<Bolt12IncomingPaymentOptions>),
250 Custom(Box<CustomIncomingPaymentOptions>),
252 Onchain(OnchainIncomingPaymentOptions),
254}
255
256#[derive(Debug, Clone, PartialEq, Eq, Hash)]
258pub struct Bolt11OutgoingPaymentOptions {
259 pub bolt11: Bolt11Invoice,
261 pub max_fee_amount: Option<Amount<CurrencyUnit>>,
263 pub timeout_secs: Option<u64>,
265 pub melt_options: Option<MeltOptions>,
267 pub quote_id: QuoteId,
272}
273
274#[derive(Debug, Clone, PartialEq, Eq, Hash)]
276pub struct Bolt12OutgoingPaymentOptions {
277 pub offer: Offer,
279 pub max_fee_amount: Option<Amount<CurrencyUnit>>,
281 pub timeout_secs: Option<u64>,
283 pub melt_options: Option<MeltOptions>,
285 pub quote_id: QuoteId,
287}
288
289#[derive(Debug, Clone, PartialEq, Eq, Hash)]
291pub struct CustomOutgoingPaymentOptions {
292 pub method: String,
294 pub request: String,
296 pub max_fee_amount: Option<Amount<CurrencyUnit>>,
298 pub timeout_secs: Option<u64>,
300 pub melt_options: Option<MeltOptions>,
302 pub extra_json: Option<String>,
307 pub quote_id: QuoteId,
313}
314
315#[derive(Debug, Clone, PartialEq, Eq, Hash)]
317pub struct OnchainOutgoingPaymentOptions {
318 pub address: String,
320 pub amount: Amount<CurrencyUnit>,
322 pub max_fee_amount: Option<Amount<CurrencyUnit>>,
324 pub quote_id: QuoteId,
337 pub fee_index: Option<u32>,
339 pub metadata: Option<String>,
341}
342
343#[derive(Debug, Clone, PartialEq, Eq, Hash)]
345pub enum OutgoingPaymentOptions {
346 Bolt11(Box<Bolt11OutgoingPaymentOptions>),
348 Bolt12(Box<Bolt12OutgoingPaymentOptions>),
350 Custom(Box<CustomOutgoingPaymentOptions>),
352 Onchain(Box<OnchainOutgoingPaymentOptions>),
354}
355
356impl OutgoingPaymentOptions {
357 pub fn from_melt_quote_with_fee(
359 melt_quote: MeltQuote,
360 ) -> Result<OutgoingPaymentOptions, Error> {
361 let fee_reserve = melt_quote.fee_reserve();
362 let quote_id = melt_quote.id.clone();
363 match &melt_quote.request {
364 MeltPaymentRequest::Bolt11 { bolt11 } => Ok(OutgoingPaymentOptions::Bolt11(Box::new(
365 Bolt11OutgoingPaymentOptions {
366 max_fee_amount: Some(fee_reserve),
367 timeout_secs: None,
368 bolt11: bolt11.clone(),
369 melt_options: melt_quote.options,
370 quote_id,
371 },
372 ))),
373 MeltPaymentRequest::Bolt12 { offer } => {
374 let melt_options = match melt_quote.options {
375 Some(MeltOptions::Mpp { mpp: _ }) => return Err(Error::UnsupportedUnit),
376 Some(options) => Some(options),
377 _ => None,
378 };
379
380 Ok(OutgoingPaymentOptions::Bolt12(Box::new(
381 Bolt12OutgoingPaymentOptions {
382 max_fee_amount: Some(fee_reserve),
383 timeout_secs: None,
384 offer: *offer.clone(),
385 melt_options,
386 quote_id,
387 },
388 )))
389 }
390 MeltPaymentRequest::Custom { method, request } => Ok(OutgoingPaymentOptions::Custom(
391 Box::new(CustomOutgoingPaymentOptions {
392 method: method.to_string(),
393 request: request.to_string(),
394 max_fee_amount: Some(fee_reserve),
395 timeout_secs: None,
396 melt_options: melt_quote.options,
397 extra_json: melt_quote
398 .extra_json
399 .as_ref()
400 .map(serde_json::Value::to_string),
401 quote_id,
402 }),
403 )),
404 MeltPaymentRequest::Onchain { address } => Ok(OutgoingPaymentOptions::Onchain(
405 Box::new(OnchainOutgoingPaymentOptions {
406 address: address.clone(),
407 amount: melt_quote.amount(),
408 max_fee_amount: Some(fee_reserve),
409 quote_id: melt_quote.id,
410 fee_index: melt_quote.selected_fee_index,
411 metadata: None,
412 }),
413 )),
414 }
415 }
416}
417
418#[async_trait]
420pub trait MintPayment {
421 type Err: Into<Error> + From<Error>;
423
424 async fn start(&self) -> Result<(), Self::Err> {
427 Ok(())
429 }
430
431 async fn stop(&self) -> Result<(), Self::Err> {
434 Ok(())
436 }
437
438 async fn get_settings(&self) -> Result<SettingsResponse, Self::Err>;
440
441 async fn create_incoming_payment_request(
443 &self,
444 options: IncomingPaymentOptions,
445 ) -> Result<CreateIncomingPaymentResponse, Self::Err>;
446
447 async fn get_payment_quote(
450 &self,
451 unit: &CurrencyUnit,
452 options: OutgoingPaymentOptions,
453 ) -> Result<PaymentQuoteResponse, Self::Err>;
454
455 async fn make_payment(
457 &self,
458 unit: &CurrencyUnit,
459 options: OutgoingPaymentOptions,
460 ) -> Result<MakePaymentResponse, Self::Err>;
461
462 async fn wait_payment_event(
465 &self,
466 ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err>;
467
468 fn is_payment_event_stream_active(&self) -> bool;
470
471 fn cancel_payment_event_stream(&self);
473
474 async fn check_incoming_payment_status(
476 &self,
477 payment_identifier: &PaymentIdentifier,
478 ) -> Result<Vec<WaitPaymentResponse>, Self::Err>;
479
480 async fn check_outgoing_payment(
482 &self,
483 payment_identifier: &PaymentIdentifier,
484 ) -> Result<MakePaymentResponse, Self::Err>;
485}
486
487#[derive(Debug, Clone, Hash)]
489pub enum Event {
490 PaymentReceived(WaitPaymentResponse),
492 PaymentSuccessful {
494 quote_id: QuoteId,
496 details: MakePaymentResponse,
498 },
499 PaymentFailed {
501 quote_id: QuoteId,
503 reason: String,
505 },
506}
507
508#[derive(Debug, Clone, Hash)]
510pub struct WaitPaymentResponse {
511 pub payment_identifier: PaymentIdentifier,
514 pub payment_amount: Amount<CurrencyUnit>,
516 pub payment_id: String,
519}
520
521impl WaitPaymentResponse {
522 pub fn unit(&self) -> &CurrencyUnit {
524 self.payment_amount.unit()
525 }
526}
527
528#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
530pub struct CreateIncomingPaymentResponse {
531 pub request_lookup_id: PaymentIdentifier,
533 pub request: String,
535 pub expiry: Option<u64>,
537 #[serde(flatten, default)]
542 pub extra_json: Option<serde_json::Value>,
543}
544
545#[derive(Debug, Clone, Hash, PartialEq, Eq)]
547pub struct MakePaymentResponse {
548 pub payment_lookup_id: PaymentIdentifier,
556 pub payment_proof: Option<String>,
558 pub status: MeltQuoteState,
560 pub total_spent: Amount<CurrencyUnit>,
563}
564
565impl MakePaymentResponse {
566 pub fn unit(&self) -> &CurrencyUnit {
568 self.total_spent.unit()
569 }
570}
571
572#[derive(Debug, Clone, Hash, PartialEq, Eq)]
574pub struct PaymentQuoteResponse {
575 pub request_lookup_id: Option<PaymentIdentifier>,
584 pub amount: Amount<CurrencyUnit>,
586 pub fee: Amount<CurrencyUnit>,
588 pub state: MeltQuoteState,
590 pub extra_json: Option<serde_json::Value>,
592 pub estimated_blocks: Option<u32>,
597 pub fee_options: Option<Vec<MeltQuoteOnchainFeeOption>>,
609}
610
611impl PaymentQuoteResponse {
612 pub fn unit(&self) -> &CurrencyUnit {
614 self.amount.unit()
615 }
616}
617
618#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
620pub struct Bolt11Settings {
621 pub mpp: bool,
623 pub amountless: bool,
625 pub invoice_description: bool,
627}
628
629#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
631pub struct Bolt12Settings {
632 pub amountless: bool,
634}
635
636#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
638pub struct OnchainSettings {
639 pub confirmations: u32,
641 pub min_receive_amount_sat: u64,
643 pub min_send_amount_sat: u64,
645}
646
647#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
650pub struct SettingsResponse {
651 pub unit: String,
653 pub bolt11: Option<Bolt11Settings>,
655 pub bolt12: Option<Bolt12Settings>,
657 pub onchain: Option<OnchainSettings>,
659 #[serde(default)]
661 pub custom: std::collections::HashMap<String, String>,
662}
663
664impl From<SettingsResponse> for Value {
665 fn from(value: SettingsResponse) -> Self {
666 serde_json::to_value(value).unwrap_or(Value::Null)
667 }
668}
669
670impl TryFrom<Value> for SettingsResponse {
671 type Error = crate::error::Error;
672
673 fn try_from(value: Value) -> Result<Self, Self::Error> {
674 serde_json::from_value(value).map_err(|err| err.into())
675 }
676}
677
678#[derive(Debug, Clone)]
684#[cfg(feature = "prometheus")]
685pub struct MetricsMintPayment<T> {
686 inner: T,
687}
688#[cfg(feature = "prometheus")]
689impl<T> MetricsMintPayment<T>
690where
691 T: MintPayment,
692{
693 pub fn new(inner: T) -> Self {
695 Self { inner }
696 }
697
698 pub fn inner(&self) -> &T {
700 &self.inner
701 }
702
703 pub fn into_inner(self) -> T {
705 self.inner
706 }
707}
708
709#[async_trait]
710#[cfg(feature = "prometheus")]
711impl<T> MintPayment for MetricsMintPayment<T>
712where
713 T: MintPayment + Send + Sync,
714{
715 type Err = T::Err;
716
717 async fn start(&self) -> Result<(), Self::Err> {
718 let metrics = MintMetricGuard::new("start");
719
720 let result = self.inner.start().await;
721
722 metrics.record(result.is_ok());
723
724 result
725 }
726
727 async fn stop(&self) -> Result<(), Self::Err> {
728 let metrics = MintMetricGuard::new("stop");
729
730 let result = self.inner.stop().await;
731
732 metrics.record(result.is_ok());
733
734 result
735 }
736 async fn get_settings(&self) -> Result<SettingsResponse, Self::Err> {
737 let metrics = MintMetricGuard::new("get_settings");
738
739 let result = self.inner.get_settings().await;
740
741 metrics.record(result.is_ok());
742
743 result
744 }
745
746 async fn create_incoming_payment_request(
747 &self,
748 options: IncomingPaymentOptions,
749 ) -> Result<CreateIncomingPaymentResponse, Self::Err> {
750 let metrics = MintMetricGuard::new("create_incoming_payment_request");
751
752 let result = self.inner.create_incoming_payment_request(options).await;
753
754 metrics.record(result.is_ok());
755
756 result
757 }
758
759 async fn get_payment_quote(
760 &self,
761 unit: &CurrencyUnit,
762 options: OutgoingPaymentOptions,
763 ) -> Result<PaymentQuoteResponse, Self::Err> {
764 let metrics = MintMetricGuard::new("get_payment_quote");
765
766 let result = self.inner.get_payment_quote(unit, options).await;
767
768 metrics.record(result.is_ok());
769
770 result
771 }
772 async fn wait_payment_event(
773 &self,
774 ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err> {
775 let metrics = MintMetricGuard::new("wait_payment_event");
776
777 let result = self.inner.wait_payment_event().await;
778
779 let success = result.is_ok();
780
781 metrics.record(success);
782
783 result
784 }
785
786 async fn make_payment(
787 &self,
788 unit: &CurrencyUnit,
789 options: OutgoingPaymentOptions,
790 ) -> Result<MakePaymentResponse, Self::Err> {
791 let metrics = MintMetricGuard::new("make_payment");
792
793 let result = self.inner.make_payment(unit, options).await;
794
795 let success = result.is_ok();
796
797 metrics.record(success);
798
799 result
800 }
801
802 fn is_payment_event_stream_active(&self) -> bool {
803 self.inner.is_payment_event_stream_active()
804 }
805
806 fn cancel_payment_event_stream(&self) {
807 self.inner.cancel_payment_event_stream()
808 }
809
810 async fn check_incoming_payment_status(
811 &self,
812 payment_identifier: &PaymentIdentifier,
813 ) -> Result<Vec<WaitPaymentResponse>, Self::Err> {
814 let metrics = MintMetricGuard::new("check_incoming_payment_status");
815
816 let result = self
817 .inner
818 .check_incoming_payment_status(payment_identifier)
819 .await;
820
821 metrics.record(result.is_ok());
822
823 result
824 }
825
826 async fn check_outgoing_payment(
827 &self,
828 payment_identifier: &PaymentIdentifier,
829 ) -> Result<MakePaymentResponse, Self::Err> {
830 let metrics = MintMetricGuard::new("check_outgoing_payment");
831
832 let result = self.inner.check_outgoing_payment(payment_identifier).await;
833
834 let success = result.is_ok();
835
836 metrics.record(success);
837
838 result
839 }
840}
841
842pub type DynMintPayment = std::sync::Arc<dyn MintPayment<Err = Error> + Send + Sync>;
844
845#[cfg(test)]
846mod tests {
847 use std::str::FromStr;
848
849 use super::*;
850 use crate::QuoteId;
851
852 #[test]
853 fn test_payment_identifier_quote_id_roundtrip() {
854 let quote_id = QuoteId::new();
855 let identifier = PaymentIdentifier::QuoteId(quote_id.clone());
856
857 let kind = identifier.kind();
858 assert_eq!(kind, "quote_id");
859
860 let display = identifier.to_string();
861 assert_eq!(display, quote_id.to_string());
862
863 let debug = format!("{:?}", identifier);
864 assert_eq!(debug, format!("QuoteId({})", quote_id));
865
866 let parsed = PaymentIdentifier::new(&kind, &display).unwrap();
867 assert_eq!(parsed, identifier);
868 }
869
870 #[test]
871 fn test_payment_identifier_quote_id_base64_roundtrip() {
872 let quote_id_str = "SGVsbG8gV29ybGQh"; let identifier = PaymentIdentifier::QuoteId(QuoteId::from_str(quote_id_str).unwrap());
874
875 let kind = identifier.kind();
876 assert_eq!(kind, "quote_id");
877
878 let display = identifier.to_string();
879 assert_eq!(display, quote_id_str);
880
881 let parsed = PaymentIdentifier::new(&kind, &display).unwrap();
882 assert_eq!(parsed, identifier);
883 }
884
885 #[test]
886 fn test_payment_identifier_unsupported_kind() {
887 let result = PaymentIdentifier::new("unsupported_kind", "123");
888 assert!(matches!(result, Err(Error::UnsupportedPaymentOption)));
889 }
890
891 #[test]
892 fn test_payment_identifier_invalid_quote_id() {
893 let result = PaymentIdentifier::new("quote_id", "invalid!@#quote");
895 assert!(matches!(result, Err(Error::Custom(_))));
896 }
897
898 #[test]
899 fn test_payment_identifier_invalid_hash() {
900 let result_hex = PaymentIdentifier::new("payment_hash", "not_hex!");
902 assert!(matches!(result_hex, Err(Error::Hex(_))));
903
904 let result_len = PaymentIdentifier::new("payment_hash", "00");
906 assert!(matches!(result_len, Err(Error::InvalidHash)));
907
908 let result_bolt12 = PaymentIdentifier::new("bolt12_payment_hash", "00");
910 assert!(matches!(result_bolt12, Err(Error::InvalidHash)));
911 }
912}
913
914#[test]
915fn test_payment_identifier_hash_variants_roundtrip() {
916 let dummy_hash = [1u8; 32];
917 let hex_encoded = hex::encode(dummy_hash);
918
919 let bolt12_identifier = PaymentIdentifier::Bolt12PaymentHash(dummy_hash);
921
922 let kind = bolt12_identifier.kind();
923 assert_eq!(kind, "bolt12_payment_hash");
924
925 let display = bolt12_identifier.to_string();
926 assert_eq!(display, hex_encoded);
927
928 let debug = format!("{:?}", bolt12_identifier);
929 assert_eq!(debug, format!("Bolt12PaymentHash({})", hex_encoded));
930
931 let parsed = PaymentIdentifier::new(&kind, &display).unwrap();
932 assert_eq!(parsed, bolt12_identifier);
933
934 let dummy_hash_2 = [2u8; 32];
936 let hex_encoded_2 = hex::encode(dummy_hash_2);
937 let payment_id_identifier = PaymentIdentifier::PaymentId(dummy_hash_2);
938
939 let kind = payment_id_identifier.kind();
940 assert_eq!(kind, "payment_id");
941
942 let display = payment_id_identifier.to_string();
943 assert_eq!(display, hex_encoded_2);
944
945 let debug = format!("{:?}", payment_id_identifier);
946 assert_eq!(debug, format!("PaymentId({})", hex_encoded_2));
947
948 let parsed = PaymentIdentifier::new(&kind, &display).unwrap();
949 assert_eq!(parsed, payment_id_identifier);
950}