Skip to main content

fedimint_ln_client/recurring/
api.rs

1use fedimint_core::config::FederationId;
2use fedimint_core::util::{FmtCompactErrorAnyhow, SafeUrl};
3use lightning_invoice::Bolt11Invoice;
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6
7use crate::recurring::{PaymentCodeRootKey, RecurringPaymentProtocol};
8
9pub struct RecurringdClient {
10    client: reqwest::Client,
11    base_url: SafeUrl,
12}
13
14impl RecurringdClient {
15    pub fn new(base_url: &SafeUrl) -> Self {
16        Self {
17            client: reqwest::Client::new(),
18            base_url: base_url.join_path("lnv1"),
19        }
20    }
21
22    pub async fn register_recurring_payment_code(
23        &self,
24        federation_id: FederationId,
25        protocol: RecurringPaymentProtocol,
26        payment_code_root_key: PaymentCodeRootKey,
27        meta: &str,
28    ) -> Result<RecurringPaymentRegistrationResponse, RecurringdApiError> {
29        // TODO: validate decoding works like this and maybe figure out a cleaner way to
30        // communicate errors
31        let request = RecurringPaymentRegistrationRequest {
32            federation_id,
33            protocol,
34            payment_code_root_key,
35            meta: meta.to_owned(),
36        };
37
38        let response = self
39            .client
40            .put(self.base_url.join_path("paycodes").to_string())
41            .json(&request)
42            .send()
43            .await
44            .map_err(RecurringdApiError::NetworkError)?;
45
46        response
47            .json::<ApiResult<RecurringPaymentRegistrationResponse>>()
48            .await
49            .map_err(|e| RecurringdApiError::DecodingError(e.into()))?
50            .into_result()
51    }
52
53    pub async fn await_new_invoice(
54        &self,
55        payment_code_root_key: PaymentCodeRootKey,
56        invoice_index: u64,
57    ) -> Result<Bolt11Invoice, RecurringdApiError> {
58        let response = self
59            .client
60            .get(
61                self.base_url
62                    .join_path(&format!(
63                        "paycodes/recipient/{payment_code_root_key}/generated/{invoice_index}"
64                    ))
65                    .to_string(),
66            )
67            .send()
68            .await
69            .map_err(RecurringdApiError::NetworkError)?;
70        response
71            .json::<ApiResult<Bolt11Invoice>>()
72            .await
73            .map_err(|e| RecurringdApiError::DecodingError(e.into()))?
74            .into_result()
75    }
76}
77
78#[derive(Debug, Error)]
79pub enum RecurringdApiError {
80    #[error("Recurring payment server error: {0}")]
81    ApiError(String),
82    #[error("Invalid response: {}", FmtCompactErrorAnyhow(.0))]
83    DecodingError(anyhow::Error),
84    #[error("Network error: {0}")]
85    NetworkError(#[from] reqwest::Error),
86}
87
88#[derive(Debug, Clone, PartialOrd, PartialEq, Hash, Serialize, Deserialize)]
89pub struct RecurringPaymentRegistrationRequest {
90    /// Federation ID in which the invoices should be generated
91    pub federation_id: FederationId,
92    /// Recurring payment protocol to use
93    pub protocol: RecurringPaymentProtocol,
94    /// Public key from which other keys will be derived for each generated
95    /// invoice
96    pub payment_code_root_key: PaymentCodeRootKey,
97    /// LNURL meta data, see LUD-06 for more details on the format
98    pub meta: String,
99}
100
101#[derive(Debug, Clone, PartialOrd, PartialEq, Hash, Serialize, Deserialize)]
102pub struct RecurringPaymentRegistrationResponse {
103    /// Either a BOLT12 offer or LNURL
104    pub recurring_payment_code: String,
105}
106
107#[derive(Debug, Deserialize)]
108#[serde(untagged)]
109enum ApiResult<T> {
110    Ok(T),
111    Err { error: String },
112}
113
114impl<T> ApiResult<T> {
115    pub fn into_result(self) -> Result<T, RecurringdApiError> {
116        match self {
117            ApiResult::Ok(result) => Ok(result),
118            ApiResult::Err { error } => Err(RecurringdApiError::ApiError(error)),
119        }
120    }
121}