etopay_sdk/core/viviswap/
kyc.rs

1use crate::backend;
2use crate::types::viviswap::{
3    ViviswapKycStatus, ViviswapPartiallyKycDetails, ViviswapState, ViviswapVerificationStatus, ViviswapVerificationStep,
4};
5use crate::{
6    backend::viviswap::{
7        create_viviswap_user, get_viviswap_kyc_status, set_viviswap_kyc_general_details,
8        set_viviswap_kyc_personal_details,
9    },
10    types::viviswap::NewViviswapUser,
11};
12use crate::{
13    core::{Sdk, viviswap::ViviswapError},
14    error::Result,
15};
16use api_types::api::viviswap::kyc::{
17    File, IdentityOfficialDocumentData, IdentityPersonalDocumentData, KycAmlaQuestion, KycOpenDocument, KycStep,
18};
19use chrono::{NaiveDate, Utc};
20use log::*;
21
22impl Sdk {
23    /// Create new viviswap user and initialize kyc verification
24    ///
25    /// # Arguments
26    ///
27    /// * `mail` - The email address of the user.
28    /// * `terms_accepted` - A boolean indicating whether the terms have been accepted.
29    ///
30    /// # Returns
31    ///
32    /// Returns a `Result` containing a `NewViviswapUser` if successful, or a [`crate::Error`] if an error occurs.
33    ///
34    /// # Errors
35    ///
36    /// Returns an `Err` variant of [`crate::Error`] if any of the following conditions are met:
37    ///
38    /// * Repository initialization error.
39    /// * User already exists.
40    /// * Viviswap API error.
41    /// * User status update error.
42    pub async fn start_kyc_verification_for_viviswap(
43        &mut self,
44        mail: &str,
45        terms_accepted: bool,
46    ) -> Result<NewViviswapUser> {
47        info!("Starting KYC verification with viviswap");
48        // load user entity
49        let mut user = self.get_user().await?;
50
51        // load repository
52        let Some(repo) = &mut self.repo else {
53            return Err(crate::Error::UserRepoNotInitialized);
54        };
55
56        let access_token = self
57            .access_token
58            .as_ref()
59            .ok_or(crate::error::Error::MissingAccessToken)?;
60        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
61
62        // check if user has already a viviswap state available
63        if user.viviswap_state.is_some() {
64            return Err(crate::Error::Viviswap(ViviswapError::UserStateExisting));
65        };
66
67        let new_viviswap_user = create_viviswap_user(config, access_token, mail, terms_accepted).await?;
68
69        user.viviswap_state = Some(ViviswapState::new());
70
71        repo.update(&user)?;
72
73        Ok(NewViviswapUser {
74            username: new_viviswap_user.username,
75        })
76    }
77
78    /// Get current kyc status of viviswap
79    ///
80    /// # Returns
81    ///
82    /// Returns a `Result` containing a `ViviswapKycStatus` if successful, or a [`crate::Error`] if an error occurs.
83    ///
84    /// # Errors
85    ///
86    /// Returns an `Err` variant of [`crate::Error`] if any of the following conditions are met:
87    ///
88    /// * Repository initialization error.
89    /// * Viviswap API error.
90    pub async fn get_kyc_details_for_viviswap(&mut self) -> Result<ViviswapKycStatus> {
91        info!("Getting KYC details for viviswap user");
92        // load user entity
93        let user = self.get_user().await?;
94        let access_token = self
95            .access_token
96            .as_ref()
97            .ok_or(crate::error::Error::MissingAccessToken)?;
98
99        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
100
101        let kyc_status = get_viviswap_kyc_status(config, access_token).await?;
102        debug!("KYC Status response: {kyc_status:#?}");
103
104        let username = user.username;
105        let verification_status: ViviswapVerificationStatus = kyc_status.verification_status.into();
106
107        let monthly_limit_eur = kyc_status.monthly_limit_eur;
108        let next_verification_step = match kyc_status.verified_step {
109            KycStep::Undefined => ViviswapVerificationStep::General,
110            KycStep::General => ViviswapVerificationStep::Personal,
111            KycStep::Personal => ViviswapVerificationStep::Residence,
112            KycStep::Residence => ViviswapVerificationStep::Identity,
113            KycStep::Identity => ViviswapVerificationStep::Amla,
114            KycStep::Amla => ViviswapVerificationStep::Documents,
115            KycStep::Document => ViviswapVerificationStep::Undefined,
116            _ => ViviswapVerificationStep::Undefined,
117        };
118
119        // update state internally
120        if let Some(repo) = &mut self.repo {
121            repo.set_viviswap_kyc_state(
122                &username,
123                verification_status.clone(),
124                monthly_limit_eur,
125                next_verification_step,
126            )?;
127        } else {
128            return Err(crate::Error::UserRepoNotInitialized);
129        };
130
131        Ok(ViviswapKycStatus {
132            full_name: kyc_status.full_name,
133            monthly_limit_eur,
134            verified_step: kyc_status.verified_step.into(),
135            submission_step: kyc_status.submission_step.into(),
136            verification_status,
137        })
138    }
139
140    /// Submit the previously entered partial kyc details for viviswap.
141    ///
142    /// # Errors
143    ///
144    /// Returns a vector of [`crate::Error`] if any of the following conditions are met:
145    ///
146    /// - Repository initialization error.
147    /// - Viviswap missing user error.
148    /// - Viviswap invalid state error.
149    /// - Viviswap missing field error.
150    /// - Viviswap API error.
151    ///
152    /// # Returns
153    ///
154    /// Returns `Ok(())` if the submission is successful.
155    pub async fn submit_kyc_partially_status_for_viviswap(&mut self) -> Result<()> {
156        info!("Submitting partial KYC status for viviswap");
157
158        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
159
160        // load user entity
161        let mut user = self.get_user().await?;
162
163        // ensure that the repository exist (cannot borrow as mutable here since we also borrow self as mutable in between)
164        if self.repo.is_none() {
165            return Err(crate::Error::UserRepoNotInitialized);
166        }
167
168        // check if user has already a viviswap state available
169        let Some(viviswap_state) = &mut user.viviswap_state else {
170            return Err(crate::Error::Viviswap(ViviswapError::MissingUser));
171        };
172
173        // check if user is in valid state
174        if viviswap_state.verification_status != ViviswapVerificationStatus::Unverified {
175            return Err(crate::Error::Viviswap(ViviswapError::InvalidState));
176        };
177
178        // check if all required fields are available
179        let mut missing_field_errors = Vec::new();
180        if viviswap_state.partial_kyc_details_input.is_individual.is_none() {
181            missing_field_errors.push(crate::Error::Viviswap(ViviswapError::MissingField {
182                field: "is_individual".to_string(),
183            }));
184        }
185        if viviswap_state.partial_kyc_details_input.is_pep.is_none() {
186            missing_field_errors.push(crate::Error::Viviswap(ViviswapError::MissingField {
187                field: "is_pep".to_string(),
188            }));
189        }
190        if viviswap_state.partial_kyc_details_input.is_us_citizen.is_none() {
191            missing_field_errors.push(crate::Error::Viviswap(ViviswapError::MissingField {
192                field: "is_us_citizen".to_string(),
193            }));
194        }
195        if viviswap_state
196            .partial_kyc_details_input
197            .is_regulatory_disclosure
198            .is_none()
199        {
200            missing_field_errors.push(crate::Error::Viviswap(ViviswapError::MissingField {
201                field: "is_regulatory_disclosure".to_string(),
202            }));
203        }
204        if viviswap_state.partial_kyc_details_input.country_of_residence.is_none() {
205            missing_field_errors.push(crate::Error::Viviswap(ViviswapError::MissingField {
206                field: "country_of_residence".to_string(),
207            }));
208        }
209        if viviswap_state.partial_kyc_details_input.nationality.is_none() {
210            missing_field_errors.push(crate::Error::Viviswap(ViviswapError::MissingField {
211                field: "nationality".to_string(),
212            }));
213        }
214        if viviswap_state.partial_kyc_details_input.full_name.is_none() {
215            missing_field_errors.push(crate::Error::Viviswap(ViviswapError::MissingField {
216                field: "full_name".to_string(),
217            }));
218        }
219        if viviswap_state.partial_kyc_details_input.date_of_birth.is_none() {
220            missing_field_errors.push(crate::Error::Viviswap(ViviswapError::MissingField {
221                field: "date_of_birth".to_string(),
222            }));
223        }
224
225        // check if a missing field error exists and return if so
226        if !missing_field_errors.is_empty() {
227            return Err(crate::Error::Viviswap(ViviswapError::Aggregate(missing_field_errors)));
228        }
229
230        // destructure the contents of the struct
231        let ViviswapPartiallyKycDetails {
232            is_individual: Some(is_individual),
233            is_pep: Some(is_pep),
234            is_us_citizen: Some(is_us_citizen),
235            is_regulatory_disclosure: Some(is_regulatory_disclosure),
236            country_of_residence: Some(country_of_residence),
237            nationality: Some(nationality),
238            full_name: Some(full_name),
239            date_of_birth: Some(date_of_birth),
240        } = &viviswap_state.partial_kyc_details_input
241        else {
242            // SAFETY: we have already checked that all are not None, and if any are we have returned an error
243            unreachable!()
244        };
245
246        let access_token = self
247            .access_token
248            .as_ref()
249            .ok_or_else(|| crate::error::Error::MissingAccessToken)?;
250
251        // submit viviswap general kyc details
252        if viviswap_state.next_verification_step == ViviswapVerificationStep::General {
253            set_viviswap_kyc_general_details(
254                config,
255                access_token,
256                *is_individual,
257                *is_pep,
258                *is_us_citizen,
259                *is_regulatory_disclosure,
260                country_of_residence,
261                nationality,
262            )
263            .await?;
264        }
265
266        // submit viviswap personal kyc details
267        set_viviswap_kyc_personal_details(config, access_token, full_name, date_of_birth).await?;
268
269        // get new verification status of viviswap user
270        self.get_kyc_details_for_viviswap().await?;
271
272        // update users state
273        let Some(repo) = &mut self.repo else {
274            return Err(crate::Error::UserRepoNotInitialized);
275        };
276
277        user.viviswap_state = Some(viviswap_state.clone());
278        repo.update(&user)?;
279
280        Ok(())
281    }
282
283    /// Update the kyc details for viviswap to be submitted
284    ///
285    /// # Arguments
286    ///
287    /// * `is_individual` - Whether the user is an individual.
288    /// * `is_pep` - Whether the user is a politically exposed person.
289    /// * `is_us_citizen` - Whether the user is a US citizen.
290    /// * `is_regulatory_disclosure` - Whether the user has accepted the regulatory disclosure.
291    /// * `country_of_residence` - The country of residence of the user.
292    /// * `nationality` - The nationality of the user.
293    /// * `full_name` - The full name of the user.
294    /// * `date_of_birth` - The date of birth of the user.
295    ///
296    /// # Returns
297    ///
298    /// Returns a `Result` containing the partially updated KYC details or a vector of errors.
299    ///
300    /// # Errors
301    ///
302    /// Returns a vector of errors if any validation errors occur during the update process.
303    #[allow(clippy::too_many_arguments)]
304    pub async fn update_kyc_partially_status_for_viviswap(
305        &mut self,
306        is_individual: Option<bool>,
307        is_pep: Option<bool>,
308        is_us_citizen: Option<bool>,
309        is_regulatory_disclosure: Option<bool>,
310        country_of_residence: Option<String>,
311        nationality: Option<String>,
312        full_name: Option<String>,
313        date_of_birth: Option<String>,
314    ) -> Result<ViviswapPartiallyKycDetails> {
315        info!("Updating partial KYC status of user in viviswap");
316
317        // load user entity
318        let mut user = self.get_user().await?;
319
320        // load repository
321        let Some(repo) = &mut self.repo else {
322            return Err(crate::Error::UserRepoNotInitialized);
323        };
324
325        // check if user has already a viviswap state available
326        let Some(viviswap_state) = &mut user.viviswap_state else {
327            return Err(crate::Error::Viviswap(ViviswapError::MissingUser));
328        };
329
330        // check if user is in valid state
331        if viviswap_state.verification_status != ViviswapVerificationStatus::Unverified {
332            return Err(crate::Error::Viviswap(ViviswapError::InvalidState));
333        };
334
335        // run validators on each field and set if valid
336        let mut field_validation_errors = Vec::new();
337        let mut partial_kyc_details_input = viviswap_state.partial_kyc_details_input.clone();
338
339        if is_individual.is_some() {
340            partial_kyc_details_input.is_individual = is_individual;
341        }
342        if is_pep.is_some() {
343            if is_pep != Some(false) {
344                field_validation_errors.push(crate::Error::Viviswap(ViviswapError::Validation(
345                    String::from("is_pep"),
346                    String::from("You are not allowed to be a pep!"),
347                )));
348            } else {
349                partial_kyc_details_input.is_pep = is_pep;
350            }
351        }
352        if is_us_citizen.is_some() {
353            if is_us_citizen != Some(false) {
354                field_validation_errors.push(crate::Error::Viviswap(ViviswapError::Validation(
355                    String::from("is_us_citizen"),
356                    String::from("You are not allowed to be a us citizen!"),
357                )));
358            } else {
359                partial_kyc_details_input.is_us_citizen = is_us_citizen;
360            }
361        }
362        if is_regulatory_disclosure.is_some() {
363            if is_regulatory_disclosure != Some(true) {
364                field_validation_errors.push(crate::Error::Viviswap(ViviswapError::Validation(
365                    String::from("is_regulatory_disclosure"),
366                    String::from("You must accept the regulatory disclosure!"),
367                )));
368            } else {
369                partial_kyc_details_input.is_regulatory_disclosure = is_regulatory_disclosure;
370            }
371        }
372        if let Some(country_of_residence_val) = country_of_residence {
373            if country_of_residence_val.len() != 2
374                || !vec![
375                    "AW", "AO", "AI", "AX", "AD", "AR", "AM", "AS", "AQ", "TF", "AG", "AU", "AZ", "BI", "BJ", "BS",
376                    "BA", "BL", "BY", "BZ", "BM", "BO", "BN", "BT", "BV", "BW", "CF", "CC", "CL", "CN", "CI", "CM",
377                    "CD", "CK", "CO", "KM", "CV", "CR", "CU", "CW", "CX", "DJ", "DM", "DO", "EC", "ER", "EH", "ET",
378                    "FJ", "FK", "FO", "FM", "GA", "GE", "GH", "GN", "GP", "GM", "GW", "GQ", "GD", "GL", "GT", "GF",
379                    "GU", "GY", "HK", "HM", "HN", "IN", "IO", "IS", "IL", "JP", "KE", "KG", "KI", "KN", "KR", "XK",
380                    "LA", "LR", "LC", "LS", "MO", "MF", "MC", "MD", "MG", "MV", "MX", "MH", "MK", "MN", "MP", "MS",
381                    "MQ", "MU", "MW", "YT", "NA", "NC", "NE", "NF", "NU", "NP", "NR", "NZ", "PN", "PE", "PW", "PG",
382                    "PR", "PY", "PF", "RE", "RU", "RW", "SG", "GS", "SJ", "SB", "SL", "SV", "SM", "PM", "RS", "ST",
383                    "SR", "SZ", "SX", "SC", "TC", "TD", "TG", "TH", "TJ", "TK", "TM", "TL", "TO", "TV", "TW", "UA",
384                    "UM", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN", "WF", "WS", "ZA", "ZM", "AT", "BE", "BG",
385                    "BR", "CA", "HR", "CY", "CZ", "DK", "DE", "EE", "FI", "FR", "GR", "GG", "IE", "IM", "IT", "JE",
386                    "KZ", "LV", "LI", "LT", "LU", "ME", "MT", "NL", "NO", "PL", "PT", "RO", "SK", "SI", "ES", "SE",
387                    "CH", "GB", "HU",
388                ]
389                .contains(&country_of_residence_val.to_uppercase().as_str())
390            {
391                field_validation_errors.push(crate::Error::Viviswap(ViviswapError::Validation(
392                    String::from("country_of_residence"),
393                    String::from("The country of residence is not valid or your country is not allowed as residence!"),
394                )));
395            } else {
396                partial_kyc_details_input.country_of_residence = Some(country_of_residence_val);
397            }
398        }
399        if let Some(nationality_val) = nationality {
400            if nationality_val.len() != 2
401                || !vec![
402                    "BD", "DZ", "EG", "ID", "IQ", "KW", "LB", "LY", "LK", "MR", "MY", "NG", "OM", "PS", "QA", "SD",
403                    "SA", "TN", "AW", "AO", "AI", "AX", "AD", "AR", "AM", "AS", "AQ", "TF", "AG", "AU", "AZ", "BI",
404                    "BJ", "BS", "BA", "BL", "BY", "BZ", "BM", "BO", "BN", "BT", "BV", "BW", "CF", "CC", "CL", "CN",
405                    "CI", "CM", "CD", "CK", "CO", "KM", "CV", "CR", "CU", "CW", "CX", "DJ", "DM", "DO", "EC", "ER",
406                    "EH", "ET", "FJ", "FK", "FO", "FM", "GA", "GE", "GH", "GN", "GP", "GM", "GW", "GQ", "GD", "GL",
407                    "GT", "GF", "GU", "GY", "HK", "HM", "HN", "IN", "IO", "IS", "IL", "JP", "KE", "KG", "KI", "KN",
408                    "KR", "XK", "LA", "LR", "LC", "LS", "MO", "MF", "MC", "MD", "MG", "MV", "MX", "MH", "MK", "MN",
409                    "MP", "MS", "MQ", "MU", "MW", "YT", "NA", "NC", "NE", "NF", "NU", "NP", "NR", "NZ", "PN", "PE",
410                    "PW", "PG", "PR", "PY", "PF", "RE", "RU", "RW", "SG", "GS", "SJ", "SB", "SL", "SV", "SM", "PM",
411                    "RS", "ST", "SR", "SZ", "SX", "SC", "TC", "TD", "TG", "TH", "TJ", "TK", "TM", "TL", "TO", "TV",
412                    "TW", "UA", "UM", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN", "WF", "WS", "ZA", "ZM", "AT",
413                    "BE", "BG", "BR", "CA", "HR", "CY", "CZ", "DK", "DE", "EE", "FI", "FR", "GR", "GG", "IE", "IM",
414                    "IT", "JE", "KZ", "LV", "LI", "LT", "LU", "ME", "MT", "NL", "NO", "PL", "PT", "RO", "SK", "SI",
415                    "ES", "SE", "CH", "GB", "HU",
416                ]
417                .contains(&nationality_val.to_uppercase().as_str())
418            {
419                field_validation_errors.push(crate::Error::Viviswap(ViviswapError::Validation(
420                    String::from("nationality"),
421                    String::from("The nationality is not valid or your country is not allowed as nationality!"),
422                )));
423            } else {
424                partial_kyc_details_input.nationality = Some(nationality_val);
425            }
426        }
427
428        if let Some(full_name_val) = full_name {
429            if full_name_val.len() < 2 || full_name_val.len() > 128 {
430                field_validation_errors.push(crate::Error::Viviswap(ViviswapError::Validation(
431                    String::from("full_name"),
432                    String::from("The full name is not valid! Must be between 2 and 128 characters."),
433                )));
434            } else {
435                partial_kyc_details_input.full_name = Some(full_name_val);
436            }
437        }
438
439        if let Some(date_of_birth_val) = date_of_birth {
440            let min_birth_date = Utc::now().date_naive() - chrono::Duration::days(18 * 365); // computes the minimum birth date allowed
441            match NaiveDate::parse_from_str(date_of_birth_val.clone().as_str(), "%Y-%m-%d") {
442                Ok(birth_date) => {
443                    if birth_date <= min_birth_date {
444                        partial_kyc_details_input.date_of_birth = Some(date_of_birth_val);
445                    } else {
446                        field_validation_errors.push(crate::Error::Viviswap(ViviswapError::Validation(
447                            String::from("date_of_birth"),
448                            String::from("The date of birth is not valid! Must be older than 18 years."),
449                        )));
450                    }
451                }
452                Err(_) => {
453                    field_validation_errors.push(crate::Error::Viviswap(ViviswapError::Validation(
454                        String::from("date_of_birth"),
455                        String::from("The date of birth is not valid! Must have format YYYY-MM-DD."),
456                    )));
457                }
458            }
459        }
460
461        // check if a missing field error exists and return if so
462        if !field_validation_errors.is_empty() {
463            return Err(crate::Error::Viviswap(ViviswapError::Aggregate(
464                field_validation_errors,
465            )));
466        }
467
468        viviswap_state.partial_kyc_details_input = partial_kyc_details_input.clone();
469        repo.update(&user)?;
470
471        Ok(partial_kyc_details_input)
472    }
473
474    /// Set KYC identity details
475    ///
476    /// # Arguments
477    ///
478    /// - `official_document` - The official document that verifies the person (eg. ID-Card, Passport, Drivers License …).
479    /// - `personal_document` - A 30 second video document that verifies that this person is willing to verify at viviswap and that the person really is the one they claim to be.
480    ///
481    /// # Errors
482    ///
483    /// - [[`crate::Error::UserNotInitialized)`]] - If the user is not initialized.
484    /// - [[`crate::Error::ViviswapApiError`]] - If there is an error in the viviswap API.
485    pub async fn set_viviswap_kyc_identity_details(
486        &self,
487        official_document: IdentityOfficialDocumentData,
488        personal_document: IdentityPersonalDocumentData,
489    ) -> Result<()> {
490        let Some(_user) = &self.active_user else {
491            return Err(crate::Error::UserNotInitialized);
492        };
493        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
494
495        let access_token = self
496            .access_token
497            .as_ref()
498            .ok_or(crate::error::Error::MissingAccessToken)?;
499        backend::viviswap::set_viviswap_kyc_identity_details(
500            config,
501            access_token,
502            official_document,
503            personal_document,
504        )
505        .await?;
506        Ok(())
507    }
508
509    /// Set KYC residence details
510    ///
511    /// # Arguments
512    ///
513    /// - `country_code`, `region`, `zip_code`, `city`, `address_line_1`, `address_line_2` - basic address data.
514    /// - `is_public_entry` - Inidcates that a valid public entry of this clients address can be found.
515    /// - `public_entry_reference` - if `is_public_entry` is `true`, then this must contain the resource link.
516    /// - `has_no_official_document` - indicates if the client does not have any document verifying their address.
517    /// - `document_residence_proof` - if `has_no_official_document` is `false`, then this must contain the document file
518    ///   that verifies that this person is currently living at the address submitted.
519    ///
520    ///
521    /// # Errors
522    ///
523    /// - [[`crate::Error::UserNotInitialized)`]] - If the user is not initialized.
524    /// - [[`crate::Error::ViviswapValidation`]] - If the input values are not valid.
525    /// - [[`crate::Error::ViviswapApiError`]] - If there is an error in the viviswap API.
526    #[allow(clippy::too_many_arguments)]
527    pub async fn set_viviswap_kyc_residence_details(
528        &self,
529        country_code: String,
530        region: String,
531        zip_code: String,
532        city: String,
533        address_line_1: String,
534        address_line_2: String,
535        is_public_entry: bool,
536        public_entry_reference: Option<String>,
537        has_no_official_document: bool,
538        document_residence_proof: Option<File>,
539    ) -> Result<()> {
540        let Some(_user) = &self.active_user else {
541            return Err(crate::Error::UserNotInitialized);
542        };
543        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
544
545        let mut field_validation_errors = Vec::new();
546        // do some validation checks
547        if is_public_entry && public_entry_reference.is_none() {
548            field_validation_errors.push(crate::Error::Viviswap(ViviswapError::Validation(
549                String::from("public_entry_reference"),
550                String::from("if is_public_entry is true, public_entry_reference must not be _None_"),
551            )));
552        }
553
554        if !has_no_official_document && document_residence_proof.is_none() {
555            field_validation_errors.push(crate::Error::Viviswap(ViviswapError::Validation(
556                String::from("document_residence_proof"),
557                String::from("if has_no_official_document is false, document_residence_proof must not be _None_"),
558            )));
559        }
560
561        // check if a missing field error exists and return if so
562        if !field_validation_errors.is_empty() {
563            return Err(crate::Error::Viviswap(ViviswapError::Aggregate(
564                field_validation_errors,
565            )));
566        }
567
568        let access_token = self
569            .access_token
570            .as_ref()
571            .ok_or_else(|| crate::error::Error::MissingAccessToken)?;
572        backend::viviswap::set_viviswap_kyc_residence_details(
573            config,
574            access_token,
575            api_types::api::viviswap::kyc::SetResidenceDataRequest {
576                country_code,
577                region,
578                zip_code,
579                city,
580                address_line_1,
581                address_line_2,
582                is_public_entry,
583                public_entry_reference,
584                has_no_official_document,
585                document_residence_proof,
586            },
587        )
588        .await?;
589        Ok(())
590    }
591
592    /// Get the open AMLA KYC questions
593    ///
594    /// # Arguments
595    ///
596    /// None
597    ///
598    /// # Returns
599    ///
600    /// - A list of the currently open AMLA questions.
601    ///
602    /// # Errors
603    ///
604    /// - [[`crate::Error::UserNotInitialized)`]] - If the user is not initialized.
605    /// - [[`crate::Error::ViviswapApiError`]] - If there is an error in the viviswap API.
606    pub async fn get_viviswap_kyc_amla_open_questions(&self) -> Result<Vec<KycAmlaQuestion>> {
607        let Some(_user) = &self.active_user else {
608            return Err(crate::Error::UserNotInitialized);
609        };
610        let access_token = self
611            .access_token
612            .as_ref()
613            .ok_or(crate::error::Error::MissingAccessToken)?;
614        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
615
616        let amla_questions = backend::viviswap::get_viviswap_kyc_amla_open_questions(config, access_token)
617            .await
618            .map(|v| v.questions)?;
619        Ok(amla_questions)
620    }
621
622    /// Set the answer to an open AMLA KYC question
623    ///
624    /// # Arguments
625    ///
626    /// - `question_id` - The ID of the question to set the answer to.
627    /// - `answers` - a list of the selected available answers for the question.
628    /// - `freetext_answer` - an optional free-text answer.
629    ///
630    /// # Errors
631    ///
632    /// - [[`crate::Error::UserNotInitialized)`]] - If the user is not initialized.
633    /// - [[`crate::Error::ViviswapApiError`]] - If there is an error in the viviswap API.
634    pub async fn set_viviswap_kyc_amla_answer(
635        &self,
636        question_id: String,
637        answers: Vec<String>,
638        freetext_answer: Option<String>,
639    ) -> Result<()> {
640        let Some(_user) = &self.active_user else {
641            return Err(crate::Error::UserNotInitialized);
642        };
643        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
644
645        let access_token = self
646            .access_token
647            .as_ref()
648            .ok_or(crate::error::Error::MissingAccessToken)?;
649        backend::viviswap::set_viviswap_kyc_amla_answer(
650            config,
651            access_token,
652            api_types::api::viviswap::kyc::AnswerData {
653                question_id,
654                answers,
655                freetext_answer,
656            },
657        )
658        .await?;
659        Ok(())
660    }
661
662    /// Get the currently open/missing documents for KYC
663    ///
664    /// # Arguments
665    ///
666    /// None
667    ///
668    /// # Returns
669    ///
670    /// - A list of the currently open documents.
671    ///
672    /// # Errors
673    ///
674    /// - [`crate::Error::UserNotInitialized)`] - If the user is not initialized.
675    /// - [`crate::Error::ViviswapApiError`] - If there is an error in the viviswap API.
676    pub async fn get_viviswap_kyc_open_documents(&self) -> Result<Vec<KycOpenDocument>> {
677        let Some(_user) = &self.active_user else {
678            return Err(crate::Error::UserNotInitialized);
679        };
680
681        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
682        let access_token = self
683            .access_token
684            .as_ref()
685            .ok_or(crate::error::Error::MissingAccessToken)?;
686
687        Ok(backend::viviswap::get_viviswap_kyc_open_documents(config, access_token)
688            .await
689            .map(|v| v.documents)?)
690    }
691
692    /// Set / upload an open KYC document
693    ///
694    /// # Arguments
695    ///
696    /// - `document_id` - The ID of the document to upload.
697    /// - `expiration_date` - the expiration date of this document.
698    /// - `document_number` - the official document number.
699    /// - `front_image` - the front image of the official document.
700    /// - `back_image` - the back image of the official document.
701    ///
702    /// # Errors
703    ///
704    /// - [`crate::Error::ViviswapApiError`] - If there is an error in the viviswap API.
705    /// - [`crate::Error::UserNotInitialized)`] - If the user is not initialized.
706    pub async fn set_viviswap_kyc_document(
707        &self,
708        document_id: String,
709        expiration_date: String,
710        document_number: String,
711        front_image: Option<File>,
712        back_image: Option<File>,
713    ) -> Result<()> {
714        let Some(_user) = &self.active_user else {
715            return Err(crate::Error::UserNotInitialized);
716        };
717        let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
718        let access_token = self
719            .access_token
720            .as_ref()
721            .ok_or(crate::error::Error::MissingAccessToken)?;
722
723        backend::viviswap::set_viviswap_kyc_document(
724            config,
725            access_token,
726            api_types::api::viviswap::kyc::SetDocumentDataRequest {
727                document_id,
728                expiration_date,
729                document_number,
730                front_image,
731                back_image,
732            },
733        )
734        .await?;
735        Ok(())
736    }
737}