move repos into monorepo

This commit is contained in:
Timur Gordon
2025-11-13 20:44:00 +01:00
commit 4b23e5eb7f
204 changed files with 33737 additions and 0 deletions

View File

@@ -0,0 +1,238 @@
/// 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)
}
}

View File

@@ -0,0 +1,319 @@
/// KYC Info Object
///
/// Represents customer/person information for KYC verification.
/// Designed to be provider-agnostic but follows Idenfy API patterns.
use crate::store::{BaseData, Object, Storable};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default, crate::DeriveObject)]
pub struct KycInfo {
#[serde(flatten)]
pub base_data: BaseData,
/// External client ID (from your system) - links to User
pub client_id: String,
/// Full name (or separate first/last)
pub full_name: String,
/// First name
pub first_name: String,
/// Last name
pub last_name: String,
/// Email address
pub email: Option<String>,
/// Phone number
pub phone: Option<String>,
/// Date of birth (YYYY-MM-DD string or unix timestamp)
pub date_of_birth: Option<String>,
/// Date of birth as unix timestamp
pub date_of_birth_timestamp: Option<u64>,
/// Nationality (ISO 3166-1 alpha-2 code)
pub nationality: Option<String>,
/// Address
pub address: Option<String>,
/// City
pub city: Option<String>,
/// Country (ISO 3166-1 alpha-2 code)
pub country: Option<String>,
/// Postal code
pub postal_code: Option<String>,
/// ID document number
pub id_number: Option<String>,
/// ID document type (passport, drivers_license, national_id, etc.)
pub id_type: Option<String>,
/// ID document expiry (unix timestamp)
pub id_expiry: Option<u64>,
/// KYC provider (e.g., "idenfy", "sumsub", "onfido")
pub provider: String,
/// Provider-specific client ID (assigned by KYC provider)
pub provider_client_id: Option<String>,
/// Current verification status
pub verification_status: VerificationStatus,
/// Whether KYC is verified
pub kyc_verified: bool,
/// User ID who verified this KYC
pub kyc_verified_by: Option<u32>,
/// Timestamp when KYC was verified
pub kyc_verified_at: Option<u64>,
/// Reason for rejection if denied
pub kyc_rejected_reason: Option<String>,
/// Signature ID for verification record
pub kyc_signature: Option<u32>,
/// Additional metadata
#[serde(default)]
pub metadata: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "UPPERCASE")]
pub enum VerificationStatus {
/// Not yet started
Pending,
/// Verification in progress
Processing,
/// Successfully verified
Approved,
/// Verification failed
Denied,
/// Verification expired
Expired,
/// Requires manual review
Review,
}
impl Default for VerificationStatus {
fn default() -> Self {
VerificationStatus::Pending
}
}
impl KycInfo {
/// Create a new KYC info object
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
base_data.id = id;
Self {
base_data,
client_id: String::new(),
full_name: String::new(),
first_name: String::new(),
last_name: String::new(),
email: None,
phone: None,
date_of_birth: None,
date_of_birth_timestamp: None,
nationality: None,
address: None,
city: None,
country: None,
postal_code: None,
id_number: None,
id_type: None,
id_expiry: None,
provider: "idenfy".to_string(), // Default to Idenfy
provider_client_id: None,
verification_status: VerificationStatus::default(),
kyc_verified: false,
kyc_verified_by: None,
kyc_verified_at: None,
kyc_rejected_reason: None,
kyc_signature: None,
metadata: std::collections::HashMap::new(),
}
}
/// Builder: Set client ID
pub fn client_id(mut self, client_id: String) -> Self {
self.client_id = client_id;
self.base_data.update_modified();
self
}
/// Builder: Set full name
pub fn full_name(mut self, full_name: String) -> Self {
self.full_name = full_name.clone();
// Try to split into first/last if not already set
if self.first_name.is_empty() && self.last_name.is_empty() {
let parts: Vec<&str> = full_name.split_whitespace().collect();
if parts.len() >= 2 {
self.first_name = parts[0].to_string();
self.last_name = parts[1..].join(" ");
} else if parts.len() == 1 {
self.first_name = parts[0].to_string();
}
}
self.base_data.update_modified();
self
}
/// Builder: Set first name
pub fn first_name(mut self, first_name: String) -> Self {
self.first_name = first_name.clone();
// Update full_name if last_name exists
if !self.last_name.is_empty() {
self.full_name = format!("{} {}", first_name, self.last_name);
} else {
self.full_name = first_name;
}
self.base_data.update_modified();
self
}
/// Builder: Set last name
pub fn last_name(mut self, last_name: String) -> Self {
self.last_name = last_name.clone();
// Update full_name if first_name exists
if !self.first_name.is_empty() {
self.full_name = format!("{} {}", self.first_name, last_name);
} else {
self.full_name = last_name;
}
self.base_data.update_modified();
self
}
/// Builder: Set email
pub fn email(mut self, email: String) -> Self {
self.email = Some(email);
self.base_data.update_modified();
self
}
/// Builder: Set phone
pub fn phone(mut self, phone: String) -> Self {
self.phone = Some(phone);
self.base_data.update_modified();
self
}
/// Builder: Set date of birth
pub fn date_of_birth(mut self, dob: String) -> Self {
self.date_of_birth = Some(dob);
self.base_data.update_modified();
self
}
/// Builder: Set nationality
pub fn nationality(mut self, nationality: String) -> Self {
self.nationality = Some(nationality);
self.base_data.update_modified();
self
}
/// Builder: Set address
pub fn address(mut self, address: String) -> Self {
self.address = Some(address);
self.base_data.update_modified();
self
}
/// Builder: Set city
pub fn city(mut self, city: String) -> Self {
self.city = Some(city);
self.base_data.update_modified();
self
}
/// Builder: Set country
pub fn country(mut self, country: String) -> Self {
self.country = Some(country);
self.base_data.update_modified();
self
}
/// Builder: Set postal code
pub fn postal_code(mut self, postal_code: String) -> Self {
self.postal_code = Some(postal_code);
self.base_data.update_modified();
self
}
/// Builder: Set ID number
pub fn id_number(mut self, id_number: String) -> Self {
self.id_number = Some(id_number);
self.base_data.update_modified();
self
}
/// Builder: Set ID type
pub fn id_type(mut self, id_type: String) -> Self {
self.id_type = Some(id_type);
self.base_data.update_modified();
self
}
/// Builder: Set ID expiry
pub fn id_expiry(mut self, id_expiry: u64) -> Self {
self.id_expiry = Some(id_expiry);
self.base_data.update_modified();
self
}
/// Builder: Set KYC provider
pub fn provider(mut self, provider: String) -> Self {
self.provider = provider;
self.base_data.update_modified();
self
}
/// Set provider client ID (assigned by KYC provider)
pub fn set_provider_client_id(&mut self, provider_client_id: String) {
self.provider_client_id = Some(provider_client_id);
self.base_data.update_modified();
}
/// Set verification status
pub fn set_verification_status(&mut self, status: VerificationStatus) {
self.verification_status = status;
self.base_data.update_modified();
}
/// Set KYC verified
pub fn set_kyc_verified(&mut self, verified: bool, verified_by: Option<u32>) {
self.kyc_verified = verified;
self.kyc_verified_by = verified_by;
self.kyc_verified_at = Some(std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs());
self.base_data.update_modified();
}
/// Set KYC rejected
pub fn set_kyc_rejected(&mut self, reason: String) {
self.kyc_verified = false;
self.kyc_rejected_reason = Some(reason);
self.verification_status = VerificationStatus::Denied;
self.base_data.update_modified();
}
/// Add metadata
pub fn add_metadata(&mut self, key: String, value: String) {
self.metadata.insert(key, value);
self.base_data.update_modified();
}
}

