synor/docs/PLAN/PHASE12-EconomicsBilling/01-Milestone-03-BillingEngine.md
Gulshan Yadav 3c9470abba a
2026-01-11 19:05:44 +05:30

13 KiB

Milestone 3: Billing Engine

Generate invoices, process payments, and manage account credits.


Overview

The Billing Engine transforms metered usage into invoices, processes SYNOR payments, and manages prepaid balances and credits.


Components

3.1 Invoice Generator

// crates/synor-economics/src/billing/invoice.rs

/// Invoice for a billing period
pub struct Invoice {
    /// Invoice ID
    pub invoice_id: InvoiceId,
    /// Account ID
    pub account_id: AccountId,
    /// Billing period
    pub period_start: u64,
    pub period_end: u64,
    /// Line items
    pub line_items: Vec<LineItem>,
    /// Subtotal before discounts
    pub subtotal_micro_synor: u64,
    /// Applied discounts
    pub discounts: Vec<AppliedDiscount>,
    /// Credits applied
    pub credits_applied: u64,
    /// Total due
    pub total_due_micro_synor: u64,
    /// Invoice status
    pub status: InvoiceStatus,
    /// Due date
    pub due_date: u64,
    /// Payment transaction (if paid)
    pub payment_tx: Option<TxId>,
    /// Created at
    pub created_at: u64,
}

pub struct LineItem {
    pub service: ServiceType,
    pub resource: ResourceType,
    pub description: String,
    pub quantity: u64,
    pub unit: MeteringUnit,
    pub unit_price_micro_synor: u64,
    pub amount_micro_synor: u64,
}

pub struct AppliedDiscount {
    pub discount_type: DiscountType,
    pub description: String,
    pub amount_micro_synor: u64,
}

pub enum DiscountType {
    /// Volume discount based on usage tier
    Volume { tier: u8 },
    /// Commitment discount (reserved capacity)
    Commitment { commitment_id: CommitmentId },
    /// Promotional credit
    Promotional { promo_code: String },
    /// Referral credit
    Referral { referrer_id: AccountId },
}

pub enum InvoiceStatus {
    Draft,
    Finalized,
    Sent,
    Paid,
    Overdue,
    Voided,
}

pub struct InvoiceGenerator {
    metering: UsageAggregator,
    pricing: PricingEngine,
    discounts: DiscountEngine,
}

impl InvoiceGenerator {
    /// Generate invoice for an account
    pub async fn generate_invoice(
        &self,
        account: &AccountId,
        period_start: u64,
        period_end: u64,
    ) -> Result<Invoice, BillingError> {
        // Get usage summary
        let usage = self.metering.get_usage_summary(account, period_start, period_end).await?;

        // Convert to line items
        let line_items = self.usage_to_line_items(&usage);

        // Calculate subtotal
        let subtotal = line_items.iter().map(|li| li.amount_micro_synor).sum();

        // Apply discounts
        let discounts = self.discounts.calculate_discounts(account, &line_items).await?;
        let discount_total: u64 = discounts.iter().map(|d| d.amount_micro_synor).sum();

        // Apply credits
        let available_credits = self.get_available_credits(account).await?;
        let credits_applied = std::cmp::min(available_credits, subtotal - discount_total);

        // Calculate total
        let total_due = subtotal.saturating_sub(discount_total).saturating_sub(credits_applied);

        Ok(Invoice {
            invoice_id: InvoiceId::new(),
            account_id: account.clone(),
            period_start,
            period_end,
            line_items,
            subtotal_micro_synor: subtotal,
            discounts,
            credits_applied,
            total_due_micro_synor: total_due,
            status: InvoiceStatus::Draft,
            due_date: period_end + 30 * 24 * 3600, // 30 days
            payment_tx: None,
            created_at: current_timestamp(),
        })
    }
}

3.2 Payment Processor

// crates/synor-economics/src/billing/payment.rs

/// Payment processor for SYNOR token payments
pub struct PaymentProcessor {
    /// Synor blockchain client
    blockchain: SynorClient,
    /// Billing contract address
    billing_contract: Address,
}

pub struct PaymentRequest {
    pub invoice_id: InvoiceId,
    pub amount_micro_synor: u64,
    pub payer: Address,
}

