add heroledger models
This commit is contained in:
@@ -1,318 +0,0 @@
|
||||
# Payment Model Usage Guide
|
||||
|
||||
This document provides comprehensive instructions for AI assistants on how to use the Payment model in the heromodels repository.
|
||||
|
||||
## Overview
|
||||
|
||||
The Payment model represents a payment transaction in the system, typically associated with company registration or subscription payments. It integrates with Stripe for payment processing and maintains comprehensive status tracking.
|
||||
|
||||
## Model Structure
|
||||
|
||||
```rust
|
||||
pub struct Payment {
|
||||
pub base_data: BaseModelData, // Auto-managed ID, timestamps, comments
|
||||
pub payment_intent_id: String, // Stripe payment intent ID
|
||||
pub company_id: u32, // Foreign key to Company
|
||||
pub payment_plan: String, // "monthly", "yearly", "two_year"
|
||||
pub setup_fee: f64, // One-time setup fee
|
||||
pub monthly_fee: f64, // Recurring monthly fee
|
||||
pub total_amount: f64, // Total amount paid
|
||||
pub currency: String, // Currency code (defaults to "usd")
|
||||
pub status: PaymentStatus, // Current payment status
|
||||
pub stripe_customer_id: Option<String>, // Stripe customer ID (set on completion)
|
||||
pub created_at: i64, // Payment creation timestamp
|
||||
pub completed_at: Option<i64>, // Payment completion timestamp
|
||||
}
|
||||
|
||||
pub enum PaymentStatus {
|
||||
Pending, // Initial state - payment created but not processed
|
||||
Processing, // Payment is being processed by Stripe
|
||||
Completed, // Payment successfully completed
|
||||
Failed, // Payment processing failed
|
||||
Refunded, // Payment was refunded
|
||||
}
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### 1. Creating a New Payment
|
||||
|
||||
```rust
|
||||
use heromodels::models::biz::{Payment, PaymentStatus};
|
||||
|
||||
// Create a new payment with required fields
|
||||
let payment = Payment::new(
|
||||
"pi_1234567890".to_string(), // Stripe payment intent ID
|
||||
company_id, // Company ID from database
|
||||
"monthly".to_string(), // Payment plan
|
||||
100.0, // Setup fee
|
||||
49.99, // Monthly fee
|
||||
149.99, // Total amount
|
||||
);
|
||||
|
||||
// Payment defaults:
|
||||
// - status: PaymentStatus::Pending
|
||||
// - currency: "usd"
|
||||
// - stripe_customer_id: None
|
||||
// - created_at: current timestamp
|
||||
// - completed_at: None
|
||||
```
|
||||
|
||||
### 2. Using Builder Pattern
|
||||
|
||||
```rust
|
||||
let payment = Payment::new(
|
||||
"pi_1234567890".to_string(),
|
||||
company_id,
|
||||
"yearly".to_string(),
|
||||
500.0,
|
||||
99.99,
|
||||
1699.88,
|
||||
)
|
||||
.currency("eur".to_string())
|
||||
.stripe_customer_id(Some("cus_existing_customer".to_string()));
|
||||
```
|
||||
|
||||
### 3. Database Operations
|
||||
|
||||
```rust
|
||||
use heromodels::db::Collection;
|
||||
|
||||
// Save payment to database
|
||||
let db = get_db()?;
|
||||
let (payment_id, saved_payment) = db.set(&payment)?;
|
||||
|
||||
// Retrieve payment by ID
|
||||
let retrieved_payment: Payment = db.get_by_id(payment_id)?.unwrap();
|
||||
|
||||
// Update payment
|
||||
let updated_payment = saved_payment.complete_payment(Some("cus_new_customer".to_string()));
|
||||
let (_, final_payment) = db.set(&updated_payment)?;
|
||||
```
|
||||
|
||||
## Payment Status Management
|
||||
|
||||
### Status Transitions
|
||||
|
||||
```rust
|
||||
// 1. Start with Pending status (default)
|
||||
let payment = Payment::new(/* ... */);
|
||||
assert!(payment.is_pending());
|
||||
|
||||
// 2. Mark as processing when Stripe starts processing
|
||||
let processing_payment = payment.process_payment();
|
||||
assert!(processing_payment.is_processing());
|
||||
|
||||
// 3. Complete payment when Stripe confirms success
|
||||
let completed_payment = processing_payment.complete_payment(Some("cus_123".to_string()));
|
||||
assert!(completed_payment.is_completed());
|
||||
assert!(completed_payment.completed_at.is_some());
|
||||
|
||||
// 4. Handle failure if payment fails
|
||||
let failed_payment = processing_payment.fail_payment();
|
||||
assert!(failed_payment.has_failed());
|
||||
|
||||
// 5. Refund if needed
|
||||
let refunded_payment = completed_payment.refund_payment();
|
||||
assert!(refunded_payment.is_refunded());
|
||||
```
|
||||
|
||||
### Status Check Methods
|
||||
|
||||
```rust
|
||||
// Check current status
|
||||
if payment.is_pending() {
|
||||
// Show "Payment Pending" UI
|
||||
} else if payment.is_processing() {
|
||||
// Show "Processing Payment" UI
|
||||
} else if payment.is_completed() {
|
||||
// Show "Payment Successful" UI
|
||||
// Enable company features
|
||||
} else if payment.has_failed() {
|
||||
// Show "Payment Failed" UI
|
||||
// Offer retry option
|
||||
} else if payment.is_refunded() {
|
||||
// Show "Payment Refunded" UI
|
||||
}
|
||||
```
|
||||
|
||||
## Integration with Company Model
|
||||
|
||||
### Complete Payment Flow
|
||||
|
||||
```rust
|
||||
use heromodels::models::biz::{Company, CompanyStatus, Payment, PaymentStatus};
|
||||
|
||||
// 1. Create company with pending payment status
|
||||
let company = Company::new(
|
||||
"TechStart Inc.".to_string(),
|
||||
"REG-TS-2024-001".to_string(),
|
||||
chrono::Utc::now().timestamp(),
|
||||
)
|
||||
.email("contact@techstart.com".to_string())
|
||||
.status(CompanyStatus::PendingPayment);
|
||||
|
||||
let (company_id, company) = db.set(&company)?;
|
||||
|
||||
// 2. Create payment for the company
|
||||
let payment = Payment::new(
|
||||
stripe_payment_intent_id,
|
||||
company_id,
|
||||
"yearly".to_string(),
|
||||
500.0, // Setup fee
|
||||
99.0, // Monthly fee
|
||||
1688.0, // Total (setup + 12 months)
|
||||
);
|
||||
|
||||
let (payment_id, payment) = db.set(&payment)?;
|
||||
|
||||
// 3. Process payment through Stripe
|
||||
let processing_payment = payment.process_payment();
|
||||
let (_, processing_payment) = db.set(&processing_payment)?;
|
||||
|
||||
// 4. On successful Stripe webhook
|
||||
let completed_payment = processing_payment.complete_payment(Some(stripe_customer_id));
|
||||
let (_, completed_payment) = db.set(&completed_payment)?;
|
||||
|
||||
// 5. Activate company
|
||||
let active_company = company.status(CompanyStatus::Active);
|
||||
let (_, active_company) = db.set(&active_company)?;
|
||||
```
|
||||
|
||||
## Database Indexing
|
||||
|
||||
The Payment model provides custom indexes for efficient querying:
|
||||
|
||||
```rust
|
||||
// Indexed fields for fast lookups:
|
||||
// - payment_intent_id: Find payment by Stripe intent ID
|
||||
// - company_id: Find all payments for a company
|
||||
// - status: Find payments by status
|
||||
|
||||
// Example queries (conceptual - actual implementation depends on your query layer)
|
||||
// let pending_payments = db.find_by_index("status", "Pending")?;
|
||||
// let company_payments = db.find_by_index("company_id", company_id.to_string())?;
|
||||
// let stripe_payment = db.find_by_index("payment_intent_id", "pi_1234567890")?;
|
||||
```
|
||||
|
||||
## Error Handling Best Practices
|
||||
|
||||
```rust
|
||||
use heromodels::db::DbError;
|
||||
|
||||
fn process_payment_flow(payment_intent_id: String, company_id: u32) -> Result<Payment, DbError> {
|
||||
let db = get_db()?;
|
||||
|
||||
// Create payment
|
||||
let payment = Payment::new(
|
||||
payment_intent_id,
|
||||
company_id,
|
||||
"monthly".to_string(),
|
||||
100.0,
|
||||
49.99,
|
||||
149.99,
|
||||
);
|
||||
|
||||
// Save to database
|
||||
let (payment_id, payment) = db.set(&payment)?;
|
||||
|
||||
// Process through Stripe (external API call)
|
||||
match process_stripe_payment(&payment.payment_intent_id) {
|
||||
Ok(stripe_customer_id) => {
|
||||
// Success: complete payment
|
||||
let completed_payment = payment.complete_payment(Some(stripe_customer_id));
|
||||
let (_, final_payment) = db.set(&completed_payment)?;
|
||||
Ok(final_payment)
|
||||
}
|
||||
Err(_) => {
|
||||
// Failure: mark as failed
|
||||
let failed_payment = payment.fail_payment();
|
||||
let (_, final_payment) = db.set(&failed_payment)?;
|
||||
Ok(final_payment)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
The Payment model includes comprehensive tests in `tests/payment.rs`. When working with payments:
|
||||
|
||||
1. **Always test status transitions**
|
||||
2. **Verify timestamp handling**
|
||||
3. **Test database persistence**
|
||||
4. **Test integration with Company model**
|
||||
5. **Test builder pattern methods**
|
||||
|
||||
```bash
|
||||
# Run payment tests
|
||||
cargo test payment
|
||||
|
||||
# Run specific test
|
||||
cargo test test_payment_completion
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### 1. Payment Retry Logic
|
||||
```rust
|
||||
fn retry_failed_payment(payment: Payment) -> Payment {
|
||||
if payment.has_failed() {
|
||||
// Reset to pending for retry
|
||||
Payment::new(
|
||||
payment.payment_intent_id,
|
||||
payment.company_id,
|
||||
payment.payment_plan,
|
||||
payment.setup_fee,
|
||||
payment.monthly_fee,
|
||||
payment.total_amount,
|
||||
)
|
||||
.currency(payment.currency)
|
||||
} else {
|
||||
payment
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Payment Summary
|
||||
```rust
|
||||
fn get_payment_summary(payment: &Payment) -> String {
|
||||
format!(
|
||||
"Payment {} for company {}: {} {} ({})",
|
||||
payment.payment_intent_id,
|
||||
payment.company_id,
|
||||
payment.total_amount,
|
||||
payment.currency.to_uppercase(),
|
||||
payment.status
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Payment Validation
|
||||
```rust
|
||||
fn validate_payment(payment: &Payment) -> Result<(), String> {
|
||||
if payment.total_amount <= 0.0 {
|
||||
return Err("Total amount must be positive".to_string());
|
||||
}
|
||||
if payment.payment_intent_id.is_empty() {
|
||||
return Err("Payment intent ID is required".to_string());
|
||||
}
|
||||
if payment.company_id == 0 {
|
||||
return Err("Valid company ID is required".to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Key Points for AI Assistants
|
||||
|
||||
1. **Always use auto-generated IDs** - Don't manually set IDs, let OurDB handle them
|
||||
2. **Follow status flow** - Pending → Processing → Completed/Failed → (optionally) Refunded
|
||||
3. **Update timestamps** - `completed_at` is automatically set when calling `complete_payment()`
|
||||
4. **Use builder pattern** - For optional fields and cleaner code
|
||||
5. **Test thoroughly** - Payment logic is critical, always verify with tests
|
||||
6. **Handle errors gracefully** - Payment failures should be tracked, not ignored
|
||||
7. **Integrate with Company** - Payments typically affect company status
|
||||
8. **Use proper indexing** - Leverage indexed fields for efficient queries
|
||||
|
||||
This model follows the heromodels patterns and integrates seamlessly with the existing codebase architecture.
|
301
heromodels/docs/prompts/v_specs_to_rust_heromodels.md
Normal file
301
heromodels/docs/prompts/v_specs_to_rust_heromodels.md
Normal file
@@ -0,0 +1,301 @@
|
||||
# AI Prompt: Convert V Language Specs to Rust Hero Models
|
||||
|
||||
## Objective
|
||||
Convert V language model specifications (`.v` files) to Rust hero models that integrate with the heromodels framework. The generated Rust models should follow the established patterns for base data embedding, indexing, fluent builder APIs, and Rhai scripting integration.
|
||||
|
||||
## V Language Input Structure Analysis
|
||||
|
||||
### V Spec Patterns to Recognize:
|
||||
1. **Module Declaration**: `module circle` or `module group`
|
||||
2. **Base Embedding**: `core.Base` - represents the base model data
|
||||
3. **Index Fields**: Fields marked with `@[index]` comments
|
||||
4. **Mutability**: Fields declared with `pub mut:`
|
||||
5. **Enums**: `pub enum Status { active, inactive, suspended }`
|
||||
6. **Nested Structs**: Embedded configuration or related data structures
|
||||
7. **Collections**: `[]u32`, `[]string`, `map[string]string`
|
||||
8. **References**: `u32` fields typically represent foreign key references
|
||||
|
||||
### Example V Spec Structure:
|
||||
```v
|
||||
module circle
|
||||
|
||||
import freeflowuniverse.herolib.hero.models.core
|
||||
|
||||
pub struct User {
|
||||
core.Base
|
||||
pub mut:
|
||||
username string @[index] // Unique username
|
||||
email []string @[index] // Multiple email addresses
|
||||
status UserStatus // Enum reference
|
||||
profile UserProfile // Nested struct
|
||||
metadata map[string]string // Key-value pairs
|
||||
}
|
||||
|
||||
pub enum UserStatus {
|
||||
active
|
||||
inactive
|
||||
suspended
|
||||
}
|
||||
|
||||
pub struct UserProfile {
|
||||
pub mut:
|
||||
full_name string
|
||||
bio string
|
||||
links map[string]string
|
||||
}
|
||||
```
|
||||
|
||||
## Rust Hero Model Conversion Rules
|
||||
|
||||
### 1. File Structure and Imports
|
||||
```rust
|
||||
use heromodels_core::{Model, BaseModelData, IndexKey};
|
||||
use heromodels_derive::model;
|
||||
use rhai::CustomType;
|
||||
use rhailib_derive::RhaiApi;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use chrono::{DateTime, Utc};
|
||||
```
|
||||
|
||||
### 2. Base Data Embedding
|
||||
- **V**: `core.Base`
|
||||
- **Rust**: `pub base_data: BaseModelData,`
|
||||
|
||||
### 3. Index Field Conversion
|
||||
- **V**: `field_name string @[index]`
|
||||
- **Rust**: `#[index] pub field_name: String,`
|
||||
|
||||
### 4. Type Mappings
|
||||
| V Type | Rust Type |
|
||||
|--------|-----------|
|
||||
| `string` | `String` |
|
||||
| `[]string` | `Vec<String>` |
|
||||
| `[]u32` | `Vec<u32>` |
|
||||
| `u32` | `u32` |
|
||||
| `u64` | `u64` |
|
||||
| `f64` | `f64` |
|
||||
| `bool` | `bool` |
|
||||
| `map[string]string` | `std::collections::HashMap<String, String>` |
|
||||
|
||||
### 5. Struct Declaration Pattern
|
||||
```rust
|
||||
/// Documentation comment describing the model
|
||||
#[model]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default, RhaiApi)]
|
||||
pub struct ModelName {
|
||||
/// Base model data
|
||||
pub base_data: BaseModelData,
|
||||
#[index]
|
||||
pub indexed_field: String,
|
||||
pub regular_field: String,
|
||||
pub optional_field: Option<String>,
|
||||
pub nested_struct: NestedType,
|
||||
pub collection: Vec<u32>,
|
||||
pub metadata: std::collections::HashMap<String, String>,
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Enum Conversion
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum UserStatus {
|
||||
Active,
|
||||
Inactive,
|
||||
Suspended,
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Fluent Builder Implementation
|
||||
Every model must implement a fluent builder pattern:
|
||||
|
||||
```rust
|
||||
impl ModelName {
|
||||
/// Create a new instance
|
||||
pub fn new(id: u32) -> Self {
|
||||
Self {
|
||||
base_data: BaseModelData::new(id),
|
||||
indexed_field: String::new(),
|
||||
regular_field: String::new(),
|
||||
optional_field: None,
|
||||
nested_struct: NestedType::new(),
|
||||
collection: Vec::new(),
|
||||
metadata: std::collections::HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set indexed field (fluent)
|
||||
pub fn indexed_field(mut self, value: impl ToString) -> Self {
|
||||
self.indexed_field = value.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set regular field (fluent)
|
||||
pub fn regular_field(mut self, value: impl ToString) -> Self {
|
||||
self.regular_field = value.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set optional field (fluent)
|
||||
pub fn optional_field(mut self, value: impl ToString) -> Self {
|
||||
self.optional_field = Some(value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set nested struct (fluent)
|
||||
pub fn nested_struct(mut self, value: NestedType) -> Self {
|
||||
self.nested_struct = value;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add to collection (fluent)
|
||||
pub fn add_to_collection(mut self, value: u32) -> Self {
|
||||
self.collection.push(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set entire collection (fluent)
|
||||
pub fn collection(mut self, value: Vec<u32>) -> Self {
|
||||
self.collection = value;
|
||||
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
|
||||
}
|
||||
|
||||
/// Build the final instance
|
||||
pub fn build(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8. Model Trait Implementation
|
||||
```rust
|
||||
impl Model for ModelName {
|
||||
fn db_prefix() -> &'static str {
|
||||
"modelname"
|
||||
}
|
||||
|
||||
fn get_id(&self) -> u32 {
|
||||
self.base_data.id
|
||||
}
|
||||
|
||||
fn base_data_mut(&mut self) -> &mut BaseModelData {
|
||||
&mut self.base_data
|
||||
}
|
||||
|
||||
fn db_keys(&self) -> Vec<IndexKey> {
|
||||
let mut keys = Vec::new();
|
||||
|
||||
// Add index keys for fields marked with #[index]
|
||||
keys.push(IndexKey::new("indexed_field", &self.indexed_field));
|
||||
|
||||
// Add additional index keys as needed
|
||||
keys
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9. Nested Struct Builder Pattern
|
||||
For embedded types, implement similar builder patterns:
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct NestedType {
|
||||
pub field1: String,
|
||||
pub field2: String,
|
||||
}
|
||||
|
||||
impl NestedType {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
field1: String::new(),
|
||||
field2: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn field1(mut self, value: impl ToString) -> Self {
|
||||
self.field1 = value.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn field2(mut self, value: impl ToString) -> Self {
|
||||
self.field2 = value.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Conversion Steps
|
||||
|
||||
1. **Analyze V Spec Structure**
|
||||
- Identify the module name and main structs
|
||||
- Note which fields are marked with `@[index]`
|
||||
- Identify nested structs and enums
|
||||
- Map field types from V to Rust
|
||||
|
||||
2. **Create Rust File Structure**
|
||||
- Add appropriate imports
|
||||
- Convert enums first (they're often referenced by structs)
|
||||
- Convert nested structs before main structs
|
||||
|
||||
3. **Implement Main Struct**
|
||||
- Add `#[model]` macro and derives
|
||||
- Embed `BaseModelData` as `base_data`
|
||||
- Mark indexed fields with `#[index]`
|
||||
- Convert field types according to mapping table
|
||||
|
||||
4. **Implement Builder Pattern**
|
||||
- Add `new(id: u32)` constructor
|
||||
- Add fluent setter methods for each field
|
||||
- Handle optional fields appropriately
|
||||
- Add collection manipulation methods
|
||||
|
||||
5. **Implement Model Trait**
|
||||
- Define appropriate `db_prefix`
|
||||
- Implement required trait methods
|
||||
- Add index keys for searchable fields
|
||||
|
||||
6. **Add Documentation**
|
||||
- Document the struct and its purpose
|
||||
- Document each field's meaning
|
||||
- Add usage examples in comments
|
||||
|
||||
## Example Usage After Conversion
|
||||
|
||||
```rust
|
||||
let user = User::new(1)
|
||||
.username("john_doe")
|
||||
.add_email("john@example.com")
|
||||
.add_email("john.doe@company.com")
|
||||
.status(UserStatus::Active)
|
||||
.profile(
|
||||
UserProfile::new()
|
||||
.full_name("John Doe")
|
||||
.bio("Software developer")
|
||||
.build()
|
||||
)
|
||||
.add_metadata("department", "engineering")
|
||||
.build();
|
||||
```
|
||||
|
||||
## Notes and Best Practices
|
||||
|
||||
1. **Field Naming**: Convert V snake_case to Rust snake_case (usually no change needed)
|
||||
2. **Optional Fields**: Use `Option<T>` for fields that may be empty in V
|
||||
3. **Collections**: Always provide both `add_item` and `set_collection` methods
|
||||
4. **Error Handling**: Builder methods should not panic; use appropriate defaults
|
||||
5. **Documentation**: Include comprehensive documentation for public APIs
|
||||
6. **Testing**: Consider adding unit tests for builder patterns
|
||||
7. **Validation**: Add validation logic in builder methods if needed
|
||||
|
||||
## File Organization
|
||||
|
||||
Place the converted Rust models in the appropriate subdirectory under `heromodels/src/models/` based on the domain (e.g., `user/`, `finance/`, `governance/`, etc.).
|
Reference in New Issue
Block a user