etopay_sdk/core/viviswap/
swap.rs

1use crate::backend::viviswap::{
2    delete_viviswap_detail, get_viviswap_details, get_viviswap_order, get_viviswap_orders, get_viviswap_payment_method,
3    set_viviswap_contract, set_viviswap_detail,
4};
5use crate::core::Sdk;
6use crate::core::viviswap::ViviswapError;
7use crate::error::Result;
8use crate::types::currencies::Currency;
9use crate::types::newtypes::EncryptionPin;
10use crate::types::viviswap::{
11    ViviswapAddressDetail, ViviswapDeposit, ViviswapDepositDetails, ViviswapDetailUpdateStrategy, ViviswapWithdrawal,
12    ViviswapWithdrawalDetails,
13};
14use api_types::api::viviswap::contract::ViviswapApiContractDetails;
15use api_types::api::viviswap::detail::SwapPaymentDetailKey;
16use api_types::api::viviswap::order::{Order, OrderList};
17use etopay_wallet::types::CryptoAmount;
18use log::{debug, info};
19use rust_decimal_macros::dec;
20
21impl Sdk {
22    /// Get current iban of viviswap user
23    ///
24    /// # Arguments
25    ///
26    /// None
27    ///
28    /// # Returns
29    ///
30    /// - `Result<ViviswapAddressDetail>` - The current IBAN of the viviswap user.
31    ///
32    /// # Errors
33    ///
34    /// - [`crate::Error::ViviswapInvalidState`] - If the viviswap state is invalid.
35    /// - [`crate::Error::UserRepoNotInitialized`] - If the repository initialization fails.
36    /// - [`crate::Error::ViviswapApi`] - If there is an error in the viviswap API.
37    /// - [`crate::Error::UserStatusUpdateError`] - If there is an error updating the user status.
38    // MARK1:get_iban_for_viviswap
39    pub async fn get_iban_for_viviswap(&mut self) -> Result<ViviswapAddressDetail> {
40        info!("Getting IBAN for viviswap");
41        // load user entity
42        let mut user = self.get_user().await?;
43
44        // check if user has already a viviswap state available
45        let Some(mut viviswap_state) = user.viviswap_state else {
46            return Err(crate::Error::Viviswap(ViviswapError::UserStateExisting));
47        };
48
49        let Some(repo) = &mut self.repo else {
50            return Err(crate::Error::UserRepoNotInitialized);
51        };
52
53        // get iban from store
54        if let Some(iban) = viviswap_state.current_iban {
55            // try to get it from backend maybe
56            Ok(iban)
57        } else {
58            let access_token = self
59                .access_token
60                .as_ref()
61                .ok_or(crate::error::Error::MissingAccessToken)?;
62            let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
63            let details = get_viviswap_details(config, access_token, SwapPaymentDetailKey::Sepa).await?;
64            // update it internally
65            // get the first detail
66            if let Some(detail) = details.payment_detail.first() {
67                let new_detail = ViviswapAddressDetail {
68                    id: detail.id.clone(),
69                    address: detail.address.clone(),
70                    is_verified: detail.is_verified.unwrap_or(false),
71                };
72                viviswap_state.current_iban = Option::Some(new_detail.clone());
73                user.viviswap_state = Some(viviswap_state.clone());
74                repo.update(&user)?;
75                Ok(new_detail)
76            } else {
77                Err(crate::Error::Viviswap(ViviswapError::Api(
78                    "Unable to find IBAN at viviswap".to_string(),
79                )))
80            }
81        }
82    }
83
84    /// Ensure details
85    ///
86    /// # Arguments
87    ///
88    /// - `address` - The address to ensure.
89    /// - `payment_method_key` - The payment method key.
90    /// - `some_update_strategy` - The update strategy for the detail.
91    ///
92    /// # Returns
93    ///
94    /// - `Result<ViviswapAddressDetail>` - The ensured Viviswap address detail.
95    ///
96    /// # Errors
97    ///
98    /// - [`crate::Error::ViviswapApi`] - If there is an error loading payment details from the Viviswap API.
99    // MARK2:ensure_detail
100    async fn ensure_detail(
101        &self,
102        address: String,
103        payment_method_key: SwapPaymentDetailKey,
104        some_update_strategy: ViviswapDetailUpdateStrategy,
105    ) -> Result<ViviswapAddressDetail> {
106        debug!("Ensuring payment detail for viviswap");
107        // load user entity
108        let _user = self.get_user().await?;
109        let access_token = self
110            .access_token
111            .as_ref()
112            .ok_or(crate::error::Error::MissingAccessToken)?;
113        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
114
115        // get all viviswap details for specific payment-method
116        let details = get_viviswap_details(config, access_token, payment_method_key).await?;
117        debug!("{details:#?}");
118        // search for address in existing details
119        for detail in &details.payment_detail {
120            debug!("{detail:#?}");
121            let add = detail.address.clone();
122            debug!("{add:#?}");
123            debug!("{address:#?}");
124            if detail.address == address {
125                return Ok(ViviswapAddressDetail {
126                    id: detail.id.to_string(),
127                    address: detail.address.to_string(),
128                    is_verified: detail.is_verified.unwrap_or(false),
129                });
130            }
131        }
132
133        // 1. creates a new detail
134        let new_detail = set_viviswap_detail(config, access_token, payment_method_key, &address).await?;
135
136        // set_viviswap_detail should fill this out, but if not, we need to return an error
137        let Some(detail) = new_detail.payment_detail else {
138            return Err(crate::Error::Viviswap(ViviswapError::Api(
139                "no payment details returned".into(),
140            )));
141        };
142
143        // 2. handle the update strategy
144        if let (ViviswapDetailUpdateStrategy::Replace, Some(old_detail)) =
145            (some_update_strategy, details.payment_detail.first())
146        {
147            delete_viviswap_detail(config, access_token, payment_method_key, old_detail.id.as_str()).await?
148        }
149
150        // 3. returns the new detail
151        Ok(ViviswapAddressDetail {
152            id: detail.id,
153            address: detail.address,
154            is_verified: detail.is_verified.unwrap_or(false),
155        })
156    }
157
158    /// Update IBAN of viviswap user.
159    ///
160    /// # Arguments
161    ///
162    /// - `pin` - The user's PIN.
163    /// - `address` - The new IBAN address.
164    ///
165    /// # Returns
166    ///
167    /// - `Result<ViviswapAddressDetail>` - The updated Viviswap address detail.
168    ///
169    /// # Errors
170    ///
171    /// - [`crate::Error::UserRepoNotInitialized`] - If the repository initialization fails.
172    /// - [`crate::Error::ViviswapMissingUserError`] - If the viviswap user is missing.
173    /// - [`crate::Error::UserStatusUpdateError`] - If there is an error updating the user status.
174    // MARK3:update_iban_for_viviswap
175    pub async fn update_iban_for_viviswap(
176        &mut self,
177        pin: &EncryptionPin,
178        address: String,
179    ) -> Result<ViviswapAddressDetail> {
180        info!("Updating user IBAN");
181        // verify pin
182        self.verify_pin(pin).await?;
183
184        // ensure that the repository exist (cannot borrow as mutable here since we also borrow self as mutable in between)
185        if self.repo.is_none() {
186            return Err(crate::Error::UserRepoNotInitialized);
187        }
188
189        // load user entity
190        let mut user = self.get_user().await?;
191
192        // check if user has already a viviswap state available
193        let Some(mut viviswap_state) = user.viviswap_state else {
194            return Err(crate::Error::Viviswap(ViviswapError::MissingUser));
195        };
196
197        // 1. check if iban is already saved to service
198        if let Some(current_iban) = viviswap_state.current_iban {
199            if current_iban.address == address {
200                return Ok(current_iban.clone());
201            }
202        }
203
204        // 2. check if iban does already exist
205        let new_detail_response = self
206            .ensure_detail(
207                address,
208                SwapPaymentDetailKey::Sepa,
209                ViviswapDetailUpdateStrategy::Replace,
210            )
211            .await?;
212        let new_detail = new_detail_response;
213
214        // 3. update storage
215
216        // load repository
217        if let Some(repo) = &mut self.repo {
218            viviswap_state.current_iban = Option::Some(new_detail.clone());
219            user.viviswap_state = Some(viviswap_state.clone());
220            repo.update(&user)?;
221        } else {
222            return Err(crate::Error::UserRepoNotInitialized);
223        };
224
225        Ok(new_detail)
226    }
227
228    /// create deposit for viviswap user
229    ///
230    /// # Returns
231    ///
232    /// - `Result<ViviswapDeposit>` - The created Viviswap deposit.
233    ///
234    /// # Errors
235    ///
236    /// - [`crate::Error::UserRepoNotInitialized`] - If the repository initialization fails.
237    /// - [`crate::Error::ViviswapMissingUserError`] - If the viviswap user is missing.
238    /// - [`crate::Error::ViviswapInvalidState`] - If the viviswap state is invalid.
239    /// - [`crate::Error::ViviswapApi`] - If there is an error with the Viviswap API.
240    // MARK4:create_deposit_with_viviswap
241    pub async fn create_deposit_with_viviswap(&mut self, pin: &EncryptionPin) -> Result<ViviswapDeposit> {
242        info!("Creating deposit for viviswap");
243        // load user entity
244        let user = self.get_user().await?;
245        let address = self.generate_new_address(pin).await?;
246
247        // check if user has already a viviswap state available
248        let Some(viviswap_state) = user.viviswap_state else {
249            return Err(crate::Error::Viviswap(ViviswapError::MissingUser));
250        };
251
252        // check if iban exists, otherwise error
253        let Some(iban_detail) = viviswap_state.current_iban else {
254            return Err(crate::Error::Viviswap(ViviswapError::InvalidState));
255        };
256
257        let iban_method_id = self.get_payment_method_id_viviswap(SwapPaymentDetailKey::Sepa).await?;
258        let network = self.active_network.clone().ok_or(crate::Error::MissingNetwork)?;
259        let currency = Currency::try_from(network.display_symbol)?;
260
261        let payment_method_key = currency.to_vivi_payment_method_key();
262
263        let coin_method_id = self.get_payment_method_id_viviswap(payment_method_key).await?;
264
265        let coin_detail = self
266            .ensure_detail(address, payment_method_key, ViviswapDetailUpdateStrategy::Add)
267            .await?;
268        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
269
270        let access_token = self
271            .access_token
272            .as_ref()
273            .ok_or(crate::error::Error::MissingAccessToken)?;
274        let contract_response = set_viviswap_contract(
275            config,
276            access_token,
277            CryptoAmount::try_from(dec!(25.0))?, // any random amount works here!
278            iban_method_id,
279            Option::Some(iban_detail.id),
280            coin_method_id,
281            coin_detail.id,
282        )
283        .await?;
284
285        let new_contract = contract_response
286            .contract
287            .ok_or(crate::Error::Viviswap(ViviswapError::Api(String::from(
288                "Error creating the new contract for user.",
289            ))))?;
290        let bank_details = new_contract
291            .details
292            .ok_or(crate::Error::Viviswap(ViviswapError::Api(String::from(
293                "The new contract has invalid state. Deposit details are missing!",
294            ))))?;
295
296        match bank_details {
297            ViviswapApiContractDetails::BankAccount(account_details) => Ok(ViviswapDeposit {
298                contract_id: new_contract.id,
299                deposit_address: coin_detail.address,
300                details: ViviswapDepositDetails {
301                    reference: new_contract.reference,
302                    beneficiary: account_details.beneficiary,
303                    name_of_bank: account_details.name_of_bank,
304                    address_of_bank: account_details.address_of_bank,
305                    iban: account_details.address,
306                    bic: account_details.bic,
307                },
308            }),
309            _ => Err(crate::Error::Viviswap(ViviswapError::Api(String::from(
310                "The new contract has invalid state. Bank deposit details are missing!",
311            )))),
312        }
313    }
314
315    /// create detail for viviswap user
316    ///
317    /// # Returns
318    ///
319    /// - `Result<ViviswapAddressDetail>` - The created Viviswap address detail.
320    ///
321    /// # Errors
322    ///
323    /// - [`crate::Error::ConfigInitError`] - If there is an error initializing the configuration.
324    /// - [`crate::Error::ViviswapMissingUserError`] - If the viviswap user is missing.
325    // MARK5:create_detail_for_viviswap
326    pub async fn create_detail_for_viviswap(&mut self, pin: &EncryptionPin) -> Result<ViviswapAddressDetail> {
327        let network = self.active_network.clone().ok_or(crate::Error::MissingNetwork)?;
328        let currency = Currency::try_from(network.display_symbol)?;
329        let payment_method_key = currency.to_vivi_payment_method_key();
330
331        info!("Creating a payment detail for viviswap for {payment_method_key:?}");
332        // load user entity
333        let user = self.get_user().await?;
334
335        // check if user has already a viviswap state available
336        if user.viviswap_state.is_none() {
337            return Err(crate::Error::Viviswap(ViviswapError::MissingUser));
338        }
339        let address = self.generate_new_address(pin).await?;
340        // 1. check if address does already exist
341        let new_detail = self
342            .ensure_detail(address, payment_method_key, ViviswapDetailUpdateStrategy::Add)
343            .await?;
344
345        Ok(new_detail)
346    }
347
348    /// get payment methods and get the id for the given method key
349    ///
350    /// # Arguments
351    ///
352    /// * `payment_method_key` - The key of the payment method.
353    ///
354    /// # Returns
355    ///
356    /// - `Result<String>` - The ID of the payment method.
357    ///
358    /// # Errors
359    ///
360    /// - [`crate::Error::UserRepoNotInitialized`] - If there is an error initializing the repository.
361    /// - [`crate::Error::ViviswapMissingUserError`] - If the viviswap user is missing.
362    /// - [`crate::Error::ViviswapApi`] - If the payment method is not found.
363    // MARK6:get_payment_method_id_viviswap
364    async fn get_payment_method_id_viviswap(&mut self, payment_method_key: SwapPaymentDetailKey) -> Result<String> {
365        let mut user = self.get_user().await?;
366
367        // load repository
368        let Some(repo) = &mut self.repo else {
369            return Err(crate::Error::UserRepoNotInitialized);
370        };
371
372        // check if user has already a viviswap state available
373        let Some(mut viviswap_state) = user.viviswap_state else {
374            return Err(crate::Error::Viviswap(ViviswapError::MissingUser));
375        };
376
377        let payment_methods = match viviswap_state.payment_methods {
378            Some(details) => details,
379            None => {
380                let access_token = self
381                    .access_token
382                    .as_ref()
383                    .ok_or(crate::error::Error::MissingAccessToken)?;
384                let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
385                let payment_methods = get_viviswap_payment_method(config, access_token).await?;
386                viviswap_state.payment_methods = Some(payment_methods.clone());
387                user.viviswap_state = Some(viviswap_state.clone());
388                repo.update(&user)?;
389                payment_methods
390            }
391        };
392
393        let method_id = payment_methods
394            .methods
395            .iter()
396            .find(|&method| method.key == payment_method_key)
397            .map(|method| method.id.clone())
398            .ok_or_else(|| {
399                crate::Error::Viviswap(ViviswapError::Api(format!(
400                    "Payment method not found for key: {payment_method_key:?}"
401                )))
402            })?;
403
404        Ok(method_id)
405    }
406
407    /// create withdrawal for viviswap user
408    ///
409    /// # Arguments
410    ///
411    /// * `amount` - The amount of the withdrawal.
412    /// * `pin` - The optional PIN for verification.
413    /// * `tag` - The transactions tag. Optional.
414    /// * `data` - The associated data with the tag. Optional.
415    /// * `message` - The transactions message. Optional.
416    ///
417    /// # Returns
418    ///
419    /// - `Result<ViviswapWithdrawal>` - The created Viviswap withdrawal.
420    ///
421    /// # Errors
422    ///
423    /// - [`crate::Error::ViviswapMissingUserError`] - If the viviswap user is missing.
424    /// - [`crate::Error::ViviswapInvalidState`] - If the viviswap state is invalid.
425    /// - [`crate::Error::ViviswapApi`] - If there is an error with the Viviswap API.
426    // MARK7:create_withdrawal_with_viviswap
427    pub async fn create_withdrawal_with_viviswap(
428        &mut self,
429        amount: CryptoAmount,
430        pin: Option<&EncryptionPin>,
431        data: Option<Vec<u8>>,
432    ) -> Result<ViviswapWithdrawal> {
433        info!("Creating withdrawal with viviswap");
434        // load user entity
435        if let Some(pin) = pin {
436            self.verify_pin(pin).await?;
437        }
438        let user = self.get_user().await?;
439
440        // check if user has already a viviswap state available
441        let Some(viviswap_state) = user.viviswap_state else {
442            return Err(crate::Error::Viviswap(ViviswapError::MissingUser));
443        };
444
445        let network = self.active_network.clone().ok_or(crate::Error::MissingNetwork)?;
446        let currency = Currency::try_from(network.display_symbol)?;
447
448        // check if iban exists, otherwise error
449        let Some(iban_detail) = viviswap_state.current_iban else {
450            return Err(crate::Error::Viviswap(ViviswapError::InvalidState));
451        };
452
453        let iban_method_id = self.get_payment_method_id_viviswap(SwapPaymentDetailKey::Sepa).await?;
454
455        let coin_method_id = self
456            .get_payment_method_id_viviswap(currency.to_vivi_payment_method_key())
457            .await?;
458
459        let access_token = self
460            .access_token
461            .as_ref()
462            .ok_or(crate::error::Error::MissingAccessToken)?;
463        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
464
465        let contract_response = set_viviswap_contract(
466            config,
467            access_token,
468            amount,
469            coin_method_id,
470            Option::None,
471            iban_method_id,
472            iban_detail.id.clone(),
473        )
474        .await?;
475
476        let new_contract = contract_response
477            .contract
478            .ok_or(crate::Error::Viviswap(ViviswapError::Api(String::from(
479                "Error creating the new contract for user.",
480            ))))?;
481        let withdrawal_details =
482            new_contract
483                .details
484                .ok_or(crate::Error::Viviswap(ViviswapError::Api(String::from(
485                    "The new contract has invalid state. Withdrawal details are missing!",
486                ))))?;
487
488        match withdrawal_details {
489            ViviswapApiContractDetails::Crypto(crypto_details) => {
490                if let Some(pin) = pin {
491                    self.send_amount(pin, &crypto_details.deposit_address, amount, data)
492                        .await?;
493                }
494                Ok(ViviswapWithdrawal {
495                    contract_id: new_contract.id,
496                    deposit_address: iban_detail.address.clone(),
497                    details: ViviswapWithdrawalDetails {
498                        reference: new_contract.reference,
499                        wallet_id: crypto_details.wallet_id,
500                        crypto_address: crypto_details.deposit_address,
501                    },
502                })
503            }
504            _ => Err(crate::Error::Viviswap(ViviswapError::Api(String::from(
505                "The new contract has invalid state. Crypto deposit details are missing!",
506            )))),
507        }
508    }
509
510    /// Get the list of swaps for the viviswap user.
511    ///
512    /// # Returns
513    ///
514    /// Returns a `Result` containing a vector of `Swap` if successful, or a [`crate::Error`] if an error occurs.
515    ///
516    /// # Errors
517    ///
518    /// Returns an `Err` variant of [`crate::Error`] if any of the following conditions are met:
519    ///
520    /// * Repository initialization error.
521    /// * Viviswap API error.
522    // MARK8:get_swap_list
523    pub async fn get_swap_list(&self, start: u32, limit: u32) -> Result<OrderList> {
524        let Some(_user) = &self.active_user else {
525            return Err(crate::Error::UserRepoNotInitialized);
526        };
527        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
528        info!("get_swap_list request");
529
530        let access_token = self
531            .access_token
532            .as_ref()
533            .ok_or(crate::error::Error::MissingAccessToken)?;
534        let orders = get_viviswap_orders(config, access_token, start, limit).await?;
535        Ok(orders)
536    }
537
538    /// Get swap details
539    ///
540    /// # Arguments
541    ///
542    /// * `order_id` - The ID of the swap order.
543    ///
544    /// # Returns
545    ///
546    /// Returns a `Result` containing the swap order details or an error.
547    // MARK9:get_swap_details
548    pub async fn get_swap_details(&self, order_id: String) -> Result<Order> {
549        let Some(_user) = &self.active_user else {
550            return Err(crate::Error::UserRepoNotInitialized);
551        };
552        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
553        info!("get_swap_details request for order_id: {order_id}");
554        let access_token = self
555            .access_token
556            .as_ref()
557            .ok_or(crate::error::Error::MissingAccessToken)?;
558        match get_viviswap_order(config, access_token, &order_id).await {
559            Ok(order_detail) => Ok(order_detail),
560            Err(_) => Err(crate::Error::Viviswap(ViviswapError::Api(format!(
561                "Swap id:{order_id} not found"
562            )))),
563        }
564    }
565}
566
567#[cfg(test)]
568mod tests {
569    use super::*;
570    use crate::testing_utils::{
571        ADDRESS, AUTH_PROVIDER, ETH_NETWORK_KEY, HEADER_X_APP_NAME, IOTA_NETWORK_KEY, ORDER_ID, PIN, TOKEN, USERNAME,
572        example_api_network, example_api_networks, example_bank_details, example_contract_response,
573        example_crypto_details, example_get_payment_details_response, example_get_user, example_viviswap_oder_response,
574        set_config,
575    };
576    use crate::types::users::KycType;
577    use crate::{
578        core::Sdk,
579        types::users::ActiveUser,
580        user::MockUserRepo,
581        wallet_manager::{MockWalletManager, WalletBorrow},
582    };
583    use api_types::api::networks::ApiNetwork;
584    use api_types::api::{dlt::SetUserAddressRequest, viviswap::order::GetOrdersResponse};
585    use etopay_wallet::MockWalletUser;
586    use mockito::Matcher;
587    use rand::Rng;
588    use rstest::rstest;
589
590    /// Create an active user
591    fn get_active_user() -> ActiveUser {
592        ActiveUser {
593            username: USERNAME.into(),
594            wallet_manager: Box::new(MockWalletManager::new()),
595            mnemonic_derivation_options: Default::default(),
596        }
597    }
598
599    #[tokio::test]
600    async fn test_err_get_swap_list_should_consume_only_authenticated_requests() {
601        // Arrange
602        let (_srv, config, _cleanup) = set_config().await;
603
604        let mut sdk = Sdk::new(config).unwrap();
605        sdk.repo = Some(Box::new(MockUserRepo::new()));
606        sdk.access_token = Some(TOKEN.clone());
607        sdk.active_user = None;
608
609        // Call the function you want to test
610        let result = sdk.get_swap_list(1, 2).await;
611
612        assert!(result.is_err());
613    }
614
615    #[tokio::test]
616    async fn test_err_get_swap_details_should_consume_only_authenticated_requests() {
617        // Arrange
618        let (_srv, config, _cleanup) = set_config().await;
619
620        let mut sdk = Sdk::new(config).unwrap();
621        sdk.repo = Some(Box::new(MockUserRepo::new()));
622        sdk.access_token = Some(TOKEN.clone());
623        sdk.active_user = None;
624
625        // Call the function you want to test
626        let result = sdk.get_swap_details(String::from(ORDER_ID)).await;
627
628        assert!(result.is_err());
629    }
630
631    #[tokio::test]
632    async fn test_ok_get_swap_details() {
633        // Arrange
634        let (mut srv, config, _cleanup) = set_config().await;
635        let mut sdk = Sdk::new(config).unwrap();
636        sdk.repo = Some(Box::new(MockUserRepo::new()));
637        sdk.access_token = Some(TOKEN.clone());
638        sdk.active_user = Some(get_active_user());
639
640        let mock_response = example_viviswap_oder_response();
641        let body = serde_json::to_string(&mock_response).unwrap();
642
643        let mock_server = srv
644            .mock("GET", format!("/api/viviswap/orders?id={}", ORDER_ID).as_str())
645            .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
646            .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
647            .with_status(200)
648            .with_body(&body)
649            .with_header("content-type", "application/json")
650            .with_body(&body)
651            .create();
652
653        // Call the function you want to test
654        let result = sdk.get_swap_details(String::from(ORDER_ID)).await;
655
656        // Assert
657        assert_eq!(result.unwrap(), example_viviswap_oder_response());
658        mock_server.assert();
659    }
660
661    #[tokio::test]
662    async fn test_ok_get_swap_list() {
663        // Arrange
664        let (mut srv, config, _cleanup) = set_config().await;
665        let mut sdk = Sdk::new(config).unwrap();
666        sdk.repo = Some(Box::new(MockUserRepo::new()));
667        sdk.access_token = Some(TOKEN.clone());
668        sdk.active_user = Some(get_active_user());
669
670        let mock_order = example_viviswap_oder_response();
671        let mock_response = GetOrdersResponse {
672            count: 1,
673            start: 2,
674            limit: 3,
675            orders: vec![mock_order],
676        };
677        let body = serde_json::to_string(&mock_response).unwrap();
678
679        let mock_server = srv
680            .mock("GET", "/api/viviswap/orders?start=2&limit=1")
681            .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
682            .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
683            .with_status(200)
684            .with_body(&body)
685            .with_header("content-type", "application/json")
686            .with_body(&body)
687            .create();
688
689        // Call the function you want to test
690        let result = sdk.get_swap_list(2, 1).await;
691
692        // Assert
693        let result = result.unwrap();
694        assert_eq!(result.orders[0].contract_id, mock_response.orders[0].contract_id);
695        assert_eq!(result.orders[0].crypto_fees, mock_response.orders[0].crypto_fees);
696        assert_eq!(
697            result.orders[0].fees_amount_eur,
698            mock_response.orders[0].fees_amount_eur
699        );
700        mock_server.assert();
701    }
702
703    #[tokio::test]
704    async fn test_err_get_swap_list_server_error() {
705        // Arrange
706        let (mut srv, config, _cleanup) = set_config().await;
707        let mut sdk = Sdk::new(config).unwrap();
708        sdk.repo = Some(Box::new(MockUserRepo::new()));
709        sdk.access_token = Some(TOKEN.clone());
710        sdk.active_user = Some(get_active_user());
711
712        let mock_response = GetOrdersResponse {
713            count: 1,
714            start: 2,
715            limit: 3,
716            orders: vec![],
717        };
718        let body = serde_json::to_string(&mock_response).unwrap();
719
720        let server_error_status = rand::rng().random_range(400..410);
721        let mock_server = srv
722            .mock("GET", "/api/viviswap/orders?start=2&limit=1")
723            .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
724            .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
725            .with_status(server_error_status)
726            .with_body(&body)
727            .with_header("content-type", "application/json")
728            .with_body(&body)
729            .create();
730
731        // Call the function you want to test
732        let result = sdk.get_swap_list(2, 1).await;
733
734        assert!(result.is_err());
735        mock_server.assert();
736    }
737
738    #[tokio::test]
739    async fn test_err_get_swap_details_server_error() {
740        // Arrange
741        let (mut srv, config, _cleanup) = set_config().await;
742        let mut sdk = Sdk::new(config).unwrap();
743        sdk.repo = Some(Box::new(MockUserRepo::new()));
744        sdk.access_token = Some(TOKEN.clone());
745        sdk.active_user = Some(get_active_user());
746
747        let server_error_status = rand::rng().random_range(400..410);
748        let mock_server = srv
749            .mock("GET", format!("/api/viviswap/orders?id={}", ORDER_ID).as_str())
750            .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
751            .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
752            .with_status(server_error_status)
753            .with_header("content-type", "application/json")
754            .create();
755
756        // Call the function you want to test
757        let result = sdk.get_swap_details(String::from(ORDER_ID)).await;
758
759        // Assert
760        assert!(result.is_err());
761        mock_server.assert();
762    }
763
764    #[rstest]
765    #[case(
766        example_api_network(IOTA_NETWORK_KEY.to_string()),
767        SwapPaymentDetailKey::Iota,
768        "/api/viviswap/details?payment_method_key=IOTA"
769    )]
770    #[case(
771        example_api_network(ETH_NETWORK_KEY.to_string()),
772        SwapPaymentDetailKey::Eth,
773        "/api/viviswap/details?payment_method_key=ETH"
774    )]
775    #[tokio::test]
776    async fn it_should_create_viviswap_deposit(
777        #[case] network: ApiNetwork,                      // Parametrized network
778        #[case] payment_detail_key: SwapPaymentDetailKey, // Payment detail key (Iota, Eth, etc.)
779        #[case] payment_method_path: &str,                // The payment method query path
780    ) {
781        // Arrange
782        let (mut srv, config, _cleanup) = set_config().await;
783        let mut sdk = Sdk::new(config).unwrap();
784        sdk.access_token = Some(TOKEN.clone());
785
786        sdk.set_networks(example_api_networks());
787        sdk.set_network(network.key.clone()).await.unwrap(); // Set parametrized network
788        sdk.refresh_access_token(Some(TOKEN.clone())).await.unwrap();
789
790        let mock_user_repo = example_get_user(payment_detail_key, false, 5, KycType::Viviswap);
791        sdk.repo = Some(Box::new(mock_user_repo));
792
793        let mut mock_wallet_manager = MockWalletManager::new();
794        mock_wallet_manager.expect_try_get().returning({
795            move |_, _, _, _, _, _| {
796                let mut mock_wallet = MockWalletUser::new();
797                mock_wallet
798                    .expect_get_address()
799                    .once()
800                    .returning(move || Ok(ADDRESS.to_string()));
801                Ok(WalletBorrow::from(mock_wallet))
802            }
803        });
804        sdk.active_user = Some(ActiveUser {
805            username: USERNAME.into(),
806            wallet_manager: Box::new(mock_wallet_manager),
807            mnemonic_derivation_options: Default::default(),
808        });
809
810        // create viviswap contract
811        let contract_mock_response = example_contract_response(example_bank_details());
812        let body = serde_json::to_string(&contract_mock_response).unwrap();
813        let create_viviswap_contract = srv
814            .mock("POST", "/api/viviswap/contracts")
815            .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
816            .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
817            .with_status(200)
818            .with_body(&body)
819            .with_header("content-type", "application/json")
820            .with_body(&body)
821            .create();
822
823        // Get user payment details
824        let payment_details_mock_response = example_get_payment_details_response();
825        let body = serde_json::to_string(&payment_details_mock_response).unwrap();
826        let get_payment_details = srv
827            .mock("GET", payment_method_path) // Use the parametrized payment method path
828            .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
829            .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
830            .with_status(200)
831            .with_body(&body)
832            .with_header("content-type", "application/json")
833            .with_body(&body)
834            .create();
835
836        let mock_request = SetUserAddressRequest {
837            address: ADDRESS.to_string(),
838        };
839        let body = serde_json::to_string(&mock_request).unwrap();
840
841        let put_user_address = srv
842            .mock("PUT", "/api/user/address")
843            .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
844            .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
845            .match_query(Matcher::Exact(format!("network_key={}", network.key)))
846            .match_body(Matcher::Exact(body))
847            .with_status(201)
848            .expect(1)
849            .with_header("content-type", "application/json")
850            .create();
851
852        // Call the function you want to test
853        let _ = sdk.create_deposit_with_viviswap(&PIN).await.unwrap();
854
855        // Assert
856        put_user_address.assert();
857        get_payment_details.assert();
858        create_viviswap_contract.assert();
859    }
860
861    #[tokio::test]
862    async fn it_should_create_withdrawal_deposit_for_iota_and_smr() {
863        // Arrange
864        let (mut srv, config, _cleanup) = set_config().await;
865
866        let mut sdk = Sdk::new(config).unwrap();
867        sdk.set_networks(example_api_networks());
868        sdk.set_network(IOTA_NETWORK_KEY.to_string()).await.unwrap();
869
870        let mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 3, KycType::Viviswap);
871        sdk.repo = Some(Box::new(mock_user_repo));
872
873        sdk.access_token = Some(TOKEN.clone());
874        sdk.active_user = Some(get_active_user());
875
876        // create viviswap contract
877        let contract_mock_response = example_contract_response(example_crypto_details());
878        let body = serde_json::to_string(&contract_mock_response).unwrap();
879        let create_viviswap_contract = srv
880            .mock("POST", "/api/viviswap/contracts")
881            .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
882            .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
883            .with_status(200)
884            .with_body(&body)
885            .with_header("content-type", "application/json")
886            .with_body(&body)
887            .create();
888
889        // Call the function you want to test
890        let result = sdk
891            .create_withdrawal_with_viviswap(dec!(50.0).try_into().unwrap(), None, Some(Vec::from([8, 16])))
892            .await;
893
894        // Assert
895        result.unwrap();
896        create_viviswap_contract.assert();
897    }
898}