pub struct PaymentResult {
    pub payment_id: PaymentId,
    pub tx_id: TxId,
    pub amount_micro_synor: u64,
    pub timestamp: u64,
    pub status: PaymentStatus,
}

pub enum PaymentStatus {
    Pending,
    Confirmed,
    Failed { reason: String },
}

impl PaymentProcessor {
    /// Process payment for an invoice
    pub async fn process_payment(
        &self,
        request: PaymentRequest,
    ) -> Result<PaymentResult, PaymentError> {
        // Verify invoice exists and is payable
        let invoice = self.get_invoice(&request.invoice_id).await?;
        if invoice.status != InvoiceStatus::Finalized && invoice.status != InvoiceStatus::Sent {
            return Err(PaymentError::InvoiceNotPayable);
        }

        // Build payment transaction
        let tx = self.blockchain.build_transaction(
            TransactionType::Transfer {
                to: self.billing_contract,
                amount: request.amount_micro_synor,
                memo: format!("Invoice payment: {}", request.invoice_id),
            },
            request.payer,
        )?;

        // Submit and wait for confirmation
        let tx_id = self.blockchain.submit_transaction(tx).await?;
        let confirmation = self.blockchain.wait_for_confirmation(&tx_id).await?;

        // Update invoice status
        self.mark_invoice_paid(&request.invoice_id, &tx_id).await?;

        Ok(PaymentResult {
            payment_id: PaymentId::new(),
            tx_id,
            amount_micro_synor: request.amount_micro_synor,
            timestamp: current_timestamp(),
            status: PaymentStatus::Confirmed,
        })
    }

    /// Set up auto-pay for an account
    pub async fn setup_auto_pay(
        &self,
        account: &AccountId,
        funding_source: Address,
        max_amount_per_invoice: u64,
    ) -> Result<AutoPayConfig, PaymentError> {
        // Verify funding source has sufficient balance/allowance
        // Store auto-pay configuration
        Ok(AutoPayConfig {
            account_id: account.clone(),
            funding_source,
            max_amount_per_invoice,
            enabled: true,
        })
    }
}

3.3 Credit System

// crates/synor-economics/src/billing/credit.rs

/// Account credit management
pub struct CreditManager {
    storage: CreditStorage,
}

pub struct AccountCredit {
    pub account_id: AccountId,
    pub balance_micro_synor: u64,
    pub credits: Vec<CreditEntry>,
}

pub struct CreditEntry {
    pub credit_id: CreditId,
    pub amount_micro_synor: u64,
    pub remaining_micro_synor: u64,
    pub source: CreditSource,
    pub expires_at: Option<u64>,
    pub created_at: u64,
}

pub enum CreditSource {
    /// Prepaid balance deposit
    Prepaid { tx_id: TxId },
    /// Promotional credit
    Promotional { campaign: String },
    /// Referral bonus
    Referral { referred_by: AccountId },
    /// SLA credit (compensation)
    SlaCredit { incident_id: String },
    /// Manual adjustment
    Manual { reason: String, admin: Address },
}

impl CreditManager {
    /// Add credits to an account
    pub async fn add_credits(
        &self,
        account: &AccountId,
        amount: u64,
        source: CreditSource,
        expires_at: Option<u64>,
    ) -> Result<CreditEntry, CreditError> {
        let entry = CreditEntry {
            credit_id: CreditId::new(),
            amount_micro_synor: amount,
            remaining_micro_synor: amount,
            source,
            expires_at,
            created_at: current_timestamp(),
        };

        self.storage.add_credit(account, &entry).await?;
        Ok(entry)
    }

    /// Get available credit balance
    pub async fn get_balance(&self, account: &AccountId) -> Result<u64, CreditError> {
        let credits = self.storage.get_credits(account).await?;
        let now = current_timestamp();

        let balance = credits.iter()
            .filter(|c| c.expires_at.map(|e| e > now).unwrap_or(true))
            .map(|c| c.remaining_micro_synor)
            .sum();

        Ok(balance)
    }

