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 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 let mut user = self.get_user().await?;
50
51 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 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 pub async fn get_kyc_details_for_viviswap(&mut self) -> Result<ViviswapKycStatus> {
91 info!("Getting KYC details for viviswap user");
92 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 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 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 let mut user = self.get_user().await?;
162
163 if self.repo.is_none() {
165 return Err(crate::Error::UserRepoNotInitialized);
166 }
167
168 let Some(viviswap_state) = &mut user.viviswap_state else {
170 return Err(crate::Error::Viviswap(ViviswapError::MissingUser));
171 };
172
173 if viviswap_state.verification_status != ViviswapVerificationStatus::Unverified {
175 return Err(crate::Error::Viviswap(ViviswapError::InvalidState));
176 };
177
178 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 if !missing_field_errors.is_empty() {
227 return Err(crate::Error::Viviswap(ViviswapError::Aggregate(missing_field_errors)));
228 }
229
230 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 unreachable!()
244 };
245
246 let access_token = self
247 .access_token
248 .as_ref()
249 .ok_or_else(|| crate::error::Error::MissingAccessToken)?;
250
251 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 set_viviswap_kyc_personal_details(config, access_token, full_name, date_of_birth).await?;
268
269 self.get_kyc_details_for_viviswap().await?;
271
272 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 #[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 let mut user = self.get_user().await?;
319
320 let Some(repo) = &mut self.repo else {
322 return Err(crate::Error::UserRepoNotInitialized);
323 };
324
325 let Some(viviswap_state) = &mut user.viviswap_state else {
327 return Err(crate::Error::Viviswap(ViviswapError::MissingUser));
328 };
329
330 if viviswap_state.verification_status != ViviswapVerificationStatus::Unverified {
332 return Err(crate::Error::Viviswap(ViviswapError::InvalidState));
333 };
334
335 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); 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 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 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 #[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 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 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 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 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 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 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}