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(&params).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(&params)).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(&params).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(&params)).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(&params)).await });
294
295        assert!(result.is_ok());
296        let transfers = result.unwrap();
297        assert_eq!(transfers.len(), 1);
298    }
299}