synor/crates/synor-economics/src/billing/payment.rs
Gulshan Yadav 17f0b4ce4b feat(economics): add Phase 12 - Economics & Billing infrastructure
Complete economics service implementation with:

- Price Oracle with TWAP (Time-Weighted Average Price)
  - Multi-source price aggregation
  - Configurable staleness thresholds
  - Exponential, SMA, and standard TWAP strategies

- Metering Service for L2 usage tracking
  - Storage: GB-months, retrieval bandwidth
  - Hosting: bandwidth, custom domains
  - Database: queries, vector searches
  - Compute: CPU core-hours, GPU hours, memory
  - Network: bandwidth, requests

- Billing Engine
  - Invoice generation with line items
  - Payment processing (crypto/fiat)
  - Credit management with expiration
  - Auto-pay from prepaid balance

- Pricing Tiers: Free, Standard, Premium, Enterprise
  - 0%, 10%, 20%, 30% usage discounts
  - SLA guarantees: 95%, 99%, 99.9%, 99.99%

- Cost Calculator & Estimator
  - Usage projections
  - Tier comparison recommendations
  - ROI analysis

- Docker deployment with PostgreSQL schema

All 61 tests passing.
2026-01-19 21:51:26 +05:30

345 lines
10 KiB
Rust

//! 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<String>,
/// 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<String>,
/// Block number (for on-chain payments)
pub block_number: Option<u64>,
/// Date created
pub created_at: DateTime<Utc>,
/// Date completed
pub completed_at: Option<DateTime<Utc>>,
/// Failure reason (if failed)
pub failure_reason: Option<String>,
/// Additional metadata
pub metadata: std::collections::HashMap<String, String>,
}
impl Payment {
/// Create a new payment
pub fn new(account_id: impl Into<String>, 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<String>) -> Self {
self.invoice_id = Some(invoice_id.into());
self
}
/// Add transaction hash
pub fn with_transaction(mut self, hash: impl Into<String>) -> 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<String>, value: impl Into<String>) -> 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<String>) {
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)));
}
}