239 lines
7.5 KiB
Rust
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)
|
|
}
|
|
}
|