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,10 @@
/// Money Module
///
/// Financial objects including accounts, assets, transactions, and payment providers.
pub mod models;
pub mod rhai;
pub mod payments;
pub use models::{Account, Asset, Transaction, AccountStatus, TransactionType, Signature, AccountPolicyItem};
pub use payments::{PaymentClient, PaymentRequest, PaymentResponse, PaymentStatus};

View File

@@ -0,0 +1,498 @@
use crate::store::{BaseData, IndexKey, Object};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Represents the status of an account
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AccountStatus {
Active,
Inactive,
Suspended,
Archived,
}
impl Default for AccountStatus {
fn default() -> Self {
AccountStatus::Active
}
}
/// Represents the type of transaction
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TransactionType {
Transfer,
Clawback,
Freeze,
Unfreeze,
Issue,
Burn,
}
impl Default for TransactionType {
fn default() -> Self {
TransactionType::Transfer
}
}
/// Represents a signature for transactions
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Signature {
pub signer_id: u32,
pub signature: String,
pub timestamp: u64,
}
impl Signature {
pub fn new() -> Self {
Self {
signer_id: 0,
signature: String::new(),
timestamp: 0,
}
}
pub fn signer_id(mut self, signer_id: u32) -> Self {
self.signer_id = signer_id;
self
}
pub fn signature(mut self, signature: impl ToString) -> Self {
self.signature = signature.to_string();
self
}
pub fn timestamp(mut self, timestamp: u64) -> Self {
self.timestamp = timestamp;
self
}
pub fn build(self) -> Self {
self
}
}
/// Policy item for account operations
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct AccountPolicyItem {
pub signers: Vec<u32>,
pub min_signatures: u32,
pub enabled: bool,
pub threshold: f64,
pub recipient: u32,
}
impl AccountPolicyItem {
pub fn new() -> Self {
Self {
signers: Vec::new(),
min_signatures: 0,
enabled: false,
threshold: 0.0,
recipient: 0,
}
}
pub fn add_signer(mut self, signer_id: u32) -> Self {
self.signers.push(signer_id);
self
}
pub fn signers(mut self, signers: Vec<u32>) -> Self {
self.signers = signers;
self
}
pub fn min_signatures(mut self, min_signatures: u32) -> Self {
self.min_signatures = min_signatures;
self
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn threshold(mut self, threshold: f64) -> Self {
self.threshold = threshold;
self
}
pub fn recipient(mut self, recipient: u32) -> Self {
self.recipient = recipient;
self
}
pub fn build(self) -> Self {
self
}
}
/// Represents an account in the financial system
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct Account {
/// Base model data
pub base_data: BaseData,
pub owner_id: u32,
#[index]
pub address: String,
pub balance: f64,
pub currency: String,
pub assetid: u32,
pub last_activity: u64,
pub administrators: Vec<u32>,
pub accountpolicy: u32,
}
impl Account {
/// Create a new account instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
owner_id: 0,
address: String::new(),
balance: 0.0,
currency: String::new(),
assetid: 0,
last_activity: 0,
administrators: Vec::new(),
accountpolicy: 0,
}
}
/// Set the owner ID (fluent)
pub fn owner_id(mut self, owner_id: u32) -> Self {
self.owner_id = owner_id;
self
}
/// Set the blockchain address (fluent)
pub fn address(mut self, address: impl ToString) -> Self {
self.address = address.to_string();
self
}
/// Set the balance (fluent)
pub fn balance(mut self, balance: f64) -> Self {
self.balance = balance;
self
}
/// Set the currency (fluent)
pub fn currency(mut self, currency: impl ToString) -> Self {
self.currency = currency.to_string();
self
}
/// Set the asset ID (fluent)
pub fn assetid(mut self, assetid: u32) -> Self {
self.assetid = assetid;
self
}
/// Set the last activity timestamp (fluent)
pub fn last_activity(mut self, last_activity: u64) -> Self {
self.last_activity = last_activity;
self
}
/// Add an administrator (fluent)
pub fn add_administrator(mut self, admin_id: u32) -> Self {
self.administrators.push(admin_id);
self
}
/// Set all administrators (fluent)
pub fn administrators(mut self, administrators: Vec<u32>) -> Self {
self.administrators = administrators;
self
}
/// Set the account policy ID (fluent)
pub fn accountpolicy(mut self, accountpolicy: u32) -> Self {
self.accountpolicy = accountpolicy;
self
}
/// Build the final account instance
pub fn build(self) -> Self {
self
}
}
/// Represents an asset in the financial system
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct Asset {
/// Base model data
pub base_data: BaseData,
#[index]
pub address: String,
pub assetid: u32,
pub asset_type: String,
pub issuer: u32,
pub supply: f64,
pub decimals: u8,
pub is_frozen: bool,
pub metadata: HashMap<String, String>,
pub administrators: Vec<u32>,
pub min_signatures: u32,
}
impl Asset {
/// Create a new asset instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
address: String::new(),
assetid: 0,
asset_type: String::new(),
issuer: 0,
supply: 0.0,
decimals: 0,
is_frozen: false,
metadata: HashMap::new(),
administrators: Vec::new(),
min_signatures: 0,
}
}
/// Set the blockchain address (fluent)
pub fn address(mut self, address: impl ToString) -> Self {
self.address = address.to_string();
self
}
/// Set the asset ID (fluent)
pub fn assetid(mut self, assetid: u32) -> Self {
self.assetid = assetid;
self
}
/// Set the asset type (fluent)
pub fn asset_type(mut self, asset_type: impl ToString) -> Self {
self.asset_type = asset_type.to_string();
self
}
/// Set the issuer (fluent)
pub fn issuer(mut self, issuer: u32) -> Self {
self.issuer = issuer;
self
}
/// Set the supply (fluent)
pub fn supply(mut self, supply: f64) -> Self {
self.supply = supply;
self
}
/// Set the decimals (fluent)
pub fn decimals(mut self, decimals: u8) -> Self {
self.decimals = decimals;
self
}
/// Set the frozen status (fluent)
pub fn is_frozen(mut self, is_frozen: bool) -> Self {
self.is_frozen = is_frozen;
self
}
/// Add metadata entry (fluent)
pub fn add_metadata(mut self, key: impl ToString, value: impl ToString) -> Self {
self.metadata.insert(key.to_string(), value.to_string());
self
}
/// Set all metadata (fluent)
pub fn metadata(mut self, metadata: HashMap<String, String>) -> Self {
self.metadata = metadata;
self
}
/// Add an administrator (fluent)
pub fn add_administrator(mut self, admin_id: u32) -> Self {
self.administrators.push(admin_id);
self
}
/// Set all administrators (fluent)
pub fn administrators(mut self, administrators: Vec<u32>) -> Self {
self.administrators = administrators;
self
}
/// Set minimum signatures required (fluent)
pub fn min_signatures(mut self, min_signatures: u32) -> Self {
self.min_signatures = min_signatures;
self
}
/// Build the final asset instance
pub fn build(self) -> Self {
self
}
}
/// Represents account policies for various operations
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct AccountPolicy {
/// Base model data
pub base_data: BaseData,
pub transferpolicy: AccountPolicyItem,
pub adminpolicy: AccountPolicyItem,
pub clawbackpolicy: AccountPolicyItem,
pub freezepolicy: AccountPolicyItem,
}
impl AccountPolicy {
/// Create a new account policy instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
transferpolicy: AccountPolicyItem::new(),
adminpolicy: AccountPolicyItem::new(),
clawbackpolicy: AccountPolicyItem::new(),
freezepolicy: AccountPolicyItem::new(),
}
}
/// Set the transfer policy (fluent)
pub fn transferpolicy(mut self, transferpolicy: AccountPolicyItem) -> Self {
self.transferpolicy = transferpolicy;
self
}
/// Set the admin policy (fluent)
pub fn adminpolicy(mut self, adminpolicy: AccountPolicyItem) -> Self {
self.adminpolicy = adminpolicy;
self
}
/// Set the clawback policy (fluent)
pub fn clawbackpolicy(mut self, clawbackpolicy: AccountPolicyItem) -> Self {
self.clawbackpolicy = clawbackpolicy;
self
}
/// Set the freeze policy (fluent)
pub fn freezepolicy(mut self, freezepolicy: AccountPolicyItem) -> Self {
self.freezepolicy = freezepolicy;
self
}
/// Build the final account policy instance
pub fn build(self) -> Self {
self
}
}
/// Represents a financial transaction
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct Transaction {
/// Base model data
pub base_data: BaseData,
pub txid: u32,
pub source: u32,
pub destination: u32,
pub assetid: u32,
pub amount: f64,
pub timestamp: u64,
pub status: String,
pub memo: String,
pub tx_type: TransactionType,
pub signatures: Vec<Signature>,
}
impl Transaction {
/// Create a new transaction instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
txid: 0,
source: 0,
destination: 0,
assetid: 0,
amount: 0.0,
timestamp: 0,
status: String::new(),
memo: String::new(),
tx_type: TransactionType::default(),
signatures: Vec::new(),
}
}
/// Set the transaction ID (fluent)
pub fn txid(mut self, txid: u32) -> Self {
self.txid = txid;
self
}
/// Set the source account (fluent)
pub fn source(mut self, source: u32) -> Self {
self.source = source;
self
}
/// Set the destination account (fluent)
pub fn destination(mut self, destination: u32) -> Self {
self.destination = destination;
self
}
/// Set the asset ID (fluent)
pub fn assetid(mut self, assetid: u32) -> Self {
self.assetid = assetid;
self
}
/// Set the amount (fluent)
pub fn amount(mut self, amount: f64) -> Self {
self.amount = amount;
self
}
/// Set the timestamp (fluent)
pub fn timestamp(mut self, timestamp: u64) -> Self {
self.timestamp = timestamp;
self
}
/// Set the status (fluent)
pub fn status(mut self, status: impl ToString) -> Self {
self.status = status.to_string();
self
}
/// Set the memo (fluent)
pub fn memo(mut self, memo: impl ToString) -> Self {
self.memo = memo.to_string();
self
}
/// Set the transaction type (fluent)
pub fn tx_type(mut self, tx_type: TransactionType) -> Self {
self.tx_type = tx_type;
self
}
/// Add a signature (fluent)
pub fn add_signature(mut self, signature: Signature) -> Self {
self.signatures.push(signature);
self
}
/// Set all signatures (fluent)
pub fn signatures(mut self, signatures: Vec<Signature>) -> Self {
self.signatures = signatures;
self
}
/// Build the final transaction instance
pub fn build(self) -> Self {
self
}
}

