strike_rs/
lib.rs

1//! Strike API SDK
2//! Rust SDK for <https://strike.me/>
3#![doc = include_str!("../README.md")]
4#![warn(missing_docs)]
5#![warn(rustdoc::bare_urls)]
6
7use std::fmt;
8use std::str::FromStr;
9
10use anyhow::{anyhow, bail};
11use rand::distributions::Alphanumeric;
12use rand::Rng;
13use reqwest::{Client, IntoUrl, Url};
14use serde::{Deserialize, Deserializer, Serialize};
15use serde_json::Value;
16
17mod error;
18pub(crate) mod hex;
19pub mod invoice;
20pub mod pay_ln;
21pub mod webhooks;
22
23pub use error::Error;
24pub use invoice::*;
25pub use pay_ln::*;
26
27/// Strike
28#[derive(Debug, Clone)]
29pub struct Strike {
30    api_key: String,
31    base_url: Url,
32    client: Client,
33    webhook_secret: String,
34}
35
36/// Currency unit
37#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
38#[serde(rename_all = "UPPERCASE")]
39pub enum Currency {
40    /// USD
41    USD,
42    /// EURO
43    EUR,
44    /// Bitcoin
45    BTC,
46}
47
48impl fmt::Display for Currency {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        match self {
51            Self::USD => write!(f, "USD"),
52            Self::EUR => write!(f, "EUR"),
53            Self::BTC => write!(f, "BTC"),
54        }
55    }
56}
57
58/// Amount with unit
59#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
60pub struct Amount {
61    /// Currency of amount
62    pub currency: Currency,
63    /// Value of amount
64    #[serde(deserialize_with = "parse_f64_from_string")]
65    pub amount: f64,
66}
67
68fn parse_f64_from_string<'de, D>(deserializer: D) -> Result<f64, D::Error>
69where
70    D: Deserializer<'de>,
71{
72    let s: String = Deserialize::deserialize(deserializer)?;
73    s.parse::<f64>().map_err(serde::de::Error::custom)
74}
75
76impl Amount {
77    /// Amount from sats
78    pub fn from_sats(amount: u64) -> Self {
79        Self {
80            currency: Currency::BTC,
81            amount: amount as f64 / 100_000_000.0,
82        }
83    }
84
85    /// Unit as sats
86    pub fn to_sats(&self) -> anyhow::Result<u64> {
87        match self.currency {
88            Currency::BTC => Ok((self.amount * 100_000_000.0) as u64),
89            _ => bail!("Unit cannot be converted to sats"),
90        }
91    }
92}
93
94/// Invoice state
95#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
96#[serde(rename_all = "UPPERCASE")]
97pub enum InvoiceState {
98    /// Payment Completed
99    Completed,
100    /// Invoice paid
101    Paid,
102    /// Invoice unpaid
103    Unpaid,
104    /// Invoice pending
105    Pending,
106}
107
108/// Conversion rate for quote
109#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
110pub struct ConversionRate {
111    /// Amount
112    #[serde(deserialize_with = "parse_f64_from_string")]
113    pub amount: f64,
114    /// Source Unit
115    #[serde(rename = "sourceCurrency")]
116    pub source_currency: Currency,
117    /// Target Unit
118    #[serde(rename = "targetCurrency")]
119    pub target_currency: Currency,
120}
121
122impl Strike {
123    /// Create Strike client
124    /// # Arguments
125    /// * `api_key` - Strike api token
126    /// * `url` - Optional Url of nodeless api
127    ///
128    /// # Example
129    /// ```
130    /// use strike_rs::Strike;
131    /// let client = Strike::new("xxxxxxxxxxx", None).unwrap();
132    /// ```
133    pub fn new(api_key: &str, api_url: Option<String>) -> anyhow::Result<Self> {
134        let base_url = match api_url {
135            Some(url) => Url::from_str(&url)?,
136            None => Url::from_str("https://api.strike.me")?,
137        };
138
139        let client = reqwest::Client::builder().build()?;
140        let secret: String = rand::thread_rng()
141            .sample_iter(&Alphanumeric)
142            .take(15)
143            .map(char::from)
144            .collect();
145
146        Ok(Self {
147            api_key: api_key.to_string(),
148            base_url,
149            client,
150            webhook_secret: secret,
151        })
152    }
153
154    async fn make_get<U>(&self, url: U) -> Result<Value, Error>
155    where
156        U: IntoUrl,
157    {
158        let res = self
159            .client
160            .get(url)
161            .header("Authorization", format!("Bearer {}", self.api_key))
162            .header("Content-Type", "application/json")
163            .header("accept", "application/json")
164            .send()
165            .await;
166
167        match res {
168            Ok(res) => Ok(res.json::<Value>().await.unwrap_or_default()),
169            Err(err) => Err(err.into()),
170        }
171    }
172
173    async fn make_post<U, T>(&self, url: U, data: Option<T>) -> anyhow::Result<Value>
174    where
175        U: IntoUrl,
176        T: Serialize,
177    {
178        let value = match data {
179            Some(data) => {
180                self.client
181                    .post(url)
182                    .header("Authorization", format!("Bearer {}", self.api_key))
183                    .header("Content-Type", "application/json")
184                    .header("accept", "application/json")
185                    .json(&data)
186                    .send()
187                    .await?
188                    .json::<Value>()
189                    .await?
190            }
191            None => {
192                self.client
193                    .post(url)
194                    .header("Authorization", format!("Bearer {}", self.api_key))
195                    .header("Content-Length", "0")
196                    .header("accept", "application/json")
197                    .send()
198                    .await?
199                    .json::<Value>()
200                    .await?
201            }
202        };
203        Ok(value)
204    }
205
206    async fn make_patch<U>(&self, url: U) -> anyhow::Result<Value>
207    where
208        U: IntoUrl,
209    {
210        Ok(self
211            .client
212            .patch(url)
213            .header("Authorization", format!("Bearer {}", self.api_key))
214            .header("Content-Length", "0")
215            .header("accept", "application/json")
216            .send()
217            .await?
218            .json::<Value>()
219            .await?)
220    }
221
222    async fn make_delete<U>(&self, url: U) -> anyhow::Result<()>
223    where
224        U: IntoUrl,
225    {
226        self.client
227            .delete(url)
228            .header("Authorization", format!("Bearer {}", self.api_key))
229            .send()
230            .await
231            .map_err(|err| anyhow!("Error making delete: {}", err.to_string()))?;
232
233        Ok(())
234    }
235
236    /*
237    async fn make_put(&self, url: Url, data: Option<Value>) -> Result<Value> {
238        let res = self
239            .client
240            .put(url)
241            .header("Authorization", format!("Bearer {}", self.api_key))
242            .header("Content-Type", "application/json")
243            .header("accept", "application/json")
244            .json(&data)
245            .send()
246            .await?;
247        let res = res.json::<Value>().await?;
248        Ok(res)
249    }
250
251    */
252}