feat: Add new Rhai example demonstrating payment flow

- Add a new Rhai example showcasing complete payment flow
  for company registration. This includes company creation,
  payment record creation, successful payment processing,
  status updates, and handling failed and refunded payments.
- Add new example demonstrating payment integration in Rhai
  scripting.  This example showcases the usage of various
  payment status methods and verifies data from the database
  after processing payments.
- Add new examples to Cargo.toml to facilitate building and
  running the examples.  This makes it easier to integrate
  and test payment functionality using Rhai scripting.
This commit is contained in:
Mahmoud-Emad
2025-06-25 18:39:47 +03:00
parent f0a0dd6d73
commit af83035d89
12 changed files with 2717 additions and 273 deletions

View File

@@ -1,19 +1,21 @@
use serde::{Deserialize, Serialize};
use heromodels_core::{BaseModelData, Model, IndexKey, IndexKeyBuilder, Index};
use rhai::{CustomType, TypeBuilder}; // For #[derive(CustomType)]
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize}; // For #[derive(CustomType)]
// --- Enums ---
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum CompanyStatus {
Active,
Inactive,
Suspended,
PendingPayment, // Company created but payment not completed
Active, // Payment completed, company is active
Suspended, // Company suspended (e.g., payment issues)
Inactive, // Company deactivated
}
impl Default for CompanyStatus {
fn default() -> Self {
CompanyStatus::Inactive
CompanyStatus::PendingPayment
}
}
@@ -34,6 +36,7 @@ impl Default for BusinessType {
// --- Company Struct ---
#[model]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, CustomType)] // Added CustomType
pub struct Company {
pub base_data: BaseModelData,
@@ -51,55 +54,11 @@ pub struct Company {
pub status: CompanyStatus,
}
// --- Model Trait Implementation ---
impl Model for Company {
fn db_prefix() -> &'static str {
"company"
}
fn get_id(&self) -> u32 {
self.base_data.id
}
fn base_data_mut(&mut self) -> &mut BaseModelData {
&mut self.base_data
}
// Override db_keys to provide custom indexes if needed
fn db_keys(&self) -> Vec<IndexKey> {
vec![
IndexKeyBuilder::new("name").value(self.name.clone()).build(),
IndexKeyBuilder::new("registration_number").value(self.registration_number.clone()).build(),
// Add other relevant keys, e.g., by status or type if frequently queried
]
}
}
// --- Index Implementations (Example) ---
pub struct CompanyNameIndex;
impl Index for CompanyNameIndex {
type Model = Company;
type Key = str;
fn key() -> &'static str {
"name"
}
}
pub struct CompanyRegistrationNumberIndex;
impl Index for CompanyRegistrationNumberIndex {
type Model = Company;
type Key = str;
fn key() -> &'static str {
"registration_number"
}
}
// --- Builder Pattern ---
impl Company {
pub fn new(name: String, registration_number: String, incorporation_date: i64) -> Self { // incorporation_date to i64
pub fn new(name: String, registration_number: String, incorporation_date: i64) -> Self {
// incorporation_date to i64
Self {
base_data: BaseModelData::new(),
name,

View File

@@ -2,23 +2,24 @@
// Sub-modules will be declared here
pub mod company;
pub mod payment;
pub mod product;
// pub mod sale;
// pub mod shareholder;
// pub mod user;
// Re-export main types from sub-modules
pub use company::{Company, CompanyStatus, BusinessType};
pub use company::{BusinessType, Company, CompanyStatus};
pub use payment::{Payment, PaymentStatus};
pub mod shareholder;
pub use product::{Product, ProductComponent, ProductStatus, ProductType};
pub use shareholder::{Shareholder, ShareholderType};
pub use product::{Product, ProductType, ProductStatus, ProductComponent};
pub mod sale;
pub use sale::{Sale, SaleItem, SaleStatus};
// pub use user::{User}; // Assuming a simple User model for now
#[cfg(feature = "rhai")]
pub mod rhai;
#[cfg(feature = "rhai")]

View File

@@ -0,0 +1,216 @@
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize}; // For #[derive(CustomType)]
// --- Enums ---
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum PaymentStatus {
Pending,
Processing,
Completed,
Failed,
Refunded,
}
impl std::fmt::Display for PaymentStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PaymentStatus::Pending => write!(f, "Pending"),
PaymentStatus::Processing => write!(f, "Processing"),
PaymentStatus::Completed => write!(f, "Completed"),
PaymentStatus::Failed => write!(f, "Failed"),
PaymentStatus::Refunded => write!(f, "Refunded"),
}
}
}
impl Default for PaymentStatus {
fn default() -> Self {
PaymentStatus::Pending
}
}
// --- Payment Struct ---
#[model]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
pub struct Payment {
pub base_data: BaseModelData,
// Stripe payment intent ID for tracking
#[index]
pub payment_intent_id: String,
// Reference to the company this payment is for
#[index]
pub company_id: u32,
// Payment plan details
pub payment_plan: String, // "monthly", "yearly", "two_year"
pub setup_fee: f64,
pub monthly_fee: f64,
pub total_amount: f64,
pub currency: String, // "usd"
pub status: PaymentStatus,
pub stripe_customer_id: Option<String>,
pub created_at: i64, // Timestamp
pub completed_at: Option<i64>, // Completion timestamp
}
// Model trait implementation is automatically generated by #[model] attribute
// --- Builder Pattern ---
impl Payment {
pub fn new(
payment_intent_id: String,
company_id: u32,
payment_plan: String,
setup_fee: f64,
monthly_fee: f64,
total_amount: f64,
) -> Self {
let now = chrono::Utc::now().timestamp();
Self {
base_data: BaseModelData::new(),
payment_intent_id,
company_id,
payment_plan,
setup_fee,
monthly_fee,
total_amount,
currency: "usd".to_string(), // Default to USD
status: PaymentStatus::default(),
stripe_customer_id: None,
created_at: now,
completed_at: None,
}
}
pub fn payment_intent_id(mut self, payment_intent_id: String) -> Self {
self.payment_intent_id = payment_intent_id;
self
}
pub fn company_id(mut self, company_id: u32) -> Self {
self.company_id = company_id;
self
}
pub fn payment_plan(mut self, payment_plan: String) -> Self {
self.payment_plan = payment_plan;
self
}
pub fn setup_fee(mut self, setup_fee: f64) -> Self {
self.setup_fee = setup_fee;
self
}
pub fn monthly_fee(mut self, monthly_fee: f64) -> Self {
self.monthly_fee = monthly_fee;
self
}
pub fn total_amount(mut self, total_amount: f64) -> Self {
self.total_amount = total_amount;
self
}
pub fn status(mut self, status: PaymentStatus) -> Self {
self.status = status;
self
}
pub fn stripe_customer_id(mut self, stripe_customer_id: Option<String>) -> Self {
self.stripe_customer_id = stripe_customer_id;
self
}
pub fn currency(mut self, currency: String) -> Self {
self.currency = currency;
self
}
pub fn created_at(mut self, created_at: i64) -> Self {
self.created_at = created_at;
self
}
pub fn completed_at(mut self, completed_at: Option<i64>) -> Self {
self.completed_at = completed_at;
self
}
// --- Business Logic Methods ---
/// Complete the payment with optional Stripe customer ID
pub fn complete_payment(mut self, stripe_customer_id: Option<String>) -> Self {
self.status = PaymentStatus::Completed;
self.stripe_customer_id = stripe_customer_id;
self.completed_at = Some(chrono::Utc::now().timestamp());
self.base_data.update_modified();
self
}
/// Mark payment as processing
pub fn process_payment(mut self) -> Self {
self.status = PaymentStatus::Processing;
self.base_data.update_modified();
self
}
/// Mark payment as failed
pub fn fail_payment(mut self) -> Self {
self.status = PaymentStatus::Failed;
self.base_data.update_modified();
self
}
/// Refund the payment
pub fn refund_payment(mut self) -> Self {
self.status = PaymentStatus::Refunded;
self.base_data.update_modified();
self
}
/// Check if payment is completed
pub fn is_completed(&self) -> bool {
self.status == PaymentStatus::Completed
}
/// Check if payment is pending
pub fn is_pending(&self) -> bool {
self.status == PaymentStatus::Pending
}
/// Check if payment is processing
pub fn is_processing(&self) -> bool {
self.status == PaymentStatus::Processing
}
/// Check if payment has failed
pub fn has_failed(&self) -> bool {
self.status == PaymentStatus::Failed
}
/// Check if payment is refunded
pub fn is_refunded(&self) -> bool {
self.status == PaymentStatus::Refunded
}
// Setter for base_data fields if needed directly
pub fn set_base_created_at(mut self, created_at: i64) -> Self {
self.base_data.created_at = created_at;
self
}
pub fn set_base_modified_at(mut self, modified_at: i64) -> Self {
self.base_data.modified_at = modified_at;
self
}
}
// Tests for Payment model are located in tests/payment.rs

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,7 @@ pub mod projects;
pub use core::Comment;
pub use userexample::User;
// pub use productexample::Product; // Temporarily remove
pub use biz::{Sale, SaleItem, SaleStatus};
pub use biz::{Payment, PaymentStatus, Sale, SaleItem, SaleStatus};
pub use calendar::{AttendanceStatus, Attendee, Calendar, Event};
pub use finance::marketplace::{Bid, BidStatus, Listing, ListingStatus, ListingType};
pub use finance::{Account, Asset, AssetType};