1#![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#[derive(Debug, Clone)]
29pub struct Strike {
30 api_key: String,
31 base_url: Url,
32 client: Client,
33 webhook_secret: String,
34}
35
36#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
38#[serde(rename_all = "UPPERCASE")]
39pub enum Currency {
40 USD,
42 EUR,
44 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#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
60pub struct Amount {
61 pub currency: Currency,
63 #[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 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 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#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
96#[serde(rename_all = "UPPERCASE")]
97pub enum InvoiceState {
98 Completed,
100 Paid,
102 Unpaid,
104 Pending,
106}
107
108#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
110pub struct ConversionRate {
111 #[serde(deserialize_with = "parse_f64_from_string")]
113 pub amount: f64,
114 #[serde(rename = "sourceCurrency")]
116 pub source_currency: Currency,
117 #[serde(rename = "targetCurrency")]
119 pub target_currency: Currency,
120}
121
122impl Strike {
123 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 }