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 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={}¤cy_to={}", from, to);
149 let req = self.get(path).await?;
150
151 Ok(json::from_str(req.as_str())?)
152 }
153 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={}¤cy_from={}¤cy_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 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}