nowpayments/
client.rs

1use miniserde::json;
2use std::collections::HashMap;
3use std::fmt::Display;
4
5use crate::response::currencies::FullCurrencies;
6use crate::response::currencies::SelectedCurrencies;
7use crate::response::payments::EstimatedPaymentAmount;
8use crate::response::payments::MinPaymentAmount;
9use crate::response::payouts::AllPayouts;
10use crate::response::payouts::Payouts;
11use crate::response::status::Status;
12use crate::response::{conversion::SingleConversion, payments::Payment};
13use crate::response::{currencies::Currencies, payments::PaymentStatus};
14use crate::{
15    jwt::{JWTJson, JWT},
16    response::conversion::AllConversions,
17};
18use anyhow::{bail, Result};
19use reqwest::header;
20
21static BASE_URL: &str = "https://api.nowpayments.io/v1/";
22static BASE_SANDBOX_URL: &str = "https://api-sandbox.nowpayments.io/v1/";
23static USERAGENT: &str = concat!("rust/nowpayments/", "0.1.0");
24
25pub struct NPClient {
26    base_url: &'static str,
27    email: Option<String>,
28    password: Option<String>,
29
30    jwt: JWT,
31    client: reqwest::Client,
32}
33
34impl NPClient {
35    pub fn new(api_key: &str) -> Self {
36        let mut headers = header::HeaderMap::new();
37        headers.insert("X-API-KEY", header::HeaderValue::from_str(api_key).unwrap());
38
39        Self {
40            base_url: BASE_URL,
41            client: reqwest::ClientBuilder::new()
42                .user_agent(USERAGENT)
43                .default_headers(headers)
44                .build()
45                .unwrap(),
46            email: None,
47            password: None,
48            jwt: JWT::new(),
49        }
50    }
51
52    pub fn new_sandbox(api_key: &str) -> Self {
53        let mut headers = header::HeaderMap::new();
54        headers.insert("X-API-KEY", header::HeaderValue::from_str(api_key).unwrap());
55
56        Self {
57            base_url: BASE_SANDBOX_URL,
58            client: reqwest::ClientBuilder::new()
59                .user_agent(USERAGENT)
60                .default_headers(headers)
61                .build()
62                .unwrap(),
63            email: None,
64            password: None,
65            jwt: JWT::new(),
66        }
67    }
68
69    pub fn set_auth(&mut self, email: String, password: String) {
70        self.email = Some(email);
71        self.password = Some(password);
72    }
73
74    async fn get(&self, endpoint: impl ToString) -> Result<String> {
75        let endpoint = format!("{}{}", self.base_url, endpoint.to_string());
76
77        let req = self
78            .client
79            .get(endpoint)
80            .bearer_auth(self.jwt.get().unwrap_or("".to_string()))
81            .build()?;
82
83        Ok(self.client.execute(req).await?.text().await?)
84    }
85
86    async fn post(
87        &self,
88        endpoint: impl Display,
89        data: HashMap<&'static str, String>,
90    ) -> Result<String> {
91        let endpoint = format!("{}{}", self.base_url, endpoint);
92
93        let req = self
94            .client
95            .post(endpoint)
96            .json(&data)
97            .bearer_auth(self.jwt.get().unwrap_or("".to_string()))
98            .build()?;
99
100        Ok(self.client.execute(req).await?.text().await?)
101    }
102
103    pub async fn authenticate(&mut self) -> Result<()> {
104        if self.email.is_none() || self.password.is_none() {
105            bail!("not set username or pass");
106        }
107        let mut json = HashMap::new();
108        json.insert("email", self.email.clone().unwrap());
109        json.insert("password", self.password.clone().unwrap());
110
111        let data = self.post("auth", json).await?;
112        let jwt: JWTJson = json::from_str(&data)?;
113        self.jwt.set(jwt.token);
114        Ok(())
115    }
116}
117
118impl NPClient {
119    pub async fn status(&self) -> Result<Status> {
120        let req = self.get("status").await?;
121
122        Ok(json::from_str(req.as_str())?)
123    }
124
125    pub async fn get_currencies(&self) -> Result<Currencies> {
126        let req = self.get("currencies").await?;
127
128        Ok(json::from_str(req.as_str())?)
129    }
130
131    pub async fn get_full_currencies(&self) -> Result<FullCurrencies> {
132        let req = self.get("full-currencies").await?;
133
134        Ok(json::from_str(req.as_str())?)
135    }
136
137    pub async fn get_checked_currencies(&self) -> Result<SelectedCurrencies> {
138        let req = self.get("merchant/coins").await?;
139
140        Ok(json::from_str(req.as_str())?)
141    }
142    // TODO
143    pub async fn get_min_payment_amount(
144        &self,
145        from: impl Display,
146        to: impl Display,
147    ) -> Result<MinPaymentAmount> {
148        let path = format!("min-amount?currency_from={}&currency_to={}", from, to);
149        let req = self.get(path).await?;
150
151        Ok(json::from_str(req.as_str())?)
152    }
153    // TODO
154    pub async fn get_estimated_price(
155        &self,
156        amount: impl Display,
157        from: impl Display,
158        to: impl Display,
159    ) -> Result<EstimatedPaymentAmount> {
160        let path = format!(
161            "estimate?amount={}&currency_from={}&currency_to={}",
162            amount, from, to
163        );
164        let req = self.get(path).await?;
165
166        Ok(json::from_str(req.as_str())?)
167    }
168
169    pub async fn get_payment_status(&self, payment_id: impl Display) -> Result<PaymentStatus> {
170        if self.jwt.is_expired() {
171            bail!("Expired jwt");
172        }
173        let path = format!("payment/{}", payment_id);
174        let req = self.get(path).await?;
175
176        Ok(json::from_str(req.as_str())?)
177    }
178
179    pub async fn get_list_of_payments(
180        &self,
181        limit: impl Display,
182        page: impl Display,
183        sort_by: impl Display,
184        order_by: impl Display,
185        date_from: impl Display,
186        date_to: impl Display,
187    ) -> Result<Payment> {
188        if self.jwt.is_expired() {
189            bail!("Expired jwt");
190        }
191        let path = format!(
192            "payment/?limit={}&page={}&sortBy={}&orderBy={}&dateFrom={}&dateTo={}",
193            limit, page, sort_by, order_by, date_from, date_to
194        );
195        let req = self.get(path).await?;
196
197        Ok(json::from_str(req.as_str())?)
198    }
199
200    // TODO
201    pub async fn get_balance(&self) -> Result<Status> {
202        let req = self.get("balance").await?;
203
204        Ok(json::from_str(req.as_str())?)
205    }
206
207    pub async fn get_payout_status(&self, payout_id: impl Display) -> Result<Payouts> {
208        let path = format!("payout/{}", payout_id);
209        let req = self.get(path).await?;
210
211        Ok(json::from_str(req.as_str())?)
212    }
213
214    pub async fn get_payout_list(&self) -> Result<AllPayouts> {
215        let req = self.get("payout").await?;
216
217        Ok(json::from_str(req.as_str())?)
218    }
219
220    pub async fn get_conversion_status(
221        &self,
222        conversion_id: impl Display,
223    ) -> Result<SingleConversion> {
224        let path = format!("conversion/{}", conversion_id);
225        let req = self.get(path).await?;
226
227        Ok(json::from_str(req.as_str())?)
228    }
229
230    pub async fn get_conversion_list(&self) -> Result<AllConversions> {
231        let path = "conversion".to_string();
232        let req = self.get(path).await?;
233
234        Ok(json::from_str(req.as_str())?)
235    }
236}
237
238pub struct PaymentOpts {
239    price_amount: String,
240    price_currency: String,
241    pay_currency: String,
242    ipn_callback_url: String,
243    order_id: String,
244    order_description: String,
245}
246
247impl PaymentOpts {
248    pub fn new(
249        price_amount: u32,
250        price_currency: impl Display,
251        pay_currency: impl Display,
252        ipn_callback_url: impl Display,
253        order_id: impl Display,
254        order_description: impl Display,
255    ) -> Self {
256        PaymentOpts {
257            price_amount: price_amount.to_string(),
258            price_currency: price_currency.to_string(),
259            pay_currency: pay_currency.to_string(),
260            ipn_callback_url: ipn_callback_url.to_string(),
261            order_id: order_id.to_string(),
262            order_description: order_description.to_string(),
263        }
264    }
265}
266
267impl NPClient {
268    pub async fn create_payment(&self, opts: PaymentOpts) -> Result<Payment> {
269        let mut h = HashMap::new();
270        h.insert("price_amount", opts.price_amount.clone());
271        h.insert("price_currency", opts.price_currency.clone());
272        h.insert("pay_currency", opts.pay_currency.clone());
273        h.insert("ipn_callback_url", opts.ipn_callback_url.clone());
274        h.insert("order_id", opts.order_id.clone());
275        h.insert("order_description", opts.order_description.clone());
276
277        let x = self.post("payment", h).await?;
278
279        Ok(json::from_str(x.as_str())?)
280    }
281
282    pub fn get_jwt(&self) {
283        dbg!(&self.jwt);
284    }
285}