move repos into monorepo
This commit is contained in:
238
lib/osiris/core/objects/kyc/client.rs
Normal file
238
lib/osiris/core/objects/kyc/client.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
319
lib/osiris/core/objects/kyc/info.rs
Normal file
319
lib/osiris/core/objects/kyc/info.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
13
lib/osiris/core/objects/kyc/mod.rs
Normal file
13
lib/osiris/core/objects/kyc/mod.rs
Normal 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};
|
||||
376
lib/osiris/core/objects/kyc/rhai.rs
Normal file
376
lib/osiris/core/objects/kyc/rhai.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
186
lib/osiris/core/objects/kyc/session.rs
Normal file
186
lib/osiris/core/objects/kyc/session.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user