crypto_pay_api/api/check.rs
1use async_trait::async_trait;
2
3use crate::{
4 client::CryptoBot,
5 error::CryptoBotError,
6 models::{
7 APIEndpoint, APIMethod, Check, CreateCheckParams, DeleteCheckParams, GetChecksParams, GetChecksResponse, Method,
8 },
9};
10
11use super::CheckAPI;
12
13#[async_trait]
14impl CheckAPI for CryptoBot {
15 /// Creates a new cryptocurrency check
16 ///
17 /// A check is a unique link that can be used once to transfer cryptocurrency.
18 /// Anyone who opens the link first can activate the check and get the cryptocurrency.
19 ///
20 /// # Arguments
21 /// * `params` - Parameters for creating a new check. See [`CreateCheckParams`] for details.
22 ///
23 /// # Returns
24 /// * `Ok(Check)` - The created check
25 /// * `Err(CryptoBotError)` - If validation fails or the request fails
26 ///
27 /// # Errors
28 /// This method will return an error if:
29 /// * The parameters are invalid (e.g., negative amount)
30 /// * The currency is not supported
31 /// * The API request fails
32 /// * The exchange rate validation fails
33 ///
34 /// # Example
35 /// ```no_run
36 /// use crypto_pay_api::prelude::*;
37 ///
38 /// #[tokio::main]
39 /// async fn main() -> Result<(), CryptoBotError> {
40 /// let client = CryptoBot::builder().api_token("YOUR_API_TOKEN").build().unwrap();
41 ///
42 /// let params = CreateCheckParamsBuilder::new()
43 /// .asset(CryptoCurrencyCode::Ton)
44 /// .amount(dec!(10.5))
45 /// .build(&client).await?;
46 ///
47 /// let check = client.create_check(¶ms).await?;
48 /// println!("Check created: {}", check.check_id);
49 ///
50 /// Ok(())
51 /// }
52 /// ```
53 ///
54 /// # See Also
55 /// * [Check](struct.Check.html) - The structure representing a check
56 /// * [CreateCheckParams](struct.CreateCheckParams.html) - The parameters for creating a check
57 async fn create_check(&self, params: &CreateCheckParams) -> Result<Check, CryptoBotError> {
58 self.make_request(
59 &APIMethod {
60 endpoint: APIEndpoint::CreateCheck,
61 method: Method::POST,
62 },
63 Some(params),
64 )
65 .await
66 }
67
68 /// Deletes an existing cryptocurrency check
69 ///
70 /// Once deleted, the check link will become invalid and cannot be activated.
71 ///
72 /// # Arguments
73 /// * `check_id` - The unique identifier of the check to delete
74 ///
75 /// # Returns
76 /// * `Ok(true)` - If the check was successfully deleted
77 /// * `Err(CryptoBotError)` - If the check doesn't exist or the request fails
78 ///
79 /// # Example
80 /// ```no_run
81 /// use crypto_pay_api::prelude::*;
82 ///
83 /// #[tokio::main]
84 /// async fn main() -> Result<(), CryptoBotError> {
85 /// let client = CryptoBot::builder().api_token("YOUR_API_TOKEN").build().unwrap();
86 ///
87 /// match client.delete_check(12345).await {
88 /// Ok(_) => println!("Check deleted successfully"),
89 /// Err(e) => eprintln!("Failed to delete check: {}", e),
90 /// }
91 ///
92 /// Ok(())
93 /// }
94 /// ```
95 async fn delete_check(&self, check_id: u64) -> Result<bool, CryptoBotError> {
96 let params = DeleteCheckParams { check_id };
97
98 self.make_request(
99 &APIMethod {
100 endpoint: APIEndpoint::DeleteCheck,
101 method: Method::DELETE,
102 },
103 Some(¶ms),
104 )
105 .await
106 }
107
108 /// Gets a list of created cryptocurrency checks
109 ///
110 /// Retrieves all checks matching the specified filter parameters.
111 /// If no parameters are provided, returns all checks.
112 ///
113 /// # Arguments
114 /// * `params` - Optional filter parameters. See [`GetChecksParams`] for available filters.
115 ///
116 /// # Returns
117 /// * `Ok(Vec<Check>)` - List of checks matching the filter criteria
118 /// * `Err(CryptoBotError)` - If the parameters are invalid or the request fails
119 ///
120 /// # Example
121 /// ```no_run
122 /// use crypto_pay_api::prelude::*;
123 ///
124 /// #[tokio::main]
125 /// async fn main() -> Result<(), CryptoBotError> {
126 /// let client = CryptoBot::builder().api_token("YOUR_API_TOKEN").build().unwrap();
127 ///
128 /// // Get all checks
129 /// let all_checks = client.get_checks(None).await?;
130 ///
131 /// // Get checks with filters
132 /// let params = GetChecksParamsBuilder::new()
133 /// .asset(CryptoCurrencyCode::Ton)
134 /// .status(CheckStatus::Active)
135 /// .build()
136 /// .unwrap();
137 ///
138 /// let filtered_checks = client.get_checks(Some(¶ms)).await?;
139 ///
140 /// for check in filtered_checks {
141 /// println!("Check ID: {}, Amount: {}",
142 /// check.check_id,
143 /// check.amount,
144 /// );
145 /// }
146 ///
147 /// Ok(())
148 /// }
149 /// ```
150 ///
151 /// # See Also
152 /// * [Check](struct.Check.html) - The structure representing a check
153 /// * [GetChecksParams](struct.GetChecksParams.html) - Available filter parameters
154 /// * [CryptoBot API Documentation](https://help.crypt.bot/crypto-pay-api#getChecks)
155 async fn get_checks(&self, params: Option<&GetChecksParams>) -> Result<Vec<Check>, CryptoBotError> {
156 let response: GetChecksResponse = self
157 .make_request(
158 &APIMethod {
159 endpoint: APIEndpoint::GetChecks,
160 method: Method::GET,
161 },
162 params,
163 )
164 .await?;
165
166 Ok(response.items)
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use mockito::Mock;
173 use rust_decimal_macros::dec;
174 use serde_json::json;
175
176 use crate::{
177 models::{CreateCheckParamsBuilder, CryptoCurrencyCode, GetChecksParamsBuilder},
178 utils::test_utils::TestContext,
179 };
180
181 use super::*;
182
183 impl TestContext {
184 pub fn mock_create_check_response(&mut self) -> Mock {
185 self.server
186 .mock("POST", "/createCheck")
187 .with_header("content-type", "application/json")
188 .with_header("Crypto-Pay-API-Token", "test_token")
189 .with_body(
190 json!({
191 "ok": true,
192 "result": {
193 "check_id": 123,
194 "hash": "hash",
195 "asset": "TON",
196 "amount": "10.00",
197 "bot_check_url": "https://example.com/check",
198 "status": "active",
199 "created_at": "2021-01-01T00:00:00Z",
200 "activated_at": "2021-01-01T00:00:00Z",
201 }
202 })
203 .to_string(),
204 )
205 .create()
206 }
207
208 pub fn mock_get_checks_response_without_params(&mut self) -> Mock {
209 self.server
210 .mock("GET", "/getChecks")
211 .with_header("content-type", "application/json")
212 .with_header("Crypto-Pay-API-Token", "test_token")
213 .with_body(
214 json!({
215 "ok": true,
216 "result": {
217 "items": [
218 {
219 "check_id": 123,
220 "hash": "hash",
221 "asset": "TON",
222 "amount": "10.00",
223 "bot_check_url": "https://example.com/check",
224 "status": "active",
225 "created_at": "2021-01-01T00:00:00Z",
226 "activated_at": "2021-01-01T00:00:00Z",
227 }
228 ]
229 }
230 })
231 .to_string(),
232 )
233 .create()
234 }
235
236 pub fn mock_get_checks_response_with_check_ids(&mut self) -> Mock {
237 self.server
238 .mock("GET", "/getChecks")
239 .match_body(json!({ "check_ids": "123" }).to_string().as_str())
240 .with_header("content-type", "application/json")
241 .with_header("Crypto-Pay-API-Token", "test_token")
242 .with_body(
243 json!({
244 "ok": true,
245 "result": {
246 "items": [
247 {
248 "check_id": 123,
249 "hash": "hash",
250 "asset": "TON",
251 "amount": "10.00",
252 "bot_check_url": "https://example.com/check",
253 "status": "active",
254 "created_at": "2021-01-01T00:00:00Z",
255 "activated_at": "2021-01-01T00:00:00Z",
256 }
257 ]
258 }
259 })
260 .to_string(),
261 )
262 .create()
263 }
264
265 pub fn mock_delete_check_response(&mut self) -> Mock {
266 self.server
267 .mock("DELETE", "/deleteCheck")
268 .with_header("content-type", "application/json")
269 .with_header("Crypto-Pay-API-Token", "test_token")
270 .with_body(json!({ "ok": true, "result": true }).to_string())
271 .create()
272 }
273 }
274
275 #[test]
276 fn test_create_check() {
277 let mut ctx = TestContext::new();
278 let _m = ctx.mock_exchange_rates_response();
279 let _m = ctx.mock_create_check_response();
280
281 let client = CryptoBot::builder()
282 .api_token("test_token")
283 .base_url(ctx.server.url())
284 .build()
285 .unwrap();
286
287 let result = ctx.run(async {
288 let params = CreateCheckParamsBuilder::new()
289 .asset(CryptoCurrencyCode::Ton)
290 .amount(dec!(10.0))
291 .build(&client)
292 .await
293 .unwrap();
294
295 client.create_check(¶ms).await
296 });
297
298 assert!(result.is_ok());
299
300 let check = result.unwrap();
301 assert_eq!(check.check_id, 123);
302 assert_eq!(check.asset, CryptoCurrencyCode::Ton);
303 assert_eq!(check.amount, dec!(10.0));
304 }
305
306 #[test]
307 fn test_get_checks_without_params() {
308 let mut ctx = TestContext::new();
309 let _m = ctx.mock_get_checks_response_without_params();
310
311 let client = CryptoBot::builder()
312 .api_token("test_token")
313 .base_url(ctx.server.url())
314 .build()
315 .unwrap();
316
317 let result = ctx.run(async { client.get_checks(None).await });
318
319 assert!(result.is_ok());
320
321 let checks = result.unwrap();
322 assert_eq!(checks.len(), 1);
323 assert_eq!(checks[0].check_id, 123);
324 }
325
326 #[test]
327 fn test_get_checks_with_check_ids() {
328 let mut ctx = TestContext::new();
329 let _m = ctx.mock_get_checks_response_with_check_ids();
330
331 let client = CryptoBot::builder()
332 .api_token("test_token")
333 .base_url(ctx.server.url())
334 .build()
335 .unwrap();
336
337 let params = GetChecksParamsBuilder::new().check_ids(vec![123]).build().unwrap();
338
339 let result = ctx.run(async { client.get_checks(Some(¶ms)).await });
340
341 assert!(result.is_ok());
342
343 let checks = result.unwrap();
344 assert_eq!(checks.len(), 1);
345 assert_eq!(checks[0].check_id, 123);
346 }
347
348 #[test]
349 fn test_delete_check() {
350 let mut ctx = TestContext::new();
351 let _m = ctx.mock_delete_check_response();
352
353 let client = CryptoBot::builder()
354 .api_token("test_token")
355 .base_url(ctx.server.url())
356 .build()
357 .unwrap();
358
359 let result = ctx.run(async { client.delete_check(123).await });
360
361 assert!(result.is_ok());
362 assert!(result.unwrap());
363 }
364}