    /// Apply credits to an invoice
    pub async fn apply_credits(
        &self,
        account: &AccountId,
        amount: u64,
        invoice_id: &InvoiceId,
    ) -> Result<u64, CreditError> {
        let mut credits = self.storage.get_credits(account).await?;
        let now = current_timestamp();

        // Sort by expiration (FIFO for expiring credits)
        credits.sort_by_key(|c| c.expires_at.unwrap_or(u64::MAX));

        let mut remaining = amount;
        let mut applied = 0u64;

        for credit in credits.iter_mut() {
            if remaining == 0 {
                break;
            }

            // Skip expired credits
            if credit.expires_at.map(|e| e <= now).unwrap_or(false) {
                continue;
            }

            let apply_amount = std::cmp::min(credit.remaining_micro_synor, remaining);
            credit.remaining_micro_synor -= apply_amount;
            remaining -= apply_amount;
            applied += apply_amount;

            // Record usage
            self.storage.record_credit_usage(
                &credit.credit_id,
                apply_amount,
                invoice_id,
            ).await?;
        }

        Ok(applied)
    }
}

3.4 Prepaid Balance

// crates/synor-economics/src/billing/prepaid.rs

/// Prepaid balance management (pay-as-you-go with deposits)
pub struct PrepaidManager {
    credits: CreditManager,
    blockchain: SynorClient,
    deposit_address: Address,
}

impl PrepaidManager {
    /// Deposit SYNOR to prepaid balance
    pub async fn deposit(
        &self,
        account: &AccountId,
        amount: u64,
        from: Address,
    ) -> Result<DepositResult, PrepaidError> {
        // Build deposit transaction
        let tx = self.blockchain.build_transaction(
            TransactionType::Transfer {
                to: self.deposit_address,
                amount,
                memo: format!("Prepaid deposit: {}", account),
            },
            from,
        )?;

        // Submit and confirm
        let tx_id = self.blockchain.submit_transaction(tx).await?;
        self.blockchain.wait_for_confirmation(&tx_id).await?;

        // Add to credits
        self.credits.add_credits(
            account,
            amount,
            CreditSource::Prepaid { tx_id: tx_id.clone() },
            None, // Prepaid credits don't expire
        ).await?;

        Ok(DepositResult {
            tx_id,
            amount,
            new_balance: self.credits.get_balance(account).await?,
        })
    }

    /// Check if account has sufficient balance
    pub async fn has_sufficient_balance(
        &self,
        account: &AccountId,
        required: u64,
    ) -> Result<bool, PrepaidError> {
        let balance = self.credits.get_balance(account).await?;
        Ok(balance >= required)
    }

    /// Deduct from prepaid balance (for real-time billing)
    pub async fn deduct(
        &self,
        account: &AccountId,
        amount: u64,
        reason: &str,
    ) -> Result<DeductResult, PrepaidError> {
        let balance = self.credits.get_balance(account).await?;

        if balance < amount {
            return Err(PrepaidError::InsufficientBalance {
                available: balance,
                required: amount,
            });
        }

        // Create a temporary invoice for the deduction
        let invoice_id = InvoiceId::new_instant();
        self.credits.apply_credits(account, amount, &invoice_id).await?;

        Ok(DeductResult {
            amount_deducted: amount,
            remaining_balance: self.credits.get_balance(account).await?,
        })
    }
}

Tasks

  • Implement Invoice data model and generator
  • Build PaymentProcessor for SYNOR payments
  • Implement CreditManager for account credits
  • Build PrepaidManager for deposit-based billing
  • Create billing smart contract
  • Implement auto-pay functionality
  • Add invoice email/notification system
  • Build billing dashboard API
  • Implement usage alerts and spending limits

Validation Commands

# Check account balance
synor billing balance

# Get current invoice
synor billing invoice --current

# View invoice history
synor billing invoices --limit 10

# Add prepaid balance
synor billing deposit 100 SYNOR

# Set up auto-pay
synor billing auto-pay enable --max 500

# Export usage report
synor billing export --format csv --period 2026-01

API Endpoints

GET  /api/v1/billing/balance          - Get account balance
GET  /api/v1/billing/invoices         - List invoices
GET  /api/v1/billing/invoices/:id     - Get invoice details
POST /api/v1/billing/invoices/:id/pay - Pay invoice
GET  /api/v1/billing/usage            - Get usage summary
POST /api/v1/billing/deposit          - Deposit prepaid balance
GET  /api/v1/billing/credits          - List credits
POST /api/v1/billing/auto-pay         - Configure auto-pay