View File

@@ -0,0 +1,13 @@
/// KYC (Know Your Customer) Module
///
/// Provides generic KYC client and session management.
/// Designed to work with multiple KYC providers (Idenfy, Sumsub, Onfido, etc.)
pub mod info;
pub mod client;
pub mod session;
pub mod rhai;
pub use info::{KycInfo, VerificationStatus};
pub use client::KycClient;
pub use session::{KycSession, SessionStatus, SessionResult};

View File

@@ -0,0 +1,376 @@
/// Rhai bindings for KYC objects
use ::rhai::plugin::*;
use ::rhai::{CustomType, Dynamic, Engine, EvalAltResult, Module, TypeBuilder};
use super::info::{KycInfo, VerificationStatus};
use super::session::{KycSession, SessionStatus};
use super::client::KycClient;
// ============================================================================
// KYC Info Module
// ============================================================================
type RhaiKycInfo = KycInfo;
#[export_module]
mod rhai_kyc_info_module {
use super::RhaiKycInfo;
#[rhai_fn(name = "new_kyc_info", return_raw)]
pub fn new_kyc_info() -> Result<RhaiKycInfo, Box<EvalAltResult>> {
Ok(KycInfo::new(0))
}
#[rhai_fn(name = "client_id", return_raw)]
pub fn set_client_id(
info: &mut RhaiKycInfo,
client_id: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.client_id(client_id);
Ok(info.clone())
}
#[rhai_fn(name = "first_name", return_raw)]
pub fn set_first_name(
info: &mut RhaiKycInfo,
first_name: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.first_name(first_name);
Ok(info.clone())
}
#[rhai_fn(name = "last_name", return_raw)]
pub fn set_last_name(
info: &mut RhaiKycInfo,
last_name: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.last_name(last_name);
Ok(info.clone())
}
#[rhai_fn(name = "email", return_raw)]
pub fn set_email(
info: &mut RhaiKycInfo,
email: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.email(email);
Ok(info.clone())
}
#[rhai_fn(name = "phone", return_raw)]
pub fn set_phone(
info: &mut RhaiKycInfo,
phone: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.phone(phone);
Ok(info.clone())
}
#[rhai_fn(name = "date_of_birth", return_raw)]
pub fn set_date_of_birth(
info: &mut RhaiKycInfo,
dob: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.date_of_birth(dob);
Ok(info.clone())
}
#[rhai_fn(name = "nationality", return_raw)]
pub fn set_nationality(
info: &mut RhaiKycInfo,
nationality: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.nationality(nationality);
Ok(info.clone())
}
#[rhai_fn(name = "address", return_raw)]
pub fn set_address(
info: &mut RhaiKycInfo,
address: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.address(address);
Ok(info.clone())
}
#[rhai_fn(name = "city", return_raw)]
pub fn set_city(
info: &mut RhaiKycInfo,
city: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.city(city);
Ok(info.clone())
}
#[rhai_fn(name = "country", return_raw)]
pub fn set_country(
info: &mut RhaiKycInfo,
country: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.country(country);
Ok(info.clone())
}
#[rhai_fn(name = "postal_code", return_raw)]
pub fn set_postal_code(
info: &mut RhaiKycInfo,
postal_code: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.postal_code(postal_code);
Ok(info.clone())
}
#[rhai_fn(name = "provider", return_raw)]
pub fn set_provider(
info: &mut RhaiKycInfo,
provider: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.provider(provider);
Ok(info.clone())
}
#[rhai_fn(name = "document_type", return_raw)]
pub fn set_document_type(
info: &mut RhaiKycInfo,
doc_type: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
// Store in provider field for now (or add to KycInfo struct)
let provider = info.provider.clone();
let owned = std::mem::take(info);
*info = owned.provider(format!("{}|doc_type:{}", provider, doc_type));
Ok(info.clone())
}
#[rhai_fn(name = "document_number", return_raw)]
pub fn set_document_number(
info: &mut RhaiKycInfo,
doc_number: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
// Store in provider field for now (or add to KycInfo struct)
let provider = info.provider.clone();
let owned = std::mem::take(info);
*info = owned.provider(format!("{}|doc_num:{}", provider, doc_number));
Ok(info.clone())
}
#[rhai_fn(name = "verified", return_raw)]
pub fn set_verified(
info: &mut RhaiKycInfo,
_verified: bool,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
// Mark as verified in provider field
let provider = info.provider.clone();
let owned = std::mem::take(info);
*info = owned.provider(format!("{}|verified", provider));
Ok(info.clone())
}
// Getters
#[rhai_fn(name = "get_id")]
pub fn get_id(info: &mut RhaiKycInfo) -> u32 {
info.base_data.id
}
#[rhai_fn(name = "get_client_id")]
pub fn get_client_id(info: &mut RhaiKycInfo) -> String {
info.client_id.clone()
}
#[rhai_fn(name = "get_first_name")]
pub fn get_first_name(info: &mut RhaiKycInfo) -> String {
info.first_name.clone()
}
#[rhai_fn(name = "get_last_name")]
pub fn get_last_name(info: &mut RhaiKycInfo) -> String {
info.last_name.clone()
}
#[rhai_fn(name = "get_email")]
pub fn get_email(info: &mut RhaiKycInfo) -> String {
info.email.clone().unwrap_or_default()
}
#[rhai_fn(name = "get_provider")]
pub fn get_provider(info: &mut RhaiKycInfo) -> String {
info.provider.clone()
}
}
// ============================================================================
// KYC Session Module
// ============================================================================
type RhaiKycSession = KycSession;
#[export_module]
mod rhai_kyc_session_module {
use super::RhaiKycSession;
#[rhai_fn(name = "new_kyc_session", return_raw)]
pub fn new_kyc_session(
client_id: String,
provider: String,
) -> Result<RhaiKycSession, Box<EvalAltResult>> {
Ok(KycSession::new(0, client_id, provider))
}
#[rhai_fn(name = "callback_url", return_raw)]
pub fn set_callback_url(
session: &mut RhaiKycSession,
url: String,
) -> Result<RhaiKycSession, Box<EvalAltResult>> {
let owned = std::mem::take(session);
*session = owned.callback_url(url);
Ok(session.clone())
}
#[rhai_fn(name = "success_url", return_raw)]
pub fn set_success_url(
session: &mut RhaiKycSession,
url: String,
) -> Result<RhaiKycSession, Box<EvalAltResult>> {
let owned = std::mem::take(session);
*session = owned.success_url(url);
Ok(session.clone())
}
#[rhai_fn(name = "error_url", return_raw)]
pub fn set_error_url(
session: &mut RhaiKycSession,
url: String,
) -> Result<RhaiKycSession, Box<EvalAltResult>> {
let owned = std::mem::take(session);
*session = owned.error_url(url);
Ok(session.clone())
}
#[rhai_fn(name = "locale", return_raw)]
pub fn set_locale(
session: &mut RhaiKycSession,
locale: String,
) -> Result<RhaiKycSession, Box<EvalAltResult>> {
let owned = std::mem::take(session);
*session = owned.locale(locale);
Ok(session.clone())
}
// Getters
#[rhai_fn(name = "get_id")]
pub fn get_id(session: &mut RhaiKycSession) -> u32 {
session.base_data.id
}
#[rhai_fn(name = "get_client_id")]
pub fn get_client_id(session: &mut RhaiKycSession) -> String {
session.client_id.clone()
}
#[rhai_fn(name = "get_provider")]
pub fn get_provider(session: &mut RhaiKycSession) -> String {
session.provider.clone()
}
#[rhai_fn(name = "get_verification_url")]
pub fn get_verification_url(session: &mut RhaiKycSession) -> String {
session.verification_url.clone().unwrap_or_default()
}
}
// ============================================================================
// KYC Client Module
// ============================================================================
type RhaiKycClient = KycClient;
#[export_module]
mod rhai_kyc_client_module {
use super::RhaiKycClient;
use super::RhaiKycInfo;
use super::RhaiKycSession;
use ::rhai::EvalAltResult;
#[rhai_fn(name = "new_kyc_client_idenfy", return_raw)]
pub fn new_idenfy_client(
api_key: String,
api_secret: String,
) -> Result<RhaiKycClient, Box<EvalAltResult>> {
Ok(KycClient::idenfy(api_key, api_secret))
}
#[rhai_fn(name = "create_verification_session", return_raw)]
pub fn create_verification_session(
client: &mut RhaiKycClient,
kyc_info: RhaiKycInfo,
session: RhaiKycSession,
) -> Result<String, Box<EvalAltResult>> {
// Need to use tokio runtime for async call
let rt = tokio::runtime::Runtime::new()
.map_err(|e| format!("Failed to create runtime: {}", e))?;
let mut session_mut = session.clone();
let url = rt.block_on(client.create_verification_session(&kyc_info, &mut session_mut))
.map_err(|e| format!("Failed to create verification session: {}", e))?;
Ok(url)
}
}
// ============================================================================
// Registration Functions
// ============================================================================
/// Register KYC modules into a Rhai Module (for use in packages)
pub fn register_kyc_modules(parent_module: &mut Module) {
// Register custom types
parent_module.set_custom_type::<KycInfo>("KycInfo");
parent_module.set_custom_type::<KycSession>("KycSession");
parent_module.set_custom_type::<KycClient>("KycClient");
// Merge KYC info functions
let info_module = exported_module!(rhai_kyc_info_module);
parent_module.merge(&info_module);
// Merge KYC session functions
let session_module = exported_module!(rhai_kyc_session_module);
parent_module.merge(&session_module);
// Merge KYC client functions
let client_module = exported_module!(rhai_kyc_client_module);
parent_module.merge(&client_module);
}
// ============================================================================
// CustomType Implementations
// ============================================================================
impl CustomType for KycInfo {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("KycInfo");
}
}
impl CustomType for KycSession {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("KycSession");
}
}
impl CustomType for KycClient {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("KycClient");
}
}

