/// KYC Client /// /// Actual API client for making KYC provider API calls. /// Currently implements Idenfy API but designed to be extensible for other providers. use serde::{Deserialize, Serialize}; use super::{KycInfo, KycSession, session::SessionStatus}; /// KYC Client for making API calls to KYC providers #[derive(Debug, Clone)] pub struct KycClient { /// Provider name (e.g., "idenfy", "sumsub", "onfido") pub provider: String, /// API key pub api_key: String, /// API secret pub api_secret: String, /// Base URL for API (optional, uses provider default if not set) pub base_url: Option, } /// Idenfy-specific API request/response structures #[derive(Debug, Serialize, Deserialize)] pub struct IdenfyTokenRequest { #[serde(rename = "clientId")] pub client_id: String, #[serde(rename = "firstName")] pub first_name: String, #[serde(rename = "lastName")] pub last_name: String, #[serde(skip_serializing_if = "Option::is_none")] pub email: Option, #[serde(skip_serializing_if = "Option::is_none")] pub phone: Option, #[serde(rename = "dateOfBirth", skip_serializing_if = "Option::is_none")] pub date_of_birth: Option, #[serde(skip_serializing_if = "Option::is_none")] pub nationality: Option, #[serde(skip_serializing_if = "Option::is_none")] pub address: Option, #[serde(skip_serializing_if = "Option::is_none")] pub city: Option, #[serde(skip_serializing_if = "Option::is_none")] pub country: Option, #[serde(rename = "zipCode", skip_serializing_if = "Option::is_none")] pub zip_code: Option, #[serde(rename = "successUrl", skip_serializing_if = "Option::is_none")] pub success_url: Option, #[serde(rename = "errorUrl", skip_serializing_if = "Option::is_none")] pub error_url: Option, #[serde(rename = "callbackUrl", skip_serializing_if = "Option::is_none")] pub callback_url: Option, #[serde(skip_serializing_if = "Option::is_none")] pub locale: Option, } #[derive(Debug, Deserialize)] pub struct IdenfyTokenResponse { #[serde(rename = "authToken")] pub auth_token: String, #[serde(rename = "scanRef")] pub scan_ref: String, #[serde(rename = "clientId")] pub client_id: String, } #[derive(Debug, Deserialize)] pub struct IdenfyVerificationStatus { pub status: String, #[serde(rename = "scanRef")] pub scan_ref: String, #[serde(rename = "clientId")] pub client_id: String, } impl KycClient { /// Create a new KYC client pub fn new(provider: String, api_key: String, api_secret: String) -> Self { Self { provider, api_key, api_secret, base_url: None, } } /// Create an Idenfy client pub fn idenfy(api_key: String, api_secret: String) -> Self { Self { provider: "idenfy".to_string(), api_key, api_secret, base_url: Some("https://ivs.idenfy.com/api/v2".to_string()), } } /// Set custom base URL pub fn with_base_url(mut self, base_url: String) -> Self { self.base_url = Some(base_url); self } /// Get the base URL for the provider fn get_base_url(&self) -> String { if let Some(url) = &self.base_url { return url.clone(); } match self.provider.as_str() { "idenfy" => "https://ivs.idenfy.com/api/v2".to_string(), "sumsub" => "https://api.sumsub.com".to_string(), "onfido" => "https://api.onfido.com/v3".to_string(), _ => panic!("Unknown provider: {}", self.provider), } } /// Create a verification session (Idenfy implementation) pub async fn create_verification_session( &self, kyc_info: &KycInfo, session: &mut KycSession, ) -> Result> { match self.provider.as_str() { "idenfy" => self.create_idenfy_session(kyc_info, session).await, _ => Err(format!("Provider {} not yet implemented", self.provider).into()), } } /// Create an Idenfy verification session async fn create_idenfy_session( &self, kyc_info: &KycInfo, session: &mut KycSession, ) -> Result> { let url = format!("{}/token", self.get_base_url()); let request = IdenfyTokenRequest { client_id: kyc_info.client_id.clone(), first_name: kyc_info.first_name.clone(), last_name: kyc_info.last_name.clone(), email: kyc_info.email.clone(), phone: kyc_info.phone.clone(), date_of_birth: kyc_info.date_of_birth.clone(), nationality: kyc_info.nationality.clone(), address: kyc_info.address.clone(), city: kyc_info.city.clone(), country: kyc_info.country.clone(), zip_code: kyc_info.postal_code.clone(), success_url: session.success_url.clone(), error_url: session.error_url.clone(), callback_url: session.callback_url.clone(), locale: session.locale.clone(), }; let client = reqwest::Client::new(); let response = client .post(&url) .basic_auth(&self.api_key, Some(&self.api_secret)) .json(&request) .send() .await?; if !response.status().is_success() { let error_text = response.text().await?; return Err(format!("Idenfy API error: {}", error_text).into()); } let token_response: IdenfyTokenResponse = response.json().await?; // Update session with token and URL session.set_session_token(token_response.auth_token.clone()); // Construct verification URL let verification_url = format!( "https://ivs.idenfy.com/api/v2/redirect?authToken={}", token_response.auth_token ); session.set_verification_url(verification_url.clone()); session.set_status(SessionStatus::Active); Ok(verification_url) } /// Get verification status (Idenfy implementation) pub async fn get_verification_status( &self, scan_ref: &str, ) -> Result> { match self.provider.as_str() { "idenfy" => self.get_idenfy_status(scan_ref).await, _ => Err(format!("Provider {} not yet implemented", self.provider).into()), } } /// Get Idenfy verification status async fn get_idenfy_status( &self, scan_ref: &str, ) -> Result> { let url = format!("{}/status/{}", self.get_base_url(), scan_ref); let client = reqwest::Client::new(); let response = client .get(&url) .basic_auth(&self.api_key, Some(&self.api_secret)) .send() .await?; if !response.status().is_success() { let error_text = response.text().await?; return Err(format!("Idenfy API error: {}", error_text).into()); } let status: IdenfyVerificationStatus = response.json().await?; Ok(status) } }