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: SafeUrl::parse(&format!("{base_url}lnv1/"))
19 .expect("failed to parse extended base url"),
20 }
21 }
22
23 pub async fn register_recurring_payment_code(
24 &self,
25 federation_id: FederationId,
26 protocol: RecurringPaymentProtocol,
27 payment_code_root_key: PaymentCodeRootKey,
28 meta: &str,
29 ) -> Result<RecurringPaymentRegistrationResponse, RecurringdApiError> {
30 let request = RecurringPaymentRegistrationRequest {
33 federation_id,
34 protocol,
35 payment_code_root_key,
36 meta: meta.to_owned(),
37 };
38
39 let response = self
40 .client
41 .put(format!("{}paycodes", self.base_url))
42 .json(&request)
43 .send()
44 .await
45 .map_err(RecurringdApiError::NetworkError)?;
46
47 response
48 .json::<ApiResult<RecurringPaymentRegistrationResponse>>()
49 .await
50 .map_err(|e| RecurringdApiError::DecodingError(e.into()))?
51 .into_result()
52 }
53
54 pub async fn await_new_invoice(
55 &self,
56 payment_code_root_key: PaymentCodeRootKey,
57 invoice_index: u64,
58 ) -> Result<Bolt11Invoice, RecurringdApiError> {
59 let response = self
60 .client
61 .get(format!(
62 "{}paycodes/recipient/{}/generated/{}",
63 self.base_url, payment_code_root_key, invoice_index
64 ))
65 .send()
66 .await
67 .map_err(RecurringdApiError::NetworkError)?;
68 response
69 .json::<ApiResult<Bolt11Invoice>>()
70 .await
71 .map_err(|e| RecurringdApiError::DecodingError(e.into()))?
72 .into_result()
73 }
74}
75
76#[derive(Debug, Error)]
77pub enum RecurringdApiError {
78 #[error("Recurring payment server error: {0}")]
79 ApiError(String),
80 #[error("Invalid response: {}", FmtCompactErrorAnyhow(.0))]
81 DecodingError(anyhow::Error),
82 #[error("Network error: {0}")]
83 NetworkError(#[from] reqwest::Error),
84}
85
86#[derive(Debug, Clone, PartialOrd, PartialEq, Hash, Serialize, Deserialize)]
87pub struct RecurringPaymentRegistrationRequest {
88 pub federation_id: FederationId,
90 pub protocol: RecurringPaymentProtocol,
92 pub payment_code_root_key: PaymentCodeRootKey,
95 pub meta: String,
97}
98
99#[derive(Debug, Clone, PartialOrd, PartialEq, Hash, Serialize, Deserialize)]
100pub struct RecurringPaymentRegistrationResponse {
101 pub recurring_payment_code: String,
103}
104
105#[derive(Debug, Deserialize)]
106#[serde(untagged)]
107enum ApiResult<T> {
108 Ok(T),
109 Err { error: String },
110}
111
112impl<T> ApiResult<T> {
113 pub fn into_result(self) -> Result<T, RecurringdApiError> {
114 match self {
115 ApiResult::Ok(result) => Ok(result),
116 ApiResult::Err { error } => Err(RecurringdApiError::ApiError(error)),
117 }
118 }
119}