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, PublicKey};
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 Backend(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: Option<Amount<CurrencyUnit>>,
227 pub unix_expiry: Option<u64>,
229 pub extra_json: Option<String>,
234 pub quote_id: QuoteId,
241 pub pubkey: Option<PublicKey>,
248}
249
250#[derive(Debug, Clone, PartialEq, Eq, Hash)]
252pub struct OnchainIncomingPaymentOptions {
253 pub quote_id: QuoteId,
255}
256
257#[derive(Debug, Clone, PartialEq, Eq, Hash)]
259pub enum IncomingPaymentOptions {
260 Bolt11(Bolt11IncomingPaymentOptions),
262 Bolt12(Box<Bolt12IncomingPaymentOptions>),
264 Custom(Box<CustomIncomingPaymentOptions>),
266 Onchain(OnchainIncomingPaymentOptions),
268}
269
270#[derive(Debug, Clone, PartialEq, Eq, Hash)]
272pub struct Bolt11OutgoingPaymentOptions {
273 pub bolt11: Bolt11Invoice,
275 pub max_fee_amount: Option<Amount<CurrencyUnit>>,
277 pub timeout_secs: Option<u64>,
279 pub melt_options: Option<MeltOptions>,
281 pub quote_id: QuoteId,
286}
287
288#[derive(Debug, Clone, PartialEq, Eq, Hash)]
290pub struct Bolt12OutgoingPaymentOptions {
291 pub offer: Offer,
293 pub max_fee_amount: Option<Amount<CurrencyUnit>>,
295 pub timeout_secs: Option<u64>,
297 pub melt_options: Option<MeltOptions>,
299 pub quote_id: QuoteId,
301}
302
303#[derive(Debug, Clone, PartialEq, Eq, Hash)]
305pub struct CustomOutgoingPaymentOptions {
306 pub method: String,
308 pub request: String,
310 pub amount: Option<Amount<CurrencyUnit>>,
312 pub max_fee_amount: Option<Amount<CurrencyUnit>>,
314 pub timeout_secs: Option<u64>,
316 pub melt_options: Option<MeltOptions>,
318 pub extra_json: Option<String>,
323 pub quote_id: QuoteId,
329}
330
331#[derive(Debug, Clone, PartialEq, Eq, Hash)]
333pub struct OnchainOutgoingPaymentOptions {
334 pub address: String,
336 pub amount: Amount<CurrencyUnit>,
338 pub max_fee_amount: Option<Amount<CurrencyUnit>>,
340 pub quote_id: QuoteId,
353 pub fee_index: Option<u32>,
355 pub metadata: Option<String>,
357}
358
359#[derive(Debug, Clone, PartialEq, Eq, Hash)]
361pub enum OutgoingPaymentOptions {
362 Bolt11(Box<Bolt11OutgoingPaymentOptions>),
364 Bolt12(Box<Bolt12OutgoingPaymentOptions>),
366 Custom(Box<CustomOutgoingPaymentOptions>),
368 Onchain(Box<OnchainOutgoingPaymentOptions>),
370}
371
372impl OutgoingPaymentOptions {
373 pub fn from_melt_quote_with_fee(
375 melt_quote: MeltQuote,
376 ) -> Result<OutgoingPaymentOptions, Error> {
377 let fee_reserve = melt_quote.fee_reserve();
378 let quote_id = melt_quote.id.clone();
379 match &melt_quote.request {
380 MeltPaymentRequest::Bolt11 { bolt11 } => Ok(OutgoingPaymentOptions::Bolt11(Box::new(
381 Bolt11OutgoingPaymentOptions {
382 max_fee_amount: Some(fee_reserve),
383 timeout_secs: None,
384 bolt11: bolt11.clone(),
385 melt_options: melt_quote.options,
386 quote_id,
387 },
388 ))),
389 MeltPaymentRequest::Bolt12 { offer } => {
390 let melt_options = match melt_quote.options {
391 Some(MeltOptions::Mpp { mpp: _ }) => return Err(Error::UnsupportedUnit),
392 Some(options) => Some(options),
393 _ => None,
394 };
395
396 Ok(OutgoingPaymentOptions::Bolt12(Box::new(
397 Bolt12OutgoingPaymentOptions {
398 max_fee_amount: Some(fee_reserve),
399 timeout_secs: None,
400 offer: *offer.clone(),
401 melt_options,
402 quote_id,
403 },
404 )))
405 }
406 MeltPaymentRequest::Custom { method, request } => Ok(OutgoingPaymentOptions::Custom(
407 Box::new(CustomOutgoingPaymentOptions {
408 method: method.to_string(),
409 request: request.to_string(),
410 amount: None,
412 max_fee_amount: Some(fee_reserve),
413 timeout_secs: None,
414 melt_options: melt_quote.options,
415 extra_json: melt_quote
416 .extra_json
417 .as_ref()
418 .map(serde_json::Value::to_string),
419 quote_id,
420 }),
421 )),
422 MeltPaymentRequest::Onchain { address } => Ok(OutgoingPaymentOptions::Onchain(
423 Box::new(OnchainOutgoingPaymentOptions {
424 address: address.clone(),
425 amount: melt_quote.amount(),
426 max_fee_amount: Some(fee_reserve),
427 quote_id: melt_quote.id,
428 fee_index: melt_quote.selected_fee_index,
429 metadata: None,
430 }),
431 )),
432 }
433 }
434}
435
436#[async_trait]
438pub trait MintPayment {
439 type Err: Into<Error> + From<Error>;
441
442 async fn start(&self) -> Result<(), Self::Err> {
445 Ok(())
447 }
448
449 async fn stop(&self) -> Result<(), Self::Err> {
452 Ok(())
454 }
455
456 async fn get_settings(&self) -> Result<SettingsResponse, Self::Err>;
458
459 async fn create_incoming_payment_request(
461 &self,
462 options: IncomingPaymentOptions,
463 ) -> Result<CreateIncomingPaymentResponse, Self::Err>;
464
465 async fn get_payment_quote(
468 &self,
469 unit: &CurrencyUnit,
470 options: OutgoingPaymentOptions,
471 ) -> Result<PaymentQuoteResponse, Self::Err>;
472
473 async fn make_payment(
475 &self,
476 unit: &CurrencyUnit,
477 options: OutgoingPaymentOptions,
478 ) -> Result<MakePaymentResponse, Self::Err>;
479
480 async fn wait_payment_event(
483 &self,
484 ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err>;
485
486 fn is_payment_event_stream_active(&self) -> bool;
488
489 fn cancel_payment_event_stream(&self);
491
492 async fn check_incoming_payment_status(
494 &self,
495 payment_identifier: &PaymentIdentifier,
496 ) -> Result<Vec<WaitPaymentResponse>, Self::Err>;
497
498 async fn check_outgoing_payment(
500 &self,
501 payment_identifier: &PaymentIdentifier,
502 ) -> Result<MakePaymentResponse, Self::Err>;
503}
504
505#[derive(Debug, Clone, Hash)]
507pub enum Event {
508 PaymentReceived(WaitPaymentResponse),
510 PaymentSuccessful {
512 quote_id: QuoteId,
514 details: MakePaymentResponse,
516 },
517 PaymentFailed {
519 quote_id: QuoteId,
521 reason: String,
523 },
524}
525
526#[derive(Debug, Clone, Hash)]
528pub struct WaitPaymentResponse {
529 pub payment_identifier: PaymentIdentifier,
532 pub payment_amount: Amount<CurrencyUnit>,
534 pub payment_id: String,
537}
538
539impl WaitPaymentResponse {
540 pub fn unit(&self) -> &CurrencyUnit {
542 self.payment_amount.unit()
543 }
544}
545
546#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
548pub struct CreateIncomingPaymentResponse {
549 pub request_lookup_id: PaymentIdentifier,
551 pub request: String,
553 pub expiry: Option<u64>,
555 #[serde(flatten, default)]
560 pub extra_json: Option<serde_json::Value>,
561}
562
563#[derive(Debug, Clone, Hash, PartialEq, Eq)]
565pub struct MakePaymentResponse {
566 pub payment_lookup_id: PaymentIdentifier,
574 pub payment_proof: Option<String>,
576 pub status: MeltQuoteState,
578 pub total_spent: Amount<CurrencyUnit>,
581}
582
583impl MakePaymentResponse {
584 pub fn unit(&self) -> &CurrencyUnit {
586 self.total_spent.unit()
587 }
588}
589
590#[derive(Debug, Clone, Hash, PartialEq, Eq)]
592pub struct PaymentQuoteResponse {
593 pub request_lookup_id: Option<PaymentIdentifier>,
602 pub amount: Amount<CurrencyUnit>,
604 pub fee: Amount<CurrencyUnit>,
606 pub state: MeltQuoteState,
608 pub extra_json: Option<serde_json::Value>,
610 pub estimated_blocks: Option<u32>,
615 pub fee_options: Option<Vec<MeltQuoteOnchainFeeOption>>,
627}
628
629impl PaymentQuoteResponse {
630 pub fn unit(&self) -> &CurrencyUnit {
632 self.amount.unit()
633 }
634}
635
636#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
638pub struct Bolt11Settings {
639 pub mpp: bool,
641 pub amountless: bool,
643 pub invoice_description: bool,
645}
646
647#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
649pub struct Bolt12Settings {
650 pub amountless: bool,
652}
653
654#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
656pub struct OnchainSettings {
657 pub confirmations: u32,
659 pub min_receive_amount_sat: u64,
661 pub min_send_amount_sat: u64,
663}
664
665#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
668pub struct SettingsResponse {
669 pub unit: String,
671 pub bolt11: Option<Bolt11Settings>,
673 pub bolt12: Option<Bolt12Settings>,
675 pub onchain: Option<OnchainSettings>,
677 #[serde(default)]
679 pub custom: std::collections::HashMap<String, String>,
680}
681
682impl From<SettingsResponse> for Value {
683 fn from(value: SettingsResponse) -> Self {
684 serde_json::to_value(value).unwrap_or(Value::Null)
685 }
686}
687
688impl TryFrom<Value> for SettingsResponse {
689 type Error = crate::error::Error;
690
691 fn try_from(value: Value) -> Result<Self, Self::Error> {
692 serde_json::from_value(value).map_err(|err| err.into())
693 }
694}
695
696#[derive(Debug, Clone)]
702#[cfg(feature = "prometheus")]
703pub struct MetricsMintPayment<T> {
704 inner: T,
705}
706#[cfg(feature = "prometheus")]
707impl<T> MetricsMintPayment<T>
708where
709 T: MintPayment,
710{
711 pub fn new(inner: T) -> Self {
713 Self { inner }
714 }
715
716 pub fn inner(&self) -> &T {
718 &self.inner
719 }
720
721 pub fn into_inner(self) -> T {
723 self.inner
724 }
725}
726
727#[async_trait]
728#[cfg(feature = "prometheus")]
729impl<T> MintPayment for MetricsMintPayment<T>
730where
731 T: MintPayment + Send + Sync,
732{
733 type Err = T::Err;
734
735 async fn start(&self) -> Result<(), Self::Err> {
736 let metrics = MintMetricGuard::new("start");
737
738 let result = self.inner.start().await;
739
740 metrics.record(result.is_ok());
741
742 result
743 }
744
745 async fn stop(&self) -> Result<(), Self::Err> {
746 let metrics = MintMetricGuard::new("stop");
747
748 let result = self.inner.stop().await;
749
750 metrics.record(result.is_ok());
751
752 result
753 }
754 async fn get_settings(&self) -> Result<SettingsResponse, Self::Err> {
755 let metrics = MintMetricGuard::new("get_settings");
756
757 let result = self.inner.get_settings().await;
758
759 metrics.record(result.is_ok());
760
761 result
762 }
763
764 async fn create_incoming_payment_request(
765 &self,
766 options: IncomingPaymentOptions,
767 ) -> Result<CreateIncomingPaymentResponse, Self::Err> {
768 let metrics = MintMetricGuard::new("create_incoming_payment_request");
769
770 let result = self.inner.create_incoming_payment_request(options).await;
771
772 metrics.record(result.is_ok());
773
774 result
775 }
776
777 async fn get_payment_quote(
778 &self,
779 unit: &CurrencyUnit,
780 options: OutgoingPaymentOptions,
781 ) -> Result<PaymentQuoteResponse, Self::Err> {
782 let metrics = MintMetricGuard::new("get_payment_quote");
783
784 let result = self.inner.get_payment_quote(unit, options).await;
785
786 metrics.record(result.is_ok());
787
788 result
789 }
790 async fn wait_payment_event(
791 &self,
792 ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err> {
793 let metrics = MintMetricGuard::new("wait_payment_event");
794
795 let result = self.inner.wait_payment_event().await;
796
797 let success = result.is_ok();
798
799 metrics.record(success);
800
801 result
802 }
803
804 async fn make_payment(
805 &self,
806 unit: &CurrencyUnit,
807 options: OutgoingPaymentOptions,
808 ) -> Result<MakePaymentResponse, Self::Err> {
809 let metrics = MintMetricGuard::new("make_payment");
810
811 let result = self.inner.make_payment(unit, options).await;
812
813 let success = result.is_ok();
814
815 metrics.record(success);
816
817 result
818 }
819
820 fn is_payment_event_stream_active(&self) -> bool {
821 self.inner.is_payment_event_stream_active()
822 }
823
824 fn cancel_payment_event_stream(&self) {
825 self.inner.cancel_payment_event_stream()
826 }
827
828 async fn check_incoming_payment_status(
829 &self,
830 payment_identifier: &PaymentIdentifier,
831 ) -> Result<Vec<WaitPaymentResponse>, Self::Err> {
832 let metrics = MintMetricGuard::new("check_incoming_payment_status");
833
834 let result = self
835 .inner
836 .check_incoming_payment_status(payment_identifier)
837 .await;
838
839 metrics.record(result.is_ok());
840
841 result
842 }
843
844 async fn check_outgoing_payment(
845 &self,
846 payment_identifier: &PaymentIdentifier,
847 ) -> Result<MakePaymentResponse, Self::Err> {
848 let metrics = MintMetricGuard::new("check_outgoing_payment");
849
850 let result = self.inner.check_outgoing_payment(payment_identifier).await;
851
852 let success = result.is_ok();
853
854 metrics.record(success);
855
856 result
857 }
858}
859
860pub type DynMintPayment = std::sync::Arc<dyn MintPayment<Err = Error> + Send + Sync>;
862
863#[cfg(test)]
864mod tests {
865 use std::str::FromStr;
866
867 use super::*;
868 use crate::QuoteId;
869
870 #[test]
871 fn test_payment_identifier_quote_id_roundtrip() {
872 let quote_id = QuoteId::new();
873 let identifier = PaymentIdentifier::QuoteId(quote_id.clone());
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.to_string());
880
881 let debug = format!("{:?}", identifier);
882 assert_eq!(debug, format!("QuoteId({})", quote_id));
883
884 let parsed = PaymentIdentifier::new(&kind, &display).unwrap();
885 assert_eq!(parsed, identifier);
886 }
887
888 #[test]
889 fn test_payment_identifier_quote_id_base64_roundtrip() {
890 let quote_id_str = "SGVsbG8gV29ybGQh"; let identifier = PaymentIdentifier::QuoteId(QuoteId::from_str(quote_id_str).unwrap());
892
893 let kind = identifier.kind();
894 assert_eq!(kind, "quote_id");
895
896 let display = identifier.to_string();
897 assert_eq!(display, quote_id_str);
898
899 let parsed = PaymentIdentifier::new(&kind, &display).unwrap();
900 assert_eq!(parsed, identifier);
901 }
902
903 #[test]
904 fn test_payment_identifier_unsupported_kind() {
905 let result = PaymentIdentifier::new("unsupported_kind", "123");
906 assert!(matches!(result, Err(Error::UnsupportedPaymentOption)));
907 }
908
909 #[test]
910 fn test_payment_identifier_invalid_quote_id() {
911 let result = PaymentIdentifier::new("quote_id", "invalid!@#quote");
913 assert!(matches!(result, Err(Error::Custom(_))));
914 }
915
916 #[test]
917 fn test_payment_identifier_invalid_hash() {
918 let result_hex = PaymentIdentifier::new("payment_hash", "not_hex!");
920 assert!(matches!(result_hex, Err(Error::Hex(_))));
921
922 let result_len = PaymentIdentifier::new("payment_hash", "00");
924 assert!(matches!(result_len, Err(Error::InvalidHash)));
925
926 let result_bolt12 = PaymentIdentifier::new("bolt12_payment_hash", "00");
928 assert!(matches!(result_bolt12, Err(Error::InvalidHash)));
929 }
930}
931
932#[test]
933fn test_payment_identifier_hash_variants_roundtrip() {
934 let dummy_hash = [1u8; 32];
935 let hex_encoded = hex::encode(dummy_hash);
936
937 let bolt12_identifier = PaymentIdentifier::Bolt12PaymentHash(dummy_hash);
939
940 let kind = bolt12_identifier.kind();
941 assert_eq!(kind, "bolt12_payment_hash");
942
943 let display = bolt12_identifier.to_string();
944 assert_eq!(display, hex_encoded);
945
946 let debug = format!("{:?}", bolt12_identifier);
947 assert_eq!(debug, format!("Bolt12PaymentHash({})", hex_encoded));
948
949 let parsed = PaymentIdentifier::new(&kind, &display).unwrap();
950 assert_eq!(parsed, bolt12_identifier);
951
952 let dummy_hash_2 = [2u8; 32];
954 let hex_encoded_2 = hex::encode(dummy_hash_2);
955 let payment_id_identifier = PaymentIdentifier::PaymentId(dummy_hash_2);
956
957 let kind = payment_id_identifier.kind();
958 assert_eq!(kind, "payment_id");
959
960 let display = payment_id_identifier.to_string();
961 assert_eq!(display, hex_encoded_2);
962
963 let debug = format!("{:?}", payment_id_identifier);
964 assert_eq!(debug, format!("PaymentId({})", hex_encoded_2));
965
966 let parsed = PaymentIdentifier::new(&kind, &display).unwrap();
967 assert_eq!(parsed, payment_id_identifier);
968}