crypto_pay_api/api/transfer.rs
1use crate::{
2 client::CryptoBot,
3 error::CryptoBotResult,
4 models::{APIEndpoint, APIMethod, GetTransfersParams, GetTransfersResponse, Method, Transfer, TransferParams},
5};
6
7use super::TransferAPI;
8
9#[async_trait::async_trait]
10impl TransferAPI for CryptoBot {
11 /// Transfer cryptocurrency to a user
12 ///
13 /// # Arguments
14 /// * `params` - Parameters for the transfer, including:
15 /// - `user_id`: Telegram user ID
16 /// - `asset`: Cryptocurrency code (e.g., "TON", "BTC")
17 /// - `amount`: Amount to transfer
18 /// - `spend_id`: Optional unique ID to ensure idempotence
19 /// - `comment`: Optional comment for transfer
20 /// - `disable_send_notification`: Optional flag to disable notification
21 ///
22 /// # Returns
23 /// Returns Result containing transfer information or CryptoBotError
24 ///
25 /// # Example
26 /// ```no_run
27 /// use crypto_pay_api::prelude::*;
28 ///
29 /// #[tokio::main]
30 /// async fn main() -> CryptoBotResult<()> {
31 /// let client = CryptoBot::builder().api_token("your_token").build()?;
32 ///
33 /// let params = TransferParamsBuilder::new()
34 /// .user_id(123456789)
35 /// .asset(CryptoCurrencyCode::Ton)
36 /// .amount(dec!(10.5))
37 /// .spend_id("unique_id")
38 /// .comment("Payment for services")
39 /// .build(&client)
40 /// .await?;
41 ///
42 /// let transfer = client.transfer(¶ms).await?;
43 ///
44 /// println!("Transfer ID: {}", transfer.transfer_id);
45 ///
46 /// Ok(())
47 /// }
48 /// ```
49 ///
50 /// # See also
51 /// * [GetTransfersParamsBuilder](crate::models::GetTransfersParamsBuilder)
52 /// * [TransferParamsBuilder](crate::models::TransferParamsBuilder)
53 async fn transfer(&self, params: &TransferParams) -> CryptoBotResult<Transfer> {
54 self.make_request(
55 &APIMethod {
56 endpoint: APIEndpoint::Transfer,
57 method: Method::POST,
58 },
59 Some(params),
60 )
61 .await
62 }
63 /// Get transfers history
64 ///
65 /// # Arguments
66 /// * `params` - Optional parameters to filter transfers:
67 /// - `asset`: Filter by cryptocurrency code
68 /// - `transfer_ids`: List of specific transfer IDs to retrieve
69 /// - `spend_id`: Unique ID to filter transfers by spend ID
70 /// - `offset`: Number of records to skip (for pagination)
71 /// - `count`: Maximum number of records to return (1-1000)
72 ///
73 /// # Returns
74 /// Returns Result containing a vector of transfers or CryptoBotError
75 ///
76 /// # Example
77 /// ```no_run
78 /// use crypto_pay_api::prelude::*;
79 ///
80 /// #[tokio::main]
81 /// async fn main() -> CryptoBotResult<()> {
82 /// let client = CryptoBot::builder().api_token("your_token").build()?;
83 ///
84 /// // Get all transfers
85 /// let all_transfers = client.get_transfers(None).await?;
86 ///
87 /// // Get filtered transfers
88 /// let params = GetTransfersParamsBuilder::new()
89 /// .asset(CryptoCurrencyCode::Ton)
90 /// .transfer_ids(vec![1, 2, 3])
91 /// .count(10)
92 /// .build()?;
93 ///
94 /// let filtered_transfers = client.get_transfers(Some(¶ms)).await?;
95 ///
96 /// Ok(())
97 /// }
98 /// ```
99 ///
100 /// # See also
101 /// * [GetTransfersParamsBuilder](crate::models::GetTransfersParamsBuilder)
102 async fn get_transfers(&self, params: Option<&GetTransfersParams>) -> CryptoBotResult<Vec<Transfer>> {
103 let response: GetTransfersResponse = self
104 .make_request(
105 &APIMethod {
106 endpoint: APIEndpoint::GetTransfers,
107 method: Method::GET,
108 },
109 params,
110 )
111 .await?;
112
113 Ok(response.items)
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use mockito::Mock;
120 use rust_decimal_macros::dec;
121 use serde_json::json;
122
123 use crate::{
124 api::TransferAPI,
125 client::CryptoBot,
126 models::{CryptoCurrencyCode, GetTransfersParamsBuilder, TransferParamsBuilder, TransferStatus},
127 utils::test_utils::TestContext,
128 };
129
130 impl TestContext {
131 pub fn mock_transfer_response(&mut self) -> Mock {
132 self.server
133 .mock("POST", "/transfer")
134 .with_header("content-type", "application/json")
135 .with_header("Crypto-Pay-API-Token", "test_token")
136 .with_body(
137 json!({
138 "ok": true,
139 "result": {
140 "transfer_id": 1,
141 "user_id": 123456789,
142 "asset": "TON",
143 "amount": "10.5",
144 "status": "completed",
145 "completed_at": "2024-03-14T12:00:00Z",
146 "comment": "test_comment",
147 "spend_id": "test_spend_id",
148 "disable_send_notification": false,
149 }
150 })
151 .to_string(),
152 )
153 .create()
154 }
155
156 pub fn mock_get_transfers_response_without_params(&mut self) -> Mock {
157 self.server
158 .mock("GET", "/getTransfers")
159 .with_header("content-type", "application/json")
160 .with_header("Crypto-Pay-API-Token", "test_token")
161 .with_body(
162 json!({
163 "ok": true,
164 "result": {
165 "items": [{
166 "transfer_id": 1,
167 "user_id": 123456789,
168 "asset": "TON",
169 "amount": "10.5",
170 "status": "completed",
171 "completed_at": "2024-03-14T12:00:00Z",
172 "comment": "test_comment",
173 "spend_id": "test_spend_id",
174 "disable_send_notification": false,
175 }]
176 }
177 })
178 .to_string(),
179 )
180 .create()
181 }
182
183 pub fn mock_get_transfers_response_with_transfer_ids(&mut self) -> Mock {
184 self.server
185 .mock("GET", "/getTransfers")
186 .match_body(json!({ "transfer_ids": "1" }).to_string().as_str())
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 "items": [
194 {
195 "transfer_id": 1,
196 "user_id": 123456789,
197 "asset": "TON",
198 "amount": "10.5",
199 "status": "completed",
200 "completed_at": "2024-03-14T12:00:00Z",
201 "comment": "test_comment",
202 "spend_id": "test_spend_id",
203 "disable_send_notification": false,
204 }
205 ]
206 }
207 })
208 .to_string(),
209 )
210 .create()
211 }
212 }
213
214 #[test]
215 fn test_transfer() {
216 let mut ctx = TestContext::new();
217 let _m = ctx.mock_exchange_rates_response();
218 let _m = ctx.mock_transfer_response();
219
220 let client = CryptoBot::builder()
221 .api_token("test_token")
222 .base_url(ctx.server.url())
223 .build()
224 .unwrap();
225
226 let result = ctx.run(async {
227 let params = TransferParamsBuilder::new()
228 .user_id(123456789)
229 .asset(CryptoCurrencyCode::Ton)
230 .amount(dec!(10.5))
231 .spend_id("test_spend_id".to_string())
232 .comment("test_comment".to_string())
233 .build(&client)
234 .await
235 .unwrap();
236 client.transfer(¶ms).await
237 });
238
239 println!("result:{:?}", result);
240
241 assert!(result.is_ok());
242
243 let transfer = result.unwrap();
244 assert_eq!(transfer.transfer_id, 1);
245 assert_eq!(transfer.user_id, 123456789);
246 assert_eq!(transfer.asset, CryptoCurrencyCode::Ton);
247 assert_eq!(transfer.amount, dec!(10.5));
248 assert_eq!(transfer.status, TransferStatus::Completed);
249 }
250
251 #[test]
252 fn test_get_transfers_without_params() {
253 let mut ctx = TestContext::new();
254 let _m = ctx.mock_get_transfers_response_without_params();
255
256 let client = CryptoBot::builder()
257 .api_token("test_token")
258 .base_url(ctx.server.url())
259 .build()
260 .unwrap();
261
262 let params = GetTransfersParamsBuilder::new()
263 .asset(CryptoCurrencyCode::Ton)
264 .transfer_ids(vec![1])
265 .build()
266 .unwrap();
267
268 let result = ctx.run(async { client.get_transfers(Some(¶ms)).await });
269
270 assert!(result.is_ok());
271 let transfers = result.unwrap();
272 assert_eq!(transfers.len(), 1);
273
274 let transfer = &transfers[0];
275 assert_eq!(transfer.transfer_id, 1);
276 assert_eq!(transfer.asset, CryptoCurrencyCode::Ton);
277 assert_eq!(transfer.status, TransferStatus::Completed);
278 }
279
280 #[test]
281 fn test_get_transfers_with_transfer_ids() {
282 let mut ctx = TestContext::new();
283 let _m = ctx.mock_get_transfers_response_with_transfer_ids();
284
285 let client = CryptoBot::builder()
286 .api_token("test_token")
287 .base_url(ctx.server.url())
288 .build()
289 .unwrap();
290
291 let params = GetTransfersParamsBuilder::new().transfer_ids(vec![1]).build().unwrap();
292
293 let result = ctx.run(async { client.get_transfers(Some(¶ms)).await });
294
295 assert!(result.is_ok());
296 let transfers = result.unwrap();
297 assert_eq!(transfers.len(), 1);
298 }
299}