//! Payment Processing use crate::{AccountId, SynorDecimal}; use chrono::{DateTime, Utc}; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use std::time::{SystemTime, UNIX_EPOCH}; /// Payment method #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum PaymentMethod { /// Direct SYNOR transfer SynorTransfer, /// Prepaid account balance PrepaidBalance, /// Credit/promotional balance CreditBalance, /// Smart contract escrow SmartContract, /// Cross-chain bridge payment CrossChain, /// Fiat on-ramp (converted to SYNOR) FiatOnRamp, } impl std::fmt::Display for PaymentMethod { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { PaymentMethod::SynorTransfer => write!(f, "SYNOR Transfer"), PaymentMethod::PrepaidBalance => write!(f, "Prepaid Balance"), PaymentMethod::CreditBalance => write!(f, "Credit Balance"), PaymentMethod::SmartContract => write!(f, "Smart Contract"), PaymentMethod::CrossChain => write!(f, "Cross-Chain"), PaymentMethod::FiatOnRamp => write!(f, "Fiat On-Ramp"), } } } /// Payment status #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum PaymentStatus { /// Payment initiated Pending, /// Payment being processed Processing, /// Payment confirmed Confirmed, /// Payment completed successfully Completed, /// Payment failed Failed, /// Payment refunded Refunded, } impl PaymentStatus { /// Check if this is a terminal status pub fn is_terminal(&self) -> bool { matches!(self, Self::Completed | Self::Failed | Self::Refunded) } /// Check if payment was successful pub fn is_success(&self) -> bool { matches!(self, Self::Completed) } } /// Payment record #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Payment { /// Unique payment ID pub id: String, /// Account making the payment pub account_id: AccountId, /// Invoice being paid (optional) pub invoice_id: Option, /// Payment amount in SYNOR pub amount: SynorDecimal, /// Payment method used pub method: PaymentMethod, /// Payment status pub status: PaymentStatus, /// Transaction hash (for on-chain payments) pub transaction_hash: Option, /// Block number (for on-chain payments) pub block_number: Option, /// Date created pub created_at: DateTime, /// Date completed pub completed_at: Option>, /// Failure reason (if failed) pub failure_reason: Option, /// Additional metadata pub metadata: std::collections::HashMap, } impl Payment { /// Create a new payment pub fn new(account_id: impl Into, amount: SynorDecimal, method: PaymentMethod) -> Self { Self { id: generate_payment_id(), account_id: account_id.into(), invoice_id: None, amount, method, status: PaymentStatus::Pending, transaction_hash: None, block_number: None, created_at: Utc::now(), completed_at: None, failure_reason: None, metadata: std::collections::HashMap::new(), } } /// Associate with invoice pub fn with_invoice(mut self, invoice_id: impl Into) -> Self { self.invoice_id = Some(invoice_id.into()); self } /// Add transaction hash pub fn with_transaction(mut self, hash: impl Into) -> Self { self.transaction_hash = Some(hash.into()); self } /// Add block number pub fn with_block(mut self, block: u64) -> Self { self.block_number = Some(block); self } /// Add metadata pub fn with_metadata(mut self, key: impl Into, value: impl Into) -> Self { self.metadata.insert(key.into(), value.into()); self } /// Mark payment as processing pub fn mark_processing(&mut self) { self.status = PaymentStatus::Processing; } /// Mark payment as confirmed (awaiting finality) pub fn mark_confirmed(&mut self, tx_hash: String) { self.status = PaymentStatus::Confirmed; self.transaction_hash = Some(tx_hash); } /// Mark payment as completed pub fn mark_completed(&mut self) { self.status = PaymentStatus::Completed; self.completed_at = Some(Utc::now()); } /// Mark payment as failed pub fn mark_failed(&mut self, reason: impl Into) { self.status = PaymentStatus::Failed; self.failure_reason = Some(reason.into()); self.completed_at = Some(Utc::now()); } /// Mark payment as refunded pub fn mark_refunded(&mut self) { self.status = PaymentStatus::Refunded; self.completed_at = Some(Utc::now()); } /// Check if payment is complete pub fn is_complete(&self) -> bool { self.status == PaymentStatus::Completed } /// Check if payment is pending pub fn is_pending(&self) -> bool { matches!( self.status, PaymentStatus::Pending | PaymentStatus::Processing | PaymentStatus::Confirmed ) } } /// Generate unique payment ID fn generate_payment_id() -> String { let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_nanos(); format!("pay_{:x}", nanos) } /// Payment processor for handling different payment methods pub struct PaymentProcessor; impl PaymentProcessor { /// Process a SYNOR transfer payment pub async fn process_synor_transfer( payment: &mut Payment, from_address: &str, to_address: &str, ) -> Result<(), PaymentError> { // In production, this would: // 1. Create and broadcast a transaction // 2. Wait for confirmation // 3. Update payment status payment.mark_processing(); // Simulate transaction let tx_hash = format!("0x{:x}000000000000000000000000000000000000000000000000000000000000", std::time::SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs()); payment.mark_confirmed(tx_hash); // Add addresses to metadata payment.metadata.insert("from".to_string(), from_address.to_string()); payment.metadata.insert("to".to_string(), to_address.to_string()); payment.mark_completed(); Ok(()) } /// Process prepaid balance payment pub async fn process_prepaid( payment: &mut Payment, available_balance: SynorDecimal, ) -> Result<(), PaymentError> { if available_balance < payment.amount { payment.mark_failed("Insufficient prepaid balance"); return Err(PaymentError::InsufficientBalance); } payment.mark_processing(); payment.mark_completed(); Ok(()) } /// Validate payment request pub fn validate(payment: &Payment) -> Result<(), PaymentError> { if payment.amount <= Decimal::ZERO { return Err(PaymentError::InvalidAmount); } if payment.account_id.is_empty() { return Err(PaymentError::InvalidAccount); } Ok(()) } } /// Payment processing errors #[derive(Debug, Clone, PartialEq, Eq)] pub enum PaymentError { /// Invalid payment amount InvalidAmount, /// Invalid account InvalidAccount, /// Insufficient balance InsufficientBalance, /// Transaction failed TransactionFailed(String), /// Network error NetworkError(String), } impl std::fmt::Display for PaymentError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { PaymentError::InvalidAmount => write!(f, "Invalid payment amount"), PaymentError::InvalidAccount => write!(f, "Invalid account"), PaymentError::InsufficientBalance => write!(f, "Insufficient balance"), PaymentError::TransactionFailed(msg) => write!(f, "Transaction failed: {}", msg), PaymentError::NetworkError(msg) => write!(f, "Network error: {}", msg), } } } impl std::error::Error for PaymentError {} #[cfg(test)] mod tests { use super::*; use rust_decimal_macros::dec; #[test] fn test_payment_creation() { let payment = Payment::new("account_123", dec!(50), PaymentMethod::SynorTransfer) .with_invoice("inv_456"); assert_eq!(payment.account_id, "account_123"); assert_eq!(payment.amount, dec!(50)); assert_eq!(payment.invoice_id, Some("inv_456".to_string())); assert_eq!(payment.status, PaymentStatus::Pending); } #[test] fn test_payment_flow() { let mut payment = Payment::new("test", dec!(100), PaymentMethod::SynorTransfer); assert!(payment.is_pending()); payment.mark_processing(); assert!(payment.is_pending()); payment.mark_confirmed("0x123".to_string()); assert!(payment.is_pending()); payment.mark_completed(); assert!(payment.is_complete()); assert!(payment.completed_at.is_some()); } #[test] fn test_payment_failure() { let mut payment = Payment::new("test", dec!(100), PaymentMethod::SynorTransfer); payment.mark_failed("Insufficient funds"); assert!(!payment.is_complete()); assert_eq!(payment.failure_reason, Some("Insufficient funds".to_string())); } #[tokio::test] async fn test_prepaid_processing() { let mut payment = Payment::new("test", dec!(50), PaymentMethod::PrepaidBalance); // Sufficient balance let result = PaymentProcessor::process_prepaid(&mut payment, dec!(100)).await; assert!(result.is_ok()); assert!(payment.is_complete()); // Insufficient balance let mut payment2 = Payment::new("test", dec!(150), PaymentMethod::PrepaidBalance); let result = PaymentProcessor::process_prepaid(&mut payment2, dec!(100)).await; assert!(matches!(result, Err(PaymentError::InsufficientBalance))); } }