# 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, // Stripe customer ID (set on completion) pub created_at: i64, // Payment creation timestamp pub completed_at: Option, // 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 { 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.