crypto_pay_api/api/misc.rs
1use crate::{
2 client::CryptoBot,
3 error::CryptoBotResult,
4 models::{APIEndpoint, APIMethod, AppStats, Currency, GetMeResponse, GetStatsParams, Method},
5};
6use async_trait::async_trait;
7
8use super::MiscAPI;
9
10#[async_trait]
11impl MiscAPI for CryptoBot {
12 /// Gets basic information about your application
13 ///
14 /// Retrieves information about your application, including app ID, name,
15 /// and payment processing bot username.
16 ///
17 /// # Returns
18 /// * `Ok(GetMeResponse)` - Basic information about your application
19 /// * `Err(CryptoBotError)` - If the request fails
20 ///
21 /// # Example
22 /// ```no_run
23 /// use crypto_pay_api::prelude::*;
24 ///
25 /// #[tokio::main]
26 /// async fn main() -> Result<(), CryptoBotError> {
27 /// let client = CryptoBot::builder().api_token("YOUR_API_TOKEN").build().unwrap();
28 ///
29 /// let app_info = client.get_me().await?;
30 /// println!("App ID: {}", app_info.app_id);
31 /// println!("App Name: {}", app_info.name);
32 /// println!("Bot Username: {}", app_info.payment_processing_bot_username);
33 ///
34 /// Ok(())
35 /// }
36 /// ```
37 ///
38 /// # See Also
39 /// * [GetMeResponse](struct.GetMeResponse.html) - The structure representing the response
40 async fn get_me(&self) -> CryptoBotResult<GetMeResponse> {
41 self.make_request(
42 &APIMethod {
43 endpoint: APIEndpoint::GetMe,
44 method: Method::GET,
45 },
46 None::<()>.as_ref(),
47 )
48 .await
49 }
50
51 /// Gets a list of all supported cryptocurrencies
52 ///
53 /// Returns information about all cryptocurrencies supported by CryptoBot,
54 /// including both crypto and fiat currencies.
55 ///
56 /// # Returns
57 /// * `Ok(Vec<Currency>)` - List of supported currencies with their properties
58 /// * `Err(CryptoBotError)` - If the request fails
59 ///
60 /// # Example
61 /// ```no_run
62 /// use crypto_pay_api::prelude::*;
63 ///
64 /// #[tokio::main]
65 /// async fn main() -> Result<(), CryptoBotError> {
66 /// let client = CryptoBot::builder().api_token("YOUR_API_TOKEN").build().unwrap();
67 ///
68 /// let currencies = client.get_currencies().await?;
69 ///
70 /// for currency in currencies {
71 /// println!("Currency: {}", currency.name);
72 /// println!("Type: {}", if currency.is_blockchain { "Crypto" }
73 /// else if currency.is_fiat { "Fiat" }
74 /// else { "Stablecoin" });
75 /// println!("Decimals: {}", currency.decimals);
76 /// if let Some(url) = currency.url {
77 /// println!("Website: {}", url);
78 /// }
79 /// println!("---");
80 /// }
81 ///
82 /// Ok(())
83 /// }
84 /// ```
85 ///
86 /// # See Also
87 /// * [Currency](struct.Currency.html) - The structure representing a currency
88 async fn get_currencies(&self) -> CryptoBotResult<Vec<Currency>> {
89 self.make_request(
90 &APIMethod {
91 endpoint: APIEndpoint::GetCurrencies,
92 method: Method::GET,
93 },
94 None::<()>.as_ref(),
95 )
96 .await
97 }
98
99 /// Gets application statistics for a specified time period
100 ///
101 /// Retrieves statistics about your application's usage, including
102 /// transaction volumes, number of invoices, and user counts.
103 ///
104 /// # Arguments
105 /// * `params` - Optional parameters to filter statistics by date range.
106 /// See [`GetStatsParams`] for available options.
107 ///
108 /// # Returns
109 /// * `Ok(AppStats)` - Application statistics for the specified period
110 /// * `Err(CryptoBotError)` - If the parameters are invalid or the request fails
111 ///
112 /// # Example
113 /// ```no_run
114 /// use crypto_pay_api::prelude::*;
115 /// use chrono::{Utc, Duration};
116 ///
117 /// #[tokio::main]
118 /// async fn main() -> Result<(), CryptoBotError> {
119 /// let client = CryptoBot::builder().api_token("YOUR_API_TOKEN").build().unwrap();
120 ///
121 /// // Get stats for the last 7 days
122 /// let end_date = Utc::now();
123 /// let start_date = end_date - Duration::days(7);
124 ///
125 /// let params = GetStatsParamsBuilder::new()
126 /// .start_at(start_date)
127 /// .end_at(end_date)
128 /// .build()
129 /// .unwrap();
130 ///
131 /// let stats = client.get_stats(Some(¶ms)).await?;
132 ///
133 /// println!("Statistics for the last 7 days:");
134 /// println!("Total volume: {}", stats.volume);
135 /// println!("Number of invoices created: {}", stats.created_invoice_count);
136 /// println!("Number of paid invoices: {}", stats.paid_invoice_count);
137 /// println!("Unique users: {}", stats.unique_users_count);
138 ///
139 /// Ok(())
140 /// }
141 /// ```
142 ///
143 /// # See Also
144 /// * [AppStats](struct.AppStats.html) - The structure representing application statistics
145 /// * [GetStatsParams](struct.GetStatsParams.html) - Parameters for filtering statistics
146 async fn get_stats(&self, params: Option<&GetStatsParams>) -> CryptoBotResult<AppStats> {
147 self.make_request(
148 &APIMethod {
149 endpoint: APIEndpoint::GetStats,
150 method: Method::GET,
151 },
152 params,
153 )
154 .await
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use mockito::Mock;
161 use rust_decimal::Decimal;
162 use serde_json::json;
163
164 use crate::{
165 api::MiscAPI,
166 client::CryptoBot,
167 models::{CryptoCurrencyCode, CurrencyCode, GetStatsParams},
168 utils::test_utils::TestContext,
169 };
170
171 impl TestContext {
172 pub fn mock_get_me_response(&mut self) -> Mock {
173 self.server
174 .mock("GET", "/getMe")
175 .with_header("content-type", "application/json")
176 .with_header("Crypto-Pay-API-Token", "test_token")
177 .with_body(
178 json!({
179 "ok": true,
180 "result": {
181 "app_id": 28692,
182 "name": "Stated Seaslug App",
183 "payment_processing_bot_username": "CryptoTestnetBot"
184 }
185 })
186 .to_string(),
187 )
188 .create()
189 }
190
191 pub fn mock_currencies_response(&mut self) -> Mock {
192 println!("Setting up mock response");
193 self.server
194 .mock("GET", "/getCurrencies")
195 .with_header("content-type", "application/json")
196 .with_header("Crypto-Pay-API-Token", "test_token")
197 .with_body(
198 json!({
199 "ok": true,
200 "result": [
201 {
202 "is_blockchain": false,
203 "is_stablecoin": true,
204 "is_fiat": false,
205 "name": "Tether",
206 "code": "USDT",
207 "url": "https://tether.to/",
208 "decimals": 18
209 },
210 {
211 "is_blockchain": true,
212 "is_stablecoin": false,
213 "is_fiat": false,
214 "name": "Toncoin",
215 "code": "TON",
216 "url": "https://ton.org/",
217 "decimals": 9
218 },
219 {
220 "is_blockchain": true,
221 "is_stablecoin": false,
222 "is_fiat": false,
223 "name": "Bitcoin",
224 "code": "BTC",
225 "url": "https://bitcoin.org/",
226 "decimals": 8
227 },
228 {
229 "is_blockchain": true,
230 "is_stablecoin": false,
231 "is_fiat": false,
232 "name": "Dogecoin",
233 "code": "DOGE",
234 "url": "https://dogecoin.org/",
235 "decimals": 8
236 },
237 {
238 "is_blockchain": true,
239 "is_stablecoin": false,
240 "is_fiat": false,
241 "name": "Litecoin",
242 "code": "LTC",
243 "url": "https://litecoin.org/",
244 "decimals": 8
245 },
246 {
247 "is_blockchain": true,
248 "is_stablecoin": false,
249 "is_fiat": false,
250 "name": "Ethereum",
251 "code": "ETH",
252 "url": "https://ethereum.org/",
253 "decimals": 18
254 },
255 {
256 "is_blockchain": true,
257 "is_stablecoin": false,
258 "is_fiat": false,
259 "name": "Binance Coin",
260 "code": "BNB",
261 "url": "https://binance.org/",
262 "decimals": 18
263 },
264 {
265 "is_blockchain": true,
266 "is_stablecoin": false,
267 "is_fiat": false,
268 "name": "TRON",
269 "code": "TRX",
270 "url": "https://tron.network/",
271 "decimals": 6
272 },
273 {
274 "is_blockchain": false,
275 "is_stablecoin": true,
276 "is_fiat": false,
277 "name": "USD Coin",
278 "code": "USDC",
279 "url": "https://www.centre.io/usdc",
280 "decimals": 18
281 },
282 {
283 "is_blockchain": false,
284 "is_stablecoin": true,
285 "is_fiat": false,
286 "name": "TON Jetton",
287 "code": "JET",
288 "url": "https://ton.org",
289 "decimals": 9
290 },
291 {
292 "is_blockchain": true,
293 "is_stablecoin": false,
294 "is_fiat": false,
295 "name": "Crypto Bot",
296 "code": "SEND",
297 "url": "https://send.tg/",
298 "decimals": 9
299 },
300 {
301 "is_blockchain": false,
302 "is_stablecoin": false,
303 "is_fiat": true,
304 "name": "Russian ruble",
305 "code": "RUB",
306 "decimals": 8
307 },
308 {
309 "is_blockchain": false,
310 "is_stablecoin": false,
311 "is_fiat": true,
312 "name": "United States Dollar",
313 "code": "USD",
314 "decimals": 8
315 },
316 {
317 "is_blockchain": false,
318 "is_stablecoin": false,
319 "is_fiat": true,
320 "name": "Euro",
321 "code": "EUR",
322 "decimals": 8
323 },
324 {
325 "is_blockchain": false,
326 "is_stablecoin": false,
327 "is_fiat": true,
328 "name": "Belarusian ruble",
329 "code": "BYN",
330 "decimals": 8
331 },
332 {
333 "is_blockchain": false,
334 "is_stablecoin": false,
335 "is_fiat": true,
336 "name": "Ukrainian hryvnia",
337 "code": "UAH",
338 "decimals": 8
339 },
340 {
341 "is_blockchain": false,
342 "is_stablecoin": false,
343 "is_fiat": true,
344 "name": "Pound sterling",
345 "code": "GBP",
346 "decimals": 8
347 },
348 {
349 "is_blockchain": false,
350 "is_stablecoin": false,
351 "is_fiat": true,
352 "name": "Chinese yuan renminbi",
353 "code": "CNY",
354 "decimals": 8
355 },
356 {
357 "is_blockchain": false,
358 "is_stablecoin": false,
359 "is_fiat": true,
360 "name": "Kazakhstani tenge",
361 "code": "KZT",
362 "decimals": 8
363 },
364 {
365 "is_blockchain": false,
366 "is_stablecoin": false,
367 "is_fiat": true,
368 "name": "Uzbekistani som",
369 "code": "UZS",
370 "decimals": 8
371 },
372 {
373 "is_blockchain": false,
374 "is_stablecoin": false,
375 "is_fiat": true,
376 "name": "Georgian lari",
377 "code": "GEL",
378 "decimals": 8
379 },
380 {
381 "is_blockchain": false,
382 "is_stablecoin": false,
383 "is_fiat": true,
384 "name": "Turkish lira",
385 "code": "TRY",
386 "decimals": 8
387 },
388 {
389 "is_blockchain": false,
390 "is_stablecoin": false,
391 "is_fiat": true,
392 "name": "Armenian dram",
393 "code": "AMD",
394 "decimals": 8
395 },
396 {
397 "is_blockchain": false,
398 "is_stablecoin": false,
399 "is_fiat": true,
400 "name": "Thai baht",
401 "code": "THB",
402 "decimals": 8
403 },
404 {
405 "is_blockchain": false,
406 "is_stablecoin": false,
407 "is_fiat": true,
408 "name": "Indian rupee",
409 "code": "INR",
410 "decimals": 8
411 },
412 {
413 "is_blockchain": false,
414 "is_stablecoin": false,
415 "is_fiat": true,
416 "name": "Brazilian real",
417 "code": "BRL",
418 "decimals": 8
419 },
420 {
421 "is_blockchain": false,
422 "is_stablecoin": false,
423 "is_fiat": true,
424 "name": "Indonesian rupiah",
425 "code": "IDR",
426 "decimals": 8
427 },
428 {
429 "is_blockchain": false,
430 "is_stablecoin": false,
431 "is_fiat": true,
432 "name": "Azerbaijani manat",
433 "code": "AZN",
434 "decimals": 8
435 },
436 {
437 "is_blockchain": false,
438 "is_stablecoin": false,
439 "is_fiat": true,
440 "name": "United Arab Emirates dirham",
441 "code": "AED",
442 "decimals": 8
443 },
444 {
445 "is_blockchain": false,
446 "is_stablecoin": false,
447 "is_fiat": true,
448 "name": "Polish zloty",
449 "code": "PLN",
450 "decimals": 8
451 },
452 {
453 "is_blockchain": false,
454 "is_stablecoin": false,
455 "is_fiat": true,
456 "name": "Israeli new shekel",
457 "code": "ILS",
458 "decimals": 8
459 },
460 {
461 "is_blockchain": false,
462 "is_stablecoin": false,
463 "is_fiat": true,
464 "name": "Kyrgystani som",
465 "code": "KGS",
466 "decimals": 8
467 },
468 {
469 "is_blockchain": false,
470 "is_stablecoin": false,
471 "is_fiat": true,
472 "name": "Tajikistani somoni",
473 "code": "TJS",
474 "decimals": 8
475 }
476 ]
477 })
478 .to_string(),
479 )
480 .create()
481 }
482 // TODO add more data
483 pub fn mock_get_stats_response(&mut self) -> Mock {
484 self.server
485 .mock("GET", "/getStats")
486 .with_header("content-type", "application/json")
487 .with_header("Crypto-Pay-API-Token", "test_token")
488 .with_body(
489 json!({
490 "ok": true,
491 "result": {
492 "volume": 0,
493 "conversion": 0,
494 "unique_users_count": 0,
495 "created_invoice_count": 0,
496 "paid_invoice_count": 0,
497 "start_at": "2025-02-07T10:55:17.438Z",
498 "end_at": "2025-02-08T10:55:17.438Z"
499 }
500 })
501 .to_string(),
502 )
503 .create()
504 }
505 }
506
507 #[test]
508 fn test_get_me() {
509 let mut ctx = TestContext::new();
510 let _m = ctx.mock_get_me_response();
511
512 let client = CryptoBot::builder()
513 .api_token("test_token")
514 .base_url(ctx.server.url())
515 .build()
516 .unwrap();
517
518 let result = ctx.run(async { client.get_me().await });
519
520 println!("Result: {:?}", result);
521
522 assert!(result.is_ok());
523 let me = result.unwrap();
524 assert_eq!(me.app_id, 28692);
525 assert_eq!(me.name, "Stated Seaslug App");
526 assert_eq!(me.payment_processing_bot_username, "CryptoTestnetBot");
527 assert_eq!(me.webhook_endpoint, None);
528 }
529
530 #[test]
531 fn test_get_currencies() {
532 let mut ctx = TestContext::new();
533 let _m = ctx.mock_currencies_response();
534
535 let client = CryptoBot::builder()
536 .api_token("test_token")
537 .base_url(ctx.server.url())
538 .build()
539 .unwrap();
540
541 let result = ctx.run(async { client.get_currencies().await });
542
543 assert!(result.is_ok());
544 let currencies = result.unwrap();
545
546 assert_eq!(currencies.len(), 33);
547 assert_eq!(currencies[0].code, CurrencyCode::Crypto(CryptoCurrencyCode::Usdt));
548 assert_eq!(currencies[1].code, CurrencyCode::Crypto(CryptoCurrencyCode::Ton));
549 }
550
551 #[test]
552 fn test_get_stats_without_params() {
553 let mut ctx = TestContext::new();
554 let _m = ctx.mock_get_stats_response();
555
556 let client = CryptoBot::builder()
557 .api_token("test_token")
558 .base_url(ctx.server.url())
559 .build()
560 .unwrap();
561
562 let result = ctx.run(async { client.get_stats(None::<&GetStatsParams>).await });
563
564 println!("result: {:?}", result);
565
566 assert!(result.is_ok());
567 let stats = result.unwrap();
568 assert_eq!(stats.volume, Decimal::from(0));
569 assert_eq!(stats.conversion, Decimal::from(0));
570 }
571
572 // #[test]
573 // fn test_get_stats_with_params() {
574 // let mut ctx = TestContext::new();
575 // let _m = ctx.mock_get_stats_response();
576
577 // let client = CryptoBot::builder()
578 // .api_token("test_token")
579 // .base_url(ctx.server.url())
580 // .build()
581 // .unwrap();
582
583 // let params = GetStatsParamsBuilder::new()
584 // .start_at(Utc::now() - Duration::days(7))
585 // .end_at(Utc::now())
586 // .build()
587 // .unwrap();
588
589 // let result = ctx.run(async { client.get_stats(Some(¶ms)).await });
590
591 // assert!(result.is_ok());
592 // let stats = result.unwrap();
593 // assert_eq!(stats.volume, Decimal::from(0));
594 // assert_eq!(stats.conversion, Decimal::from(0));
595 // }
596}