1use std::convert::Infallible;
4use std::pin::Pin;
5
6use async_trait::async_trait;
7use cashu::util::hex;
8use cashu::{Bolt11Invoice, MeltOptions};
9use futures::Stream;
10use lightning::offers::offer::Offer;
11use lightning_invoice::ParseOrSemanticError;
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use thiserror::Error;
15
16use crate::mint::MeltPaymentRequest;
17use crate::nuts::{CurrencyUnit, MeltQuoteState};
18use crate::Amount;
19
20#[derive(Debug, Error)]
22pub enum Error {
23 #[error("Invoice already paid")]
25 InvoiceAlreadyPaid,
26 #[error("Invoice pay is pending")]
28 InvoicePaymentPending,
29 #[error("Unsupported unit")]
31 UnsupportedUnit,
32 #[error("Unsupported payment option")]
34 UnsupportedPaymentOption,
35 #[error("Payment state is unknown")]
37 UnknownPaymentState,
38 #[error("Amount is not what is expected")]
40 AmountMismatch,
41 #[error(transparent)]
43 Lightning(Box<dyn std::error::Error + Send + Sync>),
44 #[error(transparent)]
46 Serde(#[from] serde_json::Error),
47 #[error(transparent)]
49 Anyhow(#[from] anyhow::Error),
50 #[error(transparent)]
52 Parse(#[from] ParseOrSemanticError),
53 #[error(transparent)]
55 Amount(#[from] crate::amount::Error),
56 #[error(transparent)]
58 NUT04(#[from] crate::nuts::nut04::Error),
59 #[error(transparent)]
61 NUT05(#[from] crate::nuts::nut05::Error),
62 #[error(transparent)]
64 NUT23(#[from] crate::nuts::nut23::Error),
65 #[error("Hex error")]
67 Hex(#[from] hex::Error),
68 #[error("Invalid hash")]
70 InvalidHash,
71 #[error("`{0}`")]
73 Custom(String),
74}
75
76impl From<Infallible> for Error {
77 fn from(_: Infallible) -> Self {
78 unreachable!("Infallible cannot be constructed")
79 }
80}
81
82#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
84#[serde(tag = "type", content = "value")]
85pub enum PaymentIdentifier {
86 Label(String),
88 OfferId(String),
90 PaymentHash([u8; 32]),
92 Bolt12PaymentHash([u8; 32]),
94 PaymentId([u8; 32]),
96 CustomId(String),
98}
99
100impl PaymentIdentifier {
101 pub fn new(kind: &str, identifier: &str) -> Result<Self, Error> {
103 match kind.to_lowercase().as_str() {
104 "label" => Ok(Self::Label(identifier.to_string())),
105 "offer_id" => Ok(Self::OfferId(identifier.to_string())),
106 "payment_hash" => Ok(Self::PaymentHash(
107 hex::decode(identifier)?
108 .try_into()
109 .map_err(|_| Error::InvalidHash)?,
110 )),
111 "bolt12_payment_hash" => Ok(Self::Bolt12PaymentHash(
112 hex::decode(identifier)?
113 .try_into()
114 .map_err(|_| Error::InvalidHash)?,
115 )),
116 "custom" => Ok(Self::CustomId(identifier.to_string())),
117 "payment_id" => Ok(Self::PaymentId(
118 hex::decode(identifier)?
119 .try_into()
120 .map_err(|_| Error::InvalidHash)?,
121 )),
122 _ => Err(Error::UnsupportedPaymentOption),
123 }
124 }
125
126 pub fn kind(&self) -> String {
128 match self {
129 Self::Label(_) => "label".to_string(),
130 Self::OfferId(_) => "offer_id".to_string(),
131 Self::PaymentHash(_) => "payment_hash".to_string(),
132 Self::Bolt12PaymentHash(_) => "bolt12_payment_hash".to_string(),
133 Self::PaymentId(_) => "payment_id".to_string(),
134 Self::CustomId(_) => "custom".to_string(),
135 }
136 }
137}
138
139impl std::fmt::Display for PaymentIdentifier {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 match self {
142 Self::Label(l) => write!(f, "{l}"),
143 Self::OfferId(o) => write!(f, "{o}"),
144 Self::PaymentHash(h) => write!(f, "{}", hex::encode(h)),
145 Self::Bolt12PaymentHash(h) => write!(f, "{}", hex::encode(h)),
146 Self::PaymentId(h) => write!(f, "{}", hex::encode(h)),
147 Self::CustomId(c) => write!(f, "{c}"),
148 }
149 }
150}
151
152#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
154pub struct Bolt11IncomingPaymentOptions {
155 pub description: Option<String>,
157 pub amount: Amount,
159 pub unix_expiry: Option<u64>,
161}
162
163#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
165pub struct Bolt12IncomingPaymentOptions {
166 pub description: Option<String>,
168 pub amount: Option<Amount>,
170 pub unix_expiry: Option<u64>,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Hash)]
176pub enum IncomingPaymentOptions {
177 Bolt11(Bolt11IncomingPaymentOptions),
179 Bolt12(Box<Bolt12IncomingPaymentOptions>),
181}
182
183#[derive(Debug, Clone, PartialEq, Eq, Hash)]
185pub struct Bolt11OutgoingPaymentOptions {
186 pub bolt11: Bolt11Invoice,
188 pub max_fee_amount: Option<Amount>,
190 pub timeout_secs: Option<u64>,
192 pub melt_options: Option<MeltOptions>,
194}
195
196#[derive(Debug, Clone, PartialEq, Eq, Hash)]
198pub struct Bolt12OutgoingPaymentOptions {
199 pub offer: Offer,
201 pub max_fee_amount: Option<Amount>,
203 pub timeout_secs: Option<u64>,
205 pub melt_options: Option<MeltOptions>,
207}
208
209#[derive(Debug, Clone, PartialEq, Eq, Hash)]
211pub enum OutgoingPaymentOptions {
212 Bolt11(Box<Bolt11OutgoingPaymentOptions>),
214 Bolt12(Box<Bolt12OutgoingPaymentOptions>),
216}
217
218impl TryFrom<crate::mint::MeltQuote> for OutgoingPaymentOptions {
219 type Error = Error;
220
221 fn try_from(melt_quote: crate::mint::MeltQuote) -> Result<Self, Self::Error> {
222 match melt_quote.request {
223 MeltPaymentRequest::Bolt11 { bolt11 } => Ok(OutgoingPaymentOptions::Bolt11(Box::new(
224 Bolt11OutgoingPaymentOptions {
225 max_fee_amount: Some(melt_quote.fee_reserve),
226 timeout_secs: None,
227 bolt11,
228 melt_options: melt_quote.options,
229 },
230 ))),
231 MeltPaymentRequest::Bolt12 { offer } => {
232 let melt_options = match melt_quote.options {
233 None => None,
234 Some(MeltOptions::Mpp { mpp: _ }) => return Err(Error::UnsupportedUnit),
235 Some(options) => Some(options),
236 };
237
238 Ok(OutgoingPaymentOptions::Bolt12(Box::new(
239 Bolt12OutgoingPaymentOptions {
240 max_fee_amount: Some(melt_quote.fee_reserve),
241 timeout_secs: None,
242 offer: *offer,
243 melt_options,
244 },
245 )))
246 }
247 }
248 }
249}
250
251#[async_trait]
253pub trait MintPayment {
254 type Err: Into<Error> + From<Error>;
256
257 async fn start(&self) -> Result<(), Self::Err> {
260 Ok(())
262 }
263
264 async fn stop(&self) -> Result<(), Self::Err> {
267 Ok(())
269 }
270
271 async fn get_settings(&self) -> Result<serde_json::Value, Self::Err>;
273
274 async fn create_incoming_payment_request(
276 &self,
277 unit: &CurrencyUnit,
278 options: IncomingPaymentOptions,
279 ) -> Result<CreateIncomingPaymentResponse, Self::Err>;
280
281 async fn get_payment_quote(
284 &self,
285 unit: &CurrencyUnit,
286 options: OutgoingPaymentOptions,
287 ) -> Result<PaymentQuoteResponse, Self::Err>;
288
289 async fn make_payment(
291 &self,
292 unit: &CurrencyUnit,
293 options: OutgoingPaymentOptions,
294 ) -> Result<MakePaymentResponse, Self::Err>;
295
296 async fn wait_any_incoming_payment(
299 &self,
300 ) -> Result<Pin<Box<dyn Stream<Item = WaitPaymentResponse> + Send>>, Self::Err>;
301
302 fn is_wait_invoice_active(&self) -> bool;
304
305 fn cancel_wait_invoice(&self);
307
308 async fn check_incoming_payment_status(
310 &self,
311 payment_identifier: &PaymentIdentifier,
312 ) -> Result<Vec<WaitPaymentResponse>, Self::Err>;
313
314 async fn check_outgoing_payment(
316 &self,
317 payment_identifier: &PaymentIdentifier,
318 ) -> Result<MakePaymentResponse, Self::Err>;
319}
320
321#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
323pub struct WaitPaymentResponse {
324 pub payment_identifier: PaymentIdentifier,
327 pub payment_amount: Amount,
329 pub unit: CurrencyUnit,
331 pub payment_id: String,
334}
335
336#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
338pub struct CreateIncomingPaymentResponse {
339 pub request_lookup_id: PaymentIdentifier,
341 pub request: String,
343 pub expiry: Option<u64>,
345}
346
347#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
349pub struct MakePaymentResponse {
350 pub payment_lookup_id: PaymentIdentifier,
352 pub payment_proof: Option<String>,
354 pub status: MeltQuoteState,
356 pub total_spent: Amount,
358 pub unit: CurrencyUnit,
360}
361
362#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
364pub struct PaymentQuoteResponse {
365 pub request_lookup_id: Option<PaymentIdentifier>,
367 pub amount: Amount,
369 pub fee: Amount,
371 pub unit: CurrencyUnit,
373 pub state: MeltQuoteState,
375}
376
377#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
379pub struct Bolt11Settings {
380 pub mpp: bool,
382 pub unit: CurrencyUnit,
384 pub invoice_description: bool,
386 pub amountless: bool,
388 pub bolt12: bool,
390}
391
392impl TryFrom<Bolt11Settings> for Value {
393 type Error = crate::error::Error;
394
395 fn try_from(value: Bolt11Settings) -> Result<Self, Self::Error> {
396 serde_json::to_value(value).map_err(|err| err.into())
397 }
398}
399
400impl TryFrom<Value> for Bolt11Settings {
401 type Error = crate::error::Error;
402
403 fn try_from(value: Value) -> Result<Self, Self::Error> {
404 serde_json::from_value(value).map_err(|err| err.into())
405 }
406}