View File

@@ -0,0 +1,457 @@
/// Payment Provider Client
///
/// Generic payment provider API client supporting multiple payment gateways.
/// Currently implements Pesapal API but designed to be extensible for other providers.
use serde::{Deserialize, Serialize};
use crate::store::{BaseData, IndexKey, Object};
// Helper to run async code synchronously
fn run_async<F, T>(future: F) -> T
where
F: std::future::Future<Output = T> + Send + 'static,
T: Send + 'static,
{
// Try to use current runtime handle if available
if tokio::runtime::Handle::try_current().is_ok() {
// We're in a runtime, spawn a blocking thread with its own runtime
std::thread::scope(|s| {
s.spawn(|| {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(future)
}).join().unwrap()
})
} else {
// No runtime, create one
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(future)
}
}
/// Payment Provider Client for making API calls to payment gateways
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentClient {
/// Base data for object storage
pub base_data: BaseData,
/// Provider name (e.g., "pesapal", "stripe", "paypal", "flutterwave")
pub provider: String,
/// Consumer key / API key
pub consumer_key: String,
/// Consumer secret / API secret
pub consumer_secret: String,
/// Base URL for API (optional, uses provider default if not set)
pub base_url: Option<String>,
/// Sandbox mode (for testing)
pub sandbox: bool,
}
/// Payment request details
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentRequest {
/// Unique merchant reference
pub merchant_reference: String,
/// Amount to charge
pub amount: f64,
/// Currency code (e.g., "USD", "KES", "UGX")
pub currency: String,
/// Description of the payment
pub description: String,
/// Callback URL for payment notifications
pub callback_url: String,
/// Redirect URL after payment (optional)
pub redirect_url: Option<String>,
/// Cancel URL (optional)
pub cancel_url: Option<String>,
/// Customer email
pub customer_email: Option<String>,
/// Customer phone
pub customer_phone: Option<String>,
/// Customer first name
pub customer_first_name: Option<String>,
/// Customer last name
pub customer_last_name: Option<String>,
}
/// Payment response from provider
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentResponse {
/// Payment link URL
pub payment_url: String,
/// Order tracking ID from provider
pub order_tracking_id: String,
/// Merchant reference
pub merchant_reference: String,
/// Status message
pub status: String,
}
/// Payment status query result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentStatus {
/// Order tracking ID
pub order_tracking_id: String,
/// Merchant reference
pub merchant_reference: String,
/// Payment status (e.g., "PENDING", "COMPLETED", "FAILED")
pub status: String,
/// Amount
pub amount: f64,
/// Currency
pub currency: String,
/// Payment method used
pub payment_method: Option<String>,
/// Transaction ID
pub transaction_id: Option<String>,
}
// Pesapal-specific structures
#[derive(Debug, Serialize)]
struct PesapalAuthRequest {
consumer_key: String,
consumer_secret: String,
}
#[derive(Debug, Deserialize)]
struct PesapalAuthResponse {
token: String,
#[serde(rename = "expiryDate")]
expiry_date: Option<serde_json::Value>,
error: Option<String>,
status: Option<String>,
message: Option<String>,
}
#[derive(Debug, Serialize)]
struct PesapalSubmitOrderRequest {
id: String,
currency: String,
amount: f64,
description: String,
callback_url: String,
redirect_mode: String,
notification_id: String,
billing_address: Option<PesapalBillingAddress>,
}
#[derive(Debug, Serialize)]
struct PesapalBillingAddress {
email_address: Option<String>,
phone_number: Option<String>,
first_name: Option<String>,
last_name: Option<String>,
}
#[derive(Debug, Deserialize)]
struct PesapalSubmitOrderResponse {
order_tracking_id: Option<String>,
merchant_reference: Option<String>,
redirect_url: Option<String>,
error: Option<serde_json::Value>,
status: Option<String>,
}
#[derive(Debug, Deserialize)]
struct PesapalTransactionStatusResponse {
payment_method: Option<String>,
amount: f64,
created_date: String,
confirmation_code: Option<String>,
payment_status_description: String,
description: String,
message: String,
payment_account: Option<String>,
call_back_url: String,
status_code: i32,
merchant_reference: String,
payment_status_code: String,
currency: String,
error: Option<String>,
status: String,
}
impl PaymentClient {
/// Create a new payment client
pub fn new(id: u32, provider: String, consumer_key: String, consumer_secret: String) -> Self {
let base_data = BaseData::with_id(id, String::new());
Self {
base_data,
provider,
consumer_key,
consumer_secret,
base_url: None,
sandbox: false,
}
}
/// Create a Pesapal client
pub fn pesapal(id: u32, consumer_key: String, consumer_secret: String) -> Self {
let base_data = BaseData::with_id(id, String::new());
Self {
base_data,
provider: "pesapal".to_string(),
consumer_key,
consumer_secret,
base_url: Some("https://pay.pesapal.com/v3".to_string()),
sandbox: false,
}
}
/// Create a Pesapal sandbox client
pub fn pesapal_sandbox(id: u32, consumer_key: String, consumer_secret: String) -> Self {
let base_data = BaseData::with_id(id, String::new());
Self {
base_data,
provider: "pesapal".to_string(),
consumer_key,
consumer_secret,
base_url: Some("https://cybqa.pesapal.com/pesapalv3".to_string()),
sandbox: true,
}
}
/// Set custom base URL
pub fn with_base_url(mut self, base_url: String) -> Self {
self.base_url = Some(base_url);
self
}
/// Enable sandbox mode
pub fn with_sandbox(mut self, sandbox: bool) -> Self {
self.sandbox = sandbox;
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() {
"pesapal" => {
if self.sandbox {
"https://cybqa.pesapal.com/pesapalv3".to_string()
} else {
"https://pay.pesapal.com/v3".to_string()
}
}
"stripe" => "https://api.stripe.com/v1".to_string(),
"paypal" => "https://api.paypal.com/v2".to_string(),
"flutterwave" => "https://api.flutterwave.com/v3".to_string(),
_ => panic!("Unknown provider: {}", self.provider),
}
}
/// Create a payment link
pub fn create_payment_link(
&self,
request: &PaymentRequest,
) -> Result<PaymentResponse, String> {
match self.provider.as_str() {
"pesapal" => self.create_pesapal_payment(request),
_ => Err(format!("Provider {} not yet implemented", self.provider)),
}
}
/// Get payment status
pub fn get_payment_status(
&self,
order_tracking_id: &str,
) -> Result<PaymentStatus, String> {
match self.provider.as_str() {
"pesapal" => self.get_pesapal_status(order_tracking_id),
_ => Err(format!("Provider {} not yet implemented", self.provider)),
}
}
/// Authenticate with Pesapal and get access token
fn pesapal_authenticate(&self) -> Result<String, String> {
let url = format!("{}/api/Auth/RequestToken", self.get_base_url());
let auth_request = PesapalAuthRequest {
consumer_key: self.consumer_key.clone(),
consumer_secret: self.consumer_secret.clone(),
};
run_async(async move {
let client = reqwest::Client::new();
let response = client
.post(&url)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.json(&auth_request)
.send()
.await
.map_err(|e| format!("Failed to send auth request: {}", e))?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
return Err(format!("Pesapal auth failed ({}): {}", status, error_text));
}
// Debug: print raw response
let response_text = response.text().await
.map_err(|e| format!("Failed to read auth response: {}", e))?;
println!("=== PESAPAL AUTH RESPONSE ===");
println!("{}", response_text);
println!("==============================");
let auth_response: PesapalAuthResponse = serde_json::from_str(&response_text)
.map_err(|e| format!("Failed to parse auth response: {}", e))?;
if let Some(error) = auth_response.error {
return Err(format!("Pesapal auth error: {}", error));
}
Ok(auth_response.token)
})
}
/// Create a Pesapal payment
fn create_pesapal_payment(
&self,
request: &PaymentRequest,
) -> Result<PaymentResponse, String> {
// Get auth token
let token = self.pesapal_authenticate()?;
let url = format!("{}/api/Transactions/SubmitOrderRequest", self.get_base_url());
let pesapal_request = PesapalSubmitOrderRequest {
id: request.merchant_reference.clone(),
currency: request.currency.clone(),
amount: request.amount,
description: request.description.clone(),
callback_url: request.callback_url.clone(),
redirect_mode: String::new(),
notification_id: String::new(),
billing_address: Some(PesapalBillingAddress {
email_address: request.customer_email.clone(),
phone_number: request.customer_phone.clone(),
first_name: request.customer_first_name.clone(),
last_name: request.customer_last_name.clone(),
}),
};
run_async(async move {
let client = reqwest::Client::new();
let response = client
.post(&url)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.bearer_auth(&token)
.json(&pesapal_request)
.send()
.await
.map_err(|e| format!("Failed to send payment request: {}", e))?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
return Err(format!("Pesapal payment request failed ({}): {}", status, error_text));
}
// Debug: print raw response
let response_text = response.text().await
.map_err(|e| format!("Failed to read payment response: {}", e))?;
println!("=== PESAPAL PAYMENT RESPONSE ===");
println!("{}", response_text);
println!("=================================");
let pesapal_response: PesapalSubmitOrderResponse = serde_json::from_str(&response_text)
.map_err(|e| format!("Failed to parse payment response: {}", e))?;
if let Some(error) = pesapal_response.error {
return Err(format!("Pesapal payment error: {}", error));
}
Ok(PaymentResponse {
payment_url: pesapal_response.redirect_url.unwrap_or_default(),
order_tracking_id: pesapal_response.order_tracking_id.unwrap_or_default(),
merchant_reference: pesapal_response.merchant_reference.unwrap_or_default(),
status: pesapal_response.status.unwrap_or_default(),
})
})
}
/// Get Pesapal payment status
fn get_pesapal_status(
&self,
order_tracking_id: &str,
) -> Result<PaymentStatus, String> {
let token = self.pesapal_authenticate()?;
let order_tracking_id = order_tracking_id.to_string();
let url = format!(
"{}/api/Transactions/GetTransactionStatus?orderTrackingId={}",
self.get_base_url(),
order_tracking_id
);
run_async(async move {
let client = reqwest::Client::new();
let response = client
.get(&url)
.header("Accept", "application/json")
.bearer_auth(&token)
.send()
.await
.map_err(|e| format!("Failed to send status request: {}", e))?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
return Err(format!("Pesapal status request failed ({}): {}", status, error_text));
}
// Debug: print raw response
let response_text = response.text().await
.map_err(|e| format!("Failed to read status response: {}", e))?;
println!("=== PESAPAL STATUS RESPONSE ===");
println!("{}", response_text);
println!("================================");
let status_response: PesapalTransactionStatusResponse = serde_json::from_str(&response_text)
.map_err(|e| format!("Failed to parse status response: {}", e))?;
if let Some(error) = status_response.error {
return Err(format!("Pesapal status error: {}", error));
}
Ok(PaymentStatus {
order_tracking_id: order_tracking_id.to_string(),
merchant_reference: status_response.merchant_reference,
status: status_response.payment_status_description,
amount: status_response.amount,
currency: status_response.currency,
payment_method: status_response.payment_method,
transaction_id: status_response.confirmation_code,
})
})
}
}

