Files
osiris/src/objects/kyc/client.rs
Timur Gordon 87c556df7a wip
2025-10-29 16:52:33 +01:00

239 lines
7.5 KiB
Rust

/// 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<String>,
}
/// 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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub phone: Option<String>,
#[serde(rename = "dateOfBirth", skip_serializing_if = "Option::is_none")]
pub date_of_birth: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nationality: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub city: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub country: Option<String>,
#[serde(rename = "zipCode", skip_serializing_if = "Option::is_none")]
pub zip_code: Option<String>,
#[serde(rename = "successUrl", skip_serializing_if = "Option::is_none")]
pub success_url: Option<String>,
#[serde(rename = "errorUrl", skip_serializing_if = "Option::is_none")]
pub error_url: Option<String>,
#[serde(rename = "callbackUrl", skip_serializing_if = "Option::is_none")]
pub callback_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub locale: Option<String>,
}
#[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<String, Box<dyn std::error::Error>> {
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<String, Box<dyn std::error::Error>> {
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<IdenfyVerificationStatus, Box<dyn std::error::Error>> {
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<IdenfyVerificationStatus, Box<dyn std::error::Error>> {
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)
}
}