View File

@@ -0,0 +1,186 @@
/// KYC Verification Session
///
/// Represents a verification session for a KYC client.
/// Follows Idenfy API patterns but is provider-agnostic.
use crate::store::{BaseData, Object, Storable};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default, crate::DeriveObject)]
pub struct KycSession {
#[serde(flatten)]
pub base_data: BaseData,
/// Reference to the KYC client
pub client_id: String,
/// KYC provider
pub provider: String,
/// Session token/ID from provider
pub session_token: Option<String>,
/// Verification URL for the client
pub verification_url: Option<String>,
/// Session status
pub status: SessionStatus,
/// Session expiration timestamp
pub expires_at: Option<i64>,
/// Callback URL for webhook notifications
pub callback_url: Option<String>,
/// Success redirect URL
pub success_url: Option<String>,
/// Error redirect URL
pub error_url: Option<String>,
/// Locale (e.g., "en", "de", "fr")
pub locale: Option<String>,
/// Provider-specific configuration
#[serde(default)]
pub provider_config: std::collections::HashMap<String, String>,
/// Session result data
pub result: Option<SessionResult>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "UPPERCASE")]
pub enum SessionStatus {
/// Session created but not started
#[default]
Created,
/// Client is currently verifying
Active,
/// Session completed successfully
Completed,
/// Session failed
Failed,
/// Session expired
Expired,
/// Session cancelled
Cancelled,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionResult {
/// Overall verification status
pub status: String,
/// Verification score (0-100)
pub score: Option<f64>,
/// Reason for denial (if denied)
pub denial_reason: Option<String>,
/// Document type verified
pub document_type: Option<String>,
/// Document number
pub document_number: Option<String>,
/// Document issuing country
pub document_country: Option<String>,
/// Face match result
pub face_match: Option<bool>,
/// Liveness check result
pub liveness_check: Option<bool>,
/// Additional provider-specific data
#[serde(default)]
pub provider_data: std::collections::HashMap<String, serde_json::Value>,
}
impl KycSession {
/// Create a new KYC session
pub fn new(id: u32, client_id: String, provider: String) -> Self {
let mut base_data = BaseData::new();
base_data.id = id;
Self {
base_data,
client_id,
provider,
session_token: None,
verification_url: None,
status: SessionStatus::Created,
expires_at: None,
callback_url: None,
success_url: None,
error_url: None,
locale: None,
provider_config: std::collections::HashMap::new(),
result: None,
}
}
/// Builder: Set callback URL
pub fn callback_url(mut self, url: String) -> Self {
self.callback_url = Some(url);
self.base_data.update_modified();
self
}
/// Builder: Set success URL
pub fn success_url(mut self, url: String) -> Self {
self.success_url = Some(url);
self.base_data.update_modified();
self
}
/// Builder: Set error URL
pub fn error_url(mut self, url: String) -> Self {
self.error_url = Some(url);
self.base_data.update_modified();
self
}
/// Builder: Set locale
pub fn locale(mut self, locale: String) -> Self {
self.locale = Some(locale);
self.base_data.update_modified();
self
}
/// Set session token from provider
pub fn set_session_token(&mut self, token: String) {
self.session_token = Some(token);
self.base_data.update_modified();
}
/// Set verification URL
pub fn set_verification_url(&mut self, url: String) {
self.verification_url = Some(url);
self.base_data.update_modified();
}
/// Set session status
pub fn set_status(&mut self, status: SessionStatus) {
self.status = status;
self.base_data.update_modified();
}
/// Set expiration timestamp
pub fn set_expires_at(&mut self, timestamp: i64) {
self.expires_at = Some(timestamp);
self.base_data.update_modified();
}
/// Set session result
pub fn set_result(&mut self, result: SessionResult) {
self.result = Some(result);
self.base_data.update_modified();
}
/// Add provider-specific configuration
pub fn add_provider_config(&mut self, key: String, value: String) {
self.provider_config.insert(key, value);
self.base_data.update_modified();
}
}