# 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 ```rust // 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, /// Subtotal before discounts pub subtotal_micro_synor: u64, /// Applied discounts pub discounts: Vec, /// 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, /// 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 { // 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 ```rust // 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 { // 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 { // 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 ```rust // 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, } pub struct CreditEntry { pub credit_id: CreditId, pub amount_micro_synor: u64, pub remaining_micro_synor: u64, pub source: CreditSource, pub expires_at: Option, 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, ) -> Result { 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 { 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 { 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 ```rust // 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 { // 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 { 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 { 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 ```bash # 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 ```