View File

@@ -0,0 +1,630 @@
/// Rhai bindings for Money objects (Account, Asset, Transaction, PaymentClient)
use ::rhai::plugin::*;
use ::rhai::{CustomType, Dynamic, Engine, EvalAltResult, Module, TypeBuilder};
use super::models::{Account, Asset, Transaction};
use super::payments::{PaymentClient, PaymentRequest, PaymentResponse, PaymentStatus};
// ============================================================================
// Account Module
// ============================================================================
type RhaiAccount = Account;
#[export_module]
mod rhai_account_module {
use super::RhaiAccount;
#[rhai_fn(name = "new_account", return_raw)]
pub fn new_account() -> Result<RhaiAccount, Box<EvalAltResult>> {
Ok(Account::new(0))
}
#[rhai_fn(name = "owner_id", return_raw)]
pub fn set_owner_id(
account: &mut RhaiAccount,
owner_id: i64,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let owned = std::mem::take(account);
*account = owned.owner_id(owner_id as u32);
Ok(account.clone())
}
#[rhai_fn(name = "address", return_raw)]
pub fn set_address(
account: &mut RhaiAccount,
address: String,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let owned = std::mem::take(account);
*account = owned.address(address);
Ok(account.clone())
}
#[rhai_fn(name = "balance", return_raw)]
pub fn set_balance(
account: &mut RhaiAccount,
balance: f64,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let owned = std::mem::take(account);
*account = owned.balance(balance);
Ok(account.clone())
}
#[rhai_fn(name = "currency", return_raw)]
pub fn set_currency(
account: &mut RhaiAccount,
currency: String,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let owned = std::mem::take(account);
*account = owned.currency(currency);
Ok(account.clone())
}
#[rhai_fn(name = "assetid", return_raw)]
pub fn set_assetid(
account: &mut RhaiAccount,
assetid: i64,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let owned = std::mem::take(account);
*account = owned.assetid(assetid as u32);
Ok(account.clone())
}
// Getters
#[rhai_fn(name = "get_id")]
pub fn get_id(account: &mut RhaiAccount) -> i64 {
account.base_data.id as i64
}
#[rhai_fn(name = "get_owner_id")]
pub fn get_owner_id(account: &mut RhaiAccount) -> i64 {
account.owner_id as i64
}
#[rhai_fn(name = "get_address")]
pub fn get_address(account: &mut RhaiAccount) -> String {
account.address.clone()
}
#[rhai_fn(name = "get_balance")]
pub fn get_balance(account: &mut RhaiAccount) -> f64 {
account.balance
}
#[rhai_fn(name = "get_currency")]
pub fn get_currency(account: &mut RhaiAccount) -> String {
account.currency.clone()
}
}
// ============================================================================
// Asset Module
// ============================================================================
type RhaiAsset = Asset;
#[export_module]
mod rhai_asset_module {
use super::RhaiAsset;
#[rhai_fn(name = "new_asset", return_raw)]
pub fn new_asset() -> Result<RhaiAsset, Box<EvalAltResult>> {
Ok(Asset::new(0))
}
#[rhai_fn(name = "address", return_raw)]
pub fn set_address(
asset: &mut RhaiAsset,
address: String,
) -> Result<RhaiAsset, Box<EvalAltResult>> {
let owned = std::mem::take(asset);
*asset = owned.address(address);
Ok(asset.clone())
}
#[rhai_fn(name = "asset_type", return_raw)]
pub fn set_asset_type(
asset: &mut RhaiAsset,
asset_type: String,
) -> Result<RhaiAsset, Box<EvalAltResult>> {
let owned = std::mem::take(asset);
*asset = owned.asset_type(asset_type);
Ok(asset.clone())
}
#[rhai_fn(name = "issuer", return_raw)]
pub fn set_issuer(
asset: &mut RhaiAsset,
issuer: i64,
) -> Result<RhaiAsset, Box<EvalAltResult>> {
let owned = std::mem::take(asset);
*asset = owned.issuer(issuer as u32);
Ok(asset.clone())
}
#[rhai_fn(name = "supply", return_raw)]
pub fn set_supply(
asset: &mut RhaiAsset,
supply: f64,
) -> Result<RhaiAsset, Box<EvalAltResult>> {
let owned = std::mem::take(asset);
*asset = owned.supply(supply);
Ok(asset.clone())
}
// Getters
#[rhai_fn(name = "get_id")]
pub fn get_id(asset: &mut RhaiAsset) -> i64 {
asset.base_data.id as i64
}
#[rhai_fn(name = "get_address")]
pub fn get_address(asset: &mut RhaiAsset) -> String {
asset.address.clone()
}
#[rhai_fn(name = "get_asset_type")]
pub fn get_asset_type(asset: &mut RhaiAsset) -> String {
asset.asset_type.clone()
}
#[rhai_fn(name = "get_supply")]
pub fn get_supply(asset: &mut RhaiAsset) -> f64 {
asset.supply
}
}
// ============================================================================
// Transaction Module
// ============================================================================
type RhaiTransaction = Transaction;
#[export_module]
mod rhai_transaction_module {
use super::RhaiTransaction;
#[rhai_fn(name = "new_transaction", return_raw)]
pub fn new_transaction() -> Result<RhaiTransaction, Box<EvalAltResult>> {
Ok(Transaction::new(0))
}
#[rhai_fn(name = "source", return_raw)]
pub fn set_source(
tx: &mut RhaiTransaction,
source: i64,
) -> Result<RhaiTransaction, Box<EvalAltResult>> {
let owned = std::mem::take(tx);
*tx = owned.source(source as u32);
Ok(tx.clone())
}
#[rhai_fn(name = "destination", return_raw)]
pub fn set_destination(
tx: &mut RhaiTransaction,
destination: i64,
) -> Result<RhaiTransaction, Box<EvalAltResult>> {
let owned = std::mem::take(tx);
*tx = owned.destination(destination as u32);
Ok(tx.clone())
}
#[rhai_fn(name = "amount", return_raw)]
pub fn set_amount(
tx: &mut RhaiTransaction,
amount: f64,
) -> Result<RhaiTransaction, Box<EvalAltResult>> {
let owned = std::mem::take(tx);
*tx = owned.amount(amount);
Ok(tx.clone())
}
#[rhai_fn(name = "assetid", return_raw)]
pub fn set_assetid(
tx: &mut RhaiTransaction,
assetid: i64,
) -> Result<RhaiTransaction, Box<EvalAltResult>> {
let owned = std::mem::take(tx);
*tx = owned.assetid(assetid as u32);
Ok(tx.clone())
}
// Getters
#[rhai_fn(name = "get_id")]
pub fn get_id(tx: &mut RhaiTransaction) -> i64 {
tx.base_data.id as i64
}
#[rhai_fn(name = "get_source")]
pub fn get_source(tx: &mut RhaiTransaction) -> i64 {
tx.source as i64
}
#[rhai_fn(name = "get_destination")]
pub fn get_destination(tx: &mut RhaiTransaction) -> i64 {
tx.destination as i64
}
#[rhai_fn(name = "get_amount")]
pub fn get_amount(tx: &mut RhaiTransaction) -> f64 {
tx.amount
}
#[rhai_fn(name = "get_assetid")]
pub fn get_assetid(tx: &mut RhaiTransaction) -> i64 {
tx.assetid as i64
}
}
// ============================================================================
// Registration Functions
// ============================================================================
/// Register money modules with the Rhai engine
pub fn register_money_modules(parent_module: &mut Module) {
// Register custom types
parent_module.set_custom_type::<Account>("Account");
parent_module.set_custom_type::<Asset>("Asset");
parent_module.set_custom_type::<Transaction>("Transaction");
parent_module.set_custom_type::<PaymentClient>("PaymentClient");
parent_module.set_custom_type::<PaymentRequest>("PaymentRequest");
parent_module.set_custom_type::<PaymentResponse>("PaymentResponse");
parent_module.set_custom_type::<PaymentStatus>("PaymentStatus");
// Merge account functions
let account_module = exported_module!(rhai_account_module);
parent_module.merge(&account_module);
// Merge asset functions
let asset_module = exported_module!(rhai_asset_module);
parent_module.merge(&asset_module);
// Merge transaction functions
let transaction_module = exported_module!(rhai_transaction_module);
parent_module.merge(&transaction_module);
// Merge payment client functions
let payment_module = exported_module!(rhai_payment_module);
parent_module.merge(&payment_module);
// Merge ethereum wallet functions
let eth_module = exported_module!(rhai_ethereum_module);
parent_module.merge(&eth_module);
}
// ============================================================================
// Payment Provider Module
// ============================================================================
type RhaiPaymentClient = PaymentClient;
type RhaiPaymentRequest = PaymentRequest;
type RhaiPaymentResponse = PaymentResponse;
type RhaiPaymentStatus = PaymentStatus;
#[export_module]
mod rhai_payment_module {
use super::{RhaiPaymentClient, RhaiPaymentRequest, RhaiPaymentResponse, RhaiPaymentStatus};
use super::super::payments::{PaymentClient, PaymentRequest, PaymentResponse, PaymentStatus};
use ::rhai::EvalAltResult;
// PaymentClient constructors
#[rhai_fn(name = "new_payment_client_pesapal", return_raw)]
pub fn new_pesapal_client(
id: i64,
consumer_key: String,
consumer_secret: String,
) -> Result<RhaiPaymentClient, Box<EvalAltResult>> {
Ok(PaymentClient::pesapal(id as u32, consumer_key, consumer_secret))
}
#[rhai_fn(name = "new_payment_client_pesapal_sandbox", return_raw)]
pub fn new_pesapal_sandbox_client(
id: i64,
consumer_key: String,
consumer_secret: String,
) -> Result<RhaiPaymentClient, Box<EvalAltResult>> {
Ok(PaymentClient::pesapal_sandbox(id as u32, consumer_key, consumer_secret))
}
// PaymentRequest constructor and builder methods
#[rhai_fn(name = "new_payment_request", return_raw)]
pub fn new_payment_request() -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
Ok(PaymentRequest {
merchant_reference: String::new(),
amount: 0.0,
currency: String::from("USD"),
description: String::new(),
callback_url: String::new(),
redirect_url: None,
cancel_url: None,
customer_email: None,
customer_phone: None,
customer_first_name: None,
customer_last_name: None,
})
}
#[rhai_fn(name = "amount", return_raw)]
pub fn set_amount(
request: &mut RhaiPaymentRequest,
amount: f64,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.amount = amount;
Ok(request.clone())
}
#[rhai_fn(name = "currency", return_raw)]
pub fn set_currency(
request: &mut RhaiPaymentRequest,
currency: String,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.currency = currency;
Ok(request.clone())
}
#[rhai_fn(name = "description", return_raw)]
pub fn set_description(
request: &mut RhaiPaymentRequest,
description: String,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.description = description;
Ok(request.clone())
}
#[rhai_fn(name = "callback_url", return_raw)]
pub fn set_callback_url(
request: &mut RhaiPaymentRequest,
url: String,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.callback_url = url;
Ok(request.clone())
}
#[rhai_fn(name = "merchant_reference", return_raw)]
pub fn set_merchant_reference(
request: &mut RhaiPaymentRequest,
reference: String,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.merchant_reference = reference;
Ok(request.clone())
}
#[rhai_fn(name = "customer_email", return_raw)]
pub fn set_customer_email(
request: &mut RhaiPaymentRequest,
email: String,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.customer_email = Some(email);
Ok(request.clone())
}
#[rhai_fn(name = "customer_phone", return_raw)]
pub fn set_customer_phone(
request: &mut RhaiPaymentRequest,
phone: String,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.customer_phone = Some(phone);
Ok(request.clone())
}
#[rhai_fn(name = "customer_name", return_raw)]
pub fn set_customer_name(
request: &mut RhaiPaymentRequest,
first_name: String,
last_name: String,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.customer_first_name = Some(first_name);
request.customer_last_name = Some(last_name);
Ok(request.clone())
}
#[rhai_fn(name = "redirect_url", return_raw)]
pub fn set_redirect_url(
request: &mut RhaiPaymentRequest,
url: String,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.redirect_url = Some(url);
Ok(request.clone())
}
// PaymentClient methods
#[rhai_fn(name = "create_payment_link", return_raw)]
pub fn create_payment_link(
client: &mut RhaiPaymentClient,
request: RhaiPaymentRequest,
) -> Result<RhaiPaymentResponse, Box<EvalAltResult>> {
client.create_payment_link(&request)
.map_err(|e| e.into())
}
#[rhai_fn(name = "get_payment_status", return_raw)]
pub fn get_payment_status(
client: &mut RhaiPaymentClient,
order_tracking_id: String,
) -> Result<RhaiPaymentStatus, Box<EvalAltResult>> {
client.get_payment_status(&order_tracking_id)
.map_err(|e| e.into())
}
// PaymentResponse getters
#[rhai_fn(name = "get_payment_url", pure)]
pub fn get_payment_url(response: &mut RhaiPaymentResponse) -> String {
response.payment_url.clone()
}
#[rhai_fn(name = "get_order_tracking_id", pure)]
pub fn get_order_tracking_id(response: &mut RhaiPaymentResponse) -> String {
response.order_tracking_id.clone()
}
#[rhai_fn(name = "get_merchant_reference", pure)]
pub fn get_merchant_reference(response: &mut RhaiPaymentResponse) -> String {
response.merchant_reference.clone()
}
#[rhai_fn(name = "get_status", pure)]
pub fn get_response_status(response: &mut RhaiPaymentResponse) -> String {
response.status.clone()
}
// PaymentStatus getters
#[rhai_fn(name = "get_status", pure)]
pub fn get_payment_status_value(status: &mut RhaiPaymentStatus) -> String {
status.status.clone()
}
#[rhai_fn(name = "get_amount", pure)]
pub fn get_amount(status: &mut RhaiPaymentStatus) -> f64 {
status.amount
}
#[rhai_fn(name = "get_currency", pure)]
pub fn get_currency(status: &mut RhaiPaymentStatus) -> String {
status.currency.clone()
}
#[rhai_fn(name = "get_payment_method", pure)]
pub fn get_payment_method(status: &mut RhaiPaymentStatus) -> String {
status.payment_method.clone().unwrap_or_default()
}
#[rhai_fn(name = "get_transaction_id", pure)]
pub fn get_transaction_id(status: &mut RhaiPaymentStatus) -> String {
status.transaction_id.clone().unwrap_or_default()
}
}
// ============================================================================
// CustomType Implementations
// ============================================================================
impl CustomType for Account {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("Account");
}
}
impl CustomType for Asset {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("Asset");
}
}
impl CustomType for Transaction {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("Transaction");
}
}
impl CustomType for PaymentClient {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("PaymentClient");
}
}
impl CustomType for PaymentRequest {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("PaymentRequest");
}
}
impl CustomType for PaymentResponse {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("PaymentResponse");
}
}
impl CustomType for PaymentStatus {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("PaymentStatus");
}
}
// ============================================================================
// Ethereum Wallet Module (Stub Implementation)
// ============================================================================
/// Simple Ethereum wallet representation
#[derive(Debug, Clone, Default)]
pub struct EthereumWallet {
pub owner_id: u32,
pub address: String,
pub network: String,
}
impl EthereumWallet {
pub fn new() -> Self {
// Generate a mock Ethereum address (in production, use ethers-rs or similar)
use std::time::{SystemTime, UNIX_EPOCH};
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let mock_address = format!("0x{:040x}", timestamp as u128);
Self {
owner_id: 0,
address: mock_address,
network: String::from("mainnet"),
}
}
pub fn owner_id(mut self, id: u32) -> Self {
self.owner_id = id;
self
}
pub fn network(mut self, network: impl ToString) -> Self {
self.network = network.to_string();
self
}
}
type RhaiEthereumWallet = EthereumWallet;
#[export_module]
mod rhai_ethereum_module {
use super::RhaiEthereumWallet;
use ::rhai::EvalAltResult;
#[rhai_fn(name = "new_ethereum_wallet", return_raw)]
pub fn new_ethereum_wallet() -> Result<RhaiEthereumWallet, Box<EvalAltResult>> {
Ok(EthereumWallet::new())
}
#[rhai_fn(name = "owner_id", return_raw)]
pub fn set_owner_id(
wallet: &mut RhaiEthereumWallet,
owner_id: i64,
) -> Result<RhaiEthereumWallet, Box<EvalAltResult>> {
let owned = std::mem::take(wallet);
*wallet = owned.owner_id(owner_id as u32);
Ok(wallet.clone())
}
#[rhai_fn(name = "network", return_raw)]
pub fn set_network(
wallet: &mut RhaiEthereumWallet,
network: String,
) -> Result<RhaiEthereumWallet, Box<EvalAltResult>> {
let owned = std::mem::take(wallet);
*wallet = owned.network(network);
Ok(wallet.clone())
}
#[rhai_fn(name = "get_address")]
pub fn get_address(wallet: &mut RhaiEthereumWallet) -> String {
wallet.address.clone()
}
#[rhai_fn(name = "get_network")]
pub fn get_network(wallet: &mut RhaiEthereumWallet) -> String {
wallet.network.clone()
}
}
impl CustomType for EthereumWallet {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("EthereumWallet");
}
}