Skip to main content

πŸ’° NextGate Payment & Financial System Architecture

πŸ“‹ Table of Contents

  1. System Overview
  2. Core Components
  3. Payment Flow
  4. Money Movement Architecture
  5. Ledger System (Double-Entry Bookkeeping)
  6. Escrow Mechanism
  7. Domain-Specific Handling
  8. Transaction History
  9. Key Design Patterns

🎯 System Overview

NextGate uses a universal payment orchestration system that handles payments across multiple domains (Products, Events, etc.) through a unified interface while maintaining domain-specific business logic.

High-Level Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        CLIENT LAYER                              β”‚
β”‚  (Web/Mobile Apps making API calls to checkout endpoints)       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     CONTROLLER LAYER                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ ProductCheckout  β”‚  β”‚  EventCheckout   β”‚  β”‚    Wallet    β”‚  β”‚
β”‚  β”‚   Controller     β”‚  β”‚   Controller     β”‚  β”‚  Controller  β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   CHECKOUT SERVICE LAYER                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  ProductCheckoutService  β”‚  EventCheckoutService         β”‚   β”‚
β”‚  β”‚  - Create checkout       β”‚  - Create checkout            β”‚   β”‚
β”‚  β”‚  - Validate items        β”‚  - Validate tickets           β”‚   β”‚
β”‚  β”‚  - Hold inventory        β”‚  - Hold tickets               β”‚   β”‚
β”‚  β”‚  - Calculate pricing     β”‚  - Calculate pricing          β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              UNIVERSAL PAYMENT ORCHESTRATION                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚                  PaymentOrchestrator                      β”‚   β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚   β”‚
β”‚  β”‚  β”‚  β€’ Receives: (sessionId, domain: PRODUCT/EVENT)    β”‚  β”‚   β”‚
β”‚  β”‚  β”‚  β€’ Validates session status & expiration           β”‚  β”‚   β”‚
β”‚  β”‚  β”‚  β€’ Routes to appropriate payment processor         β”‚  β”‚   β”‚
β”‚  β”‚  β”‚  β€’ Handles: SUCCESS | FAILED | PENDING             β”‚  β”‚   β”‚
β”‚  β”‚  β”‚  β€’ Publishes events for async processing          β”‚  β”‚   β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                              ↓                                    β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚      UniversalCheckoutSessionService                      β”‚   β”‚
β”‚  β”‚  (Fetches session from correct domain repository)        β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                 PAYMENT PROCESSOR LAYER                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚  β”‚ WalletPayment        β”‚         β”‚ ExternalPayment      β”‚      β”‚
β”‚  β”‚ Processor            β”‚         β”‚ Processor            β”‚      β”‚
β”‚  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚         β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚      β”‚
β”‚  β”‚ β”‚ β€’ Check balance  β”‚ β”‚         β”‚ β”‚ β€’ M-Pesa         β”‚ β”‚      β”‚
β”‚  β”‚ β”‚ β€’ Extract payee  β”‚ β”‚         β”‚ β”‚ β€’ Tigo Pesa      β”‚ β”‚      β”‚
β”‚  β”‚ β”‚ β€’ Call escrow    β”‚ β”‚         β”‚ β”‚ β€’ Airtel Money   β”‚ β”‚      β”‚
β”‚  β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚         β”‚ β”‚ β€’ Cards          β”‚ β”‚      β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚      β”‚
β”‚                                    β”‚ (Not yet implemented)β”‚      β”‚
β”‚                                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              STRATEGY PATTERN - SESSION METADATA                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚         SessionMetadataExtractor Registry                 β”‚   β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚   β”‚
β”‚  β”‚  β”‚ ProductSession     β”‚    β”‚ EventSession       β”‚        β”‚   β”‚
β”‚  β”‚  β”‚ MetadataExtractor  β”‚    β”‚ MetadataExtractor  β”‚        β”‚   β”‚
β”‚  β”‚  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚    β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚        β”‚   β”‚
β”‚  β”‚  β”‚ β”‚ Extract payee: β”‚ β”‚    β”‚ β”‚ Extract payee: β”‚ β”‚        β”‚   β”‚
β”‚  β”‚  β”‚ β”‚ Shop Owner     β”‚ β”‚    β”‚ β”‚ Event Organizerβ”‚ β”‚        β”‚   β”‚
β”‚  β”‚  β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚    β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚        β”‚   β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    FINANCIAL CORE LAYER                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚                    EscrowService                          β”‚   β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚   β”‚
β”‚  β”‚  β”‚ holdMoney(session, payer, payee, amount)           β”‚  β”‚   β”‚
β”‚  β”‚  β”‚ β€’ Generate escrow number                           β”‚  β”‚   β”‚
β”‚  β”‚  β”‚ β€’ Calculate fees (5% platform fee)                 β”‚  β”‚   β”‚
β”‚  β”‚  β”‚ β€’ Create escrow entity                             β”‚  β”‚   β”‚
β”‚  β”‚  β”‚ β€’ Move money via LedgerService                     β”‚  β”‚   β”‚
β”‚  β”‚  β”‚ β€’ Create transaction history                       β”‚  β”‚   β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                              ↓                                    β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚                    LedgerService                          β”‚   β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚   β”‚
β”‚  β”‚  β”‚ Double-Entry Bookkeeping System                    β”‚  β”‚   β”‚
β”‚  β”‚  β”‚ createEntry(debitAccount, creditAccount, amount)   β”‚  β”‚   β”‚
β”‚  β”‚  β”‚ β€’ Validate accounts                                β”‚  β”‚   β”‚
β”‚  β”‚  β”‚ β€’ Generate entry number                            β”‚  β”‚   β”‚
β”‚  β”‚  β”‚ β€’ DEBIT one account                                β”‚  β”‚   β”‚
β”‚  β”‚  β”‚ β€’ CREDIT another account                           β”‚  β”‚   β”‚
β”‚  β”‚  β”‚ β€’ Update balances atomically                       β”‚  β”‚   β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                              ↓                                    β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚               TransactionHistoryService                   β”‚   β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚   β”‚
β”‚  β”‚  β”‚ β€’ Records user-facing transaction history          β”‚  β”‚   β”‚
β”‚  β”‚  β”‚ β€’ Links to ledger entries                          β”‚  β”‚   β”‚
β”‚  β”‚  β”‚ β€’ Tracks DEBIT/CREDIT/NEUTRAL directions           β”‚  β”‚   β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     DATABASE LAYER                               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Checkout       β”‚  β”‚ Escrow         β”‚  β”‚ Ledger Accounts  β”‚  β”‚
β”‚  β”‚ Sessions       β”‚  β”‚ Accounts       β”‚  β”‚ & Entries        β”‚  β”‚
β”‚  β”‚ (JSONB)        β”‚  β”‚ (Relational)   β”‚  β”‚ (Relational)     β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Wallets        β”‚  β”‚ Transaction History                  β”‚  β”‚
β”‚  β”‚ (Metadata)     β”‚  β”‚ (User-facing records)                β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                EVENT & CALLBACK LAYER (Async)                    β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚              PaymentCompletedEvent                        β”‚   β”‚
β”‚  β”‚              (Spring ApplicationEvent)                    β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                              ↓                                    β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚  β”‚ ProductPayment       β”‚         β”‚ EventPayment         β”‚      β”‚
β”‚  β”‚ CompletedListener    β”‚         β”‚ CompletedListener    β”‚      β”‚
β”‚  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚         β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚      β”‚
β”‚  β”‚ β”‚ β€’ Create orders  β”‚ β”‚         β”‚ β”‚ β€’ Create booking β”‚ β”‚      β”‚
β”‚  β”‚ β”‚ β€’ Handle group   β”‚ β”‚         β”‚ β”‚ β€’ Reserve ticketsβ”‚ β”‚      β”‚
β”‚  β”‚ β”‚ β€’ Handle install β”‚ β”‚         β”‚ β”‚ β€’ Generate QR    β”‚ β”‚      β”‚
β”‚  β”‚ β”‚ β€’ Clear cart     β”‚ β”‚         β”‚ β”‚ β€’ Send emails    β”‚ β”‚      β”‚
β”‚  β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚         β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚      β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”§ Core Components

1. PayableCheckoutSession (Contract Interface)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          PayableCheckoutSession Interface              β”‚
β”‚  (Universal contract for all checkout sessions)       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Required Methods:                                     β”‚
β”‚  β€’ getSessionId() : UUID                              β”‚
β”‚  β€’ getSessionDomain() : CheckoutSessionsDomains       β”‚
β”‚  β€’ getPayer() : AccountEntity                         β”‚
β”‚  β€’ getTotalAmount() : BigDecimal                      β”‚
β”‚  β€’ getCurrency() : String                             β”‚
β”‚  β€’ getStatus() : CheckoutSessionStatus                β”‚
β”‚  β€’ isExpired() : boolean                              β”‚
β”‚  β€’ canRetryPayment() : boolean                        β”‚
β”‚  β€’ getPaymentAttemptCount() : int                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                       ↑           ↑
                       β”‚           β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           └──────────────┐
        β”‚                                         β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ ProductCheckout   β”‚                   β”‚ EventCheckout      β”‚
β”‚ SessionEntity     β”‚                   β”‚ SessionEntity      β”‚
β”‚ (implements       β”‚                   β”‚ (implements        β”‚
β”‚  interface)       β”‚                   β”‚  interface)        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2. Payment Orchestrator

The central coordinator that:

  • βœ… Validates checkout session
  • βœ… Routes to correct payment processor
  • βœ… Handles callbacks
  • βœ… Publishes events
  • βœ… Returns unified response

3. Strategy Pattern Components

SessionMetadataExtractor Strategy

Purpose: Extract domain-specific payee information

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      SessionMetadataExtractorRegistry               β”‚
β”‚  (Routes to correct extractor based on domain)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     ↓
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        ↓                         ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Product Domain   β”‚    β”‚ Event Domain     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ extractPayee():  β”‚    β”‚ extractPayee():  β”‚
β”‚ β€’ Get shopId     β”‚    β”‚ β€’ Get eventId    β”‚
β”‚ β€’ Fetch shop     β”‚    β”‚ β€’ Fetch event    β”‚
β”‚ β€’ Return owner   β”‚    β”‚ β€’ Return organizerβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

PostPaymentHandler Strategy

Purpose: Handle domain-specific post-payment logic

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         PostPaymentHandlerRegistry                  β”‚
β”‚  (Routes to correct handler based on domain)       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     ↓
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        ↓                         ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Product Domain   β”‚    β”‚ Event Domain     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β€’ Handle group   β”‚    β”‚ β€’ Update session β”‚
β”‚ β€’ Handle install β”‚    β”‚ β€’ Async booking  β”‚
β”‚ β€’ Clear cart     β”‚    β”‚   via listener   β”‚
β”‚ β€’ Update session β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ’Έ Payment Flow - Step by Step

Step 1: User Initiates Payment

User clicks "Pay Now"
     ↓
POST /api/v1/e-commerce/checkout-sessions/{sessionId}/process-payment
     OR
POST /api/v1/e-events/checkout/{sessionId}/payment

Step 2: Controller β†’ Service

Controller receives request
     ↓
Calls: checkoutService.processPayment(sessionId)
     ↓
Service delegates to: paymentOrchestrator.processPayment(sessionId, domain)

Step 3: Payment Orchestrator Validates

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  PaymentOrchestrator.processPayment()      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                             β”‚
β”‚  1. Fetch session via UniversalCheckout     β”‚
β”‚     SessionService                          β”‚
β”‚       ↓                                     β”‚
β”‚  2. Validate session.status ==              β”‚
β”‚     PENDING_PAYMENT                         β”‚
β”‚       ↓                                     β”‚
β”‚  3. Check if session.isExpired()            β”‚
β”‚       ↓                                     β”‚
β”‚  4. Determine payment method (WALLET)       β”‚
β”‚       ↓                                     β”‚
β”‚  5. Route to WalletPaymentProcessor         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Step 4: Wallet Payment Processing

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  WalletPaymentProcessor.processPayment()        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                  β”‚
β”‚  1. Extract payer = session.getPayer()          β”‚
β”‚       ↓                                          β”‚
β”‚  2. Extract payee via SessionMetadataExtractor  β”‚
β”‚     β€’ PRODUCT β†’ Shop Owner                      β”‚
β”‚     β€’ EVENT β†’ Event Organizer                   β”‚
β”‚       ↓                                          β”‚
β”‚  3. Get wallet balance via WalletService        β”‚
β”‚       ↓                                          β”‚
β”‚  4. Validate: balance >= totalAmount            β”‚
β”‚       ↓                                          β”‚
β”‚  5. Call: escrowService.holdMoney(              β”‚
β”‚       session, payer, payee, amount)            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Step 5: Escrow Creation

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  EscrowService.holdMoney()                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                    β”‚
β”‚  1. Generate escrow number: "ESC-2025-000123"     β”‚
β”‚       ↓                                            β”‚
β”‚  2. Calculate fees:                                β”‚
β”‚     platformFee = amount Γ— 5%                     β”‚
β”‚     sellerAmount = amount - platformFee           β”‚
β”‚       ↓                                            β”‚
β”‚  3. Create EscrowAccountEntity                    β”‚
β”‚     - buyer: payer                                β”‚
β”‚     - seller: payee                               β”‚
β”‚     - totalAmount: amount                         β”‚
β”‚     - status: HELD                                β”‚
β”‚       ↓                                            β”‚
β”‚  4. Create ledger account for escrow              β”‚
β”‚       ↓                                            β”‚
β”‚  5. Move money via LedgerService:                 β”‚
β”‚     createEntry(                                  β”‚
β”‚       debit: payerWalletAccount,                  β”‚
β”‚       credit: escrowAccount,                      β”‚
β”‚       amount: totalAmount                         β”‚
β”‚     )                                             β”‚
β”‚       ↓                                            β”‚
β”‚  6. Create transaction history records            β”‚
β”‚     - PRODUCT_PURCHASE (DEBIT) for buyer         β”‚
β”‚     - ESCROW_HOLD (DEBIT) for admin tracking     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Step 6: Ledger Entry (Double-Entry Bookkeeping)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  LedgerService.createEntry()                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                      β”‚
β”‚  Input:                                              β”‚
β”‚  β€’ debitAccount: Payer's Wallet Ledger Account      β”‚
β”‚  β€’ creditAccount: Escrow Ledger Account             β”‚
β”‚  β€’ amount: 50,000 TZS                               β”‚
β”‚  β€’ type: PURCHASE                                   β”‚
β”‚                                                      β”‚
β”‚  Process:                                            β”‚
β”‚  1. Validate accounts are different                 β”‚
β”‚  2. Validate amount > 0                             β”‚
β”‚  3. Check: debitAccount.balance >= amount           β”‚
β”‚  4. Generate entry number: "LE-2025-000456"         β”‚
β”‚       ↓                                              β”‚
β”‚  5. Create LedgerEntryEntity                        β”‚
β”‚       ↓                                              β”‚
β”‚  6. Update balances ATOMICALLY:                     β”‚
β”‚     debitAccount.balance  -= 50,000                 β”‚
β”‚     creditAccount.balance += 50,000                 β”‚
β”‚       ↓                                              β”‚
β”‚  7. Save entry and accounts in transaction          β”‚
β”‚                                                      β”‚
β”‚  Result: Money moved from payer to escrow           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Step 7: Success Handling

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  PaymentOrchestrator.handleSuccessfulPayment()     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                      β”‚
β”‚  1. Update session status:                          β”‚
β”‚     PAYMENT_COMPLETED                               β”‚
β”‚       ↓                                              β”‚
β”‚  2. Set: session.escrowId = escrow.getId()          β”‚
β”‚       ↓                                              β”‚
β”‚  3. Publish: PaymentCompletedEvent                  β”‚
β”‚     (Async - handled by listeners)                  β”‚
β”‚       ↓                                              β”‚
β”‚  4. Call: paymentCallback.onPaymentSuccess()        β”‚
β”‚     (Routes to PostPaymentHandler)                  β”‚
β”‚       ↓                                              β”‚
β”‚  5. Return PaymentResponse to user                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Step 8: Async Processing (Event Listeners)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  PaymentCompletedEvent Published                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     ↓
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        ↓                         ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ ProductPayment     β”‚  β”‚ EventPayment       β”‚
β”‚ CompletedListener  β”‚  β”‚ CompletedListener  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ @Async             β”‚  β”‚ @Async             β”‚
β”‚ @Transactional     β”‚  β”‚ @Transactional     β”‚
β”‚                    β”‚  β”‚                    β”‚
β”‚ Switch by type:    β”‚  β”‚ Actions:           β”‚
β”‚                    β”‚  β”‚                    β”‚
β”‚ REGULAR_DIRECTLY:  β”‚  β”‚ 1. Create booking  β”‚
β”‚ β€’ Create order     β”‚  β”‚ 2. Reserve tickets β”‚
β”‚                    β”‚  β”‚ 3. Generate QR     β”‚
β”‚ REGULAR_CART:      β”‚  β”‚ 4. Send emails     β”‚
β”‚ β€’ Create order     β”‚  β”‚ 5. Update status:  β”‚
β”‚ β€’ Clear cart       β”‚  β”‚    COMPLETED       β”‚
β”‚                    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ GROUP_PURCHASE:    β”‚
β”‚ β€’ Join/create groupβ”‚
β”‚                    β”‚
β”‚ INSTALLMENT:       β”‚
β”‚ β€’ Create agreement β”‚
β”‚ β€’ Schedule paymentsβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ’΅ Money Movement Architecture

Account Types in the System

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              LEDGER ACCOUNT TYPES                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                            β”‚
β”‚  1. USER_WALLET                                            β”‚
β”‚     β€’ One per user                                         β”‚
β”‚     β€’ Stores user's balance                                β”‚
β”‚     β€’ Linked to WalletEntity                               β”‚
β”‚                                                            β”‚
β”‚  2. ESCROW                                                 β”‚
β”‚     β€’ One per transaction                                  β”‚
β”‚     β€’ Temporary holding account                            β”‚
β”‚     β€’ Released to seller after delivery/completion         β”‚
β”‚                                                            β”‚
β”‚  3. PLATFORM_REVENUE                                       β”‚
β”‚     β€’ Single account for entire platform                   β”‚
β”‚     β€’ Collects all platform fees (5%)                      β”‚
β”‚     β€’ Never debited (only credited)                        β”‚
β”‚                                                            β”‚
β”‚  4. PLATFORM_RESERVE                                       β”‚
β”‚     β€’ Emergency fund                                       β”‚
β”‚     β€’ For refunds, disputes, etc.                          β”‚
β”‚                                                            β”‚
β”‚  5. EXTERNAL_MONEY_IN                                      β”‚
β”‚     β€’ Virtual account                                      β”‚
β”‚     β€’ Represents money coming from outside                 β”‚
β”‚     β€’ Source for wallet top-ups                            β”‚
β”‚                                                            β”‚
β”‚  6. EXTERNAL_MONEY_OUT                                     β”‚
β”‚     β€’ Virtual account                                      β”‚
β”‚     β€’ Represents money leaving platform                    β”‚
β”‚     β€’ Destination for withdrawals                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Money Flow: Purchase Transaction

STEP 1: PAYMENT (Buyer β†’ Escrow)
═══════════════════════════════════════════════════════════

    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚   Buyer's   β”‚    50,000 TZS     β”‚   Escrow    β”‚
    β”‚   Wallet    β”‚  ─────────────→   β”‚   Account   β”‚
    β”‚  (DEBIT)    β”‚                    β”‚  (CREDIT)   β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         ↓
    Balance: 100,000 β†’ 50,000           Balance: 0 β†’ 50,000

    Ledger Entry: LE-2025-000456
    β€’ DEBIT:  Buyer Wallet     -50,000 TZS
    β€’ CREDIT: Escrow Account   +50,000 TZS


STEP 2: ESCROW RELEASE (Escrow β†’ Seller + Platform)
═══════════════════════════════════════════════════════════

    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚   Escrow    β”‚
    β”‚   Account   β”‚  47,500 TZS (95%)
    β”‚  (DEBIT)    β”‚  ─────────────→  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚             β”‚                   β”‚   Seller's  β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β”‚   Wallet    β”‚
         β”‚                            β”‚  (CREDIT)   β”‚
         β”‚                            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β”‚  2,500 TZS (5%)
         └─────────────→  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                         β”‚  Platform         β”‚
                         β”‚  Revenue Account  β”‚
                         β”‚  (CREDIT)         β”‚
                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

    Ledger Entries (Split):
    
    Entry 1: LE-2025-000457
    β€’ DEBIT:  Escrow Account      -47,500 TZS
    β€’ CREDIT: Seller Wallet       +47,500 TZS
    
    Entry 2: LE-2025-000458
    β€’ DEBIT:  Escrow Account       -2,500 TZS
    β€’ CREDIT: Platform Revenue     +2,500 TZS

    Final Escrow Balance: 50,000 β†’ 0 TZS

Money Flow: Wallet Top-Up

WALLET TOP-UP (External β†’ User Wallet)
═══════════════════════════════════════════════════════════

    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚  External       β”‚  100,000 TZS   β”‚   User's    β”‚
    β”‚  Money In       β”‚  ────────────→  β”‚   Wallet    β”‚
    β”‚  (Virtual)      β”‚                 β”‚  (CREDIT)   β”‚
    β”‚  (DEBIT)        β”‚                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

    Ledger Entry: LE-2025-000459
    β€’ DEBIT:  External Money In    -100,000 TZS (virtual)
    β€’ CREDIT: User Wallet          +100,000 TZS

    Transaction History:
    β€’ Type: WALLET_TOPUP
    β€’ Direction: CREDIT
    β€’ Amount: 100,000 TZS

Money Flow: Wallet Withdrawal

WALLET WITHDRAWAL (User Wallet β†’ External)
═══════════════════════════════════════════════════════════

    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚   User's    β”‚  50,000 TZS    β”‚  External       β”‚
    β”‚   Wallet    β”‚  ────────────→  β”‚  Money Out      β”‚
    β”‚  (DEBIT)    β”‚                 β”‚  (Virtual)      β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                 β”‚  (CREDIT)       β”‚
                                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

    Ledger Entry: LE-2025-000460
    β€’ DEBIT:  User Wallet           -50,000 TZS
    β€’ CREDIT: External Money Out    +50,000 TZS (virtual)

    Transaction History:
    β€’ Type: WALLET_WITHDRAWAL
    β€’ Direction: DEBIT
    β€’ Amount: 50,000 TZS

πŸ“Š Ledger System (Double-Entry Bookkeeping)

Core Principle

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     FUNDAMENTAL ACCOUNTING RULE                        β”‚
β”‚                                                        β”‚
β”‚     For every transaction:                            β”‚
β”‚     TOTAL DEBITS = TOTAL CREDITS                      β”‚
β”‚                                                        β”‚
β”‚     Money never appears or disappears.                β”‚
β”‚     It only moves between accounts.                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Ledger Accounts

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  LedgerAccountEntity                                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β€’ id: UUID                                          β”‚
β”‚  β€’ accountNumber: "WALLET-johndoe"                   β”‚
β”‚  β€’ accountType: USER_WALLET | ESCROW | PLATFORM_...  β”‚
β”‚  β€’ owner: AccountEntity (nullable for platform)      β”‚
β”‚  β€’ currentBalance: BigDecimal (cached)               β”‚
β”‚  β€’ currency: "TZS"                                   β”‚
β”‚  β€’ isActive: Boolean                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Ledger Entries

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  LedgerEntryEntity                                   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β€’ id: UUID                                          β”‚
β”‚  β€’ entryNumber: "LE-2025-000123"                     β”‚
β”‚  β€’ debitAccount: LedgerAccountEntity                 β”‚
β”‚  β€’ creditAccount: LedgerAccountEntity                β”‚
β”‚  β€’ amount: BigDecimal                                β”‚
β”‚  β€’ entryType: PURCHASE | ESCROW_RELEASE | REFUND     β”‚
β”‚  β€’ referenceType: "PRODUCT_CHECKOUT"                 β”‚
β”‚  β€’ referenceId: UUID (checkout session ID)           β”‚
β”‚  β€’ description: "Product purchase payment"           β”‚
β”‚  β€’ createdAt: LocalDateTime                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Example Ledger Flow

SCENARIO: User buys product for 10,000 TZS

BEFORE TRANSACTION:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Buyer Wallet:    100,000 TZS               β”‚
β”‚  Escrow Account:        0 TZS               β”‚
β”‚  Seller Wallet:    50,000 TZS               β”‚
β”‚  Platform Revenue:  5,000 TZS               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

STEP 1: Create Ledger Entry (Payment)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Entry: LE-2025-000123                      β”‚
β”‚  β€’ DEBIT:  Buyer Wallet     -10,000 TZS     β”‚
β”‚  β€’ CREDIT: Escrow           +10,000 TZS     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

AFTER PAYMENT:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Buyer Wallet:     90,000 TZS  (-10,000)   β”‚
β”‚  Escrow Account:   10,000 TZS  (+10,000)   β”‚
β”‚  Seller Wallet:    50,000 TZS  (no change) β”‚
β”‚  Platform Revenue:  5,000 TZS  (no change) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

STEP 2: Create Ledger Entries (Escrow Release)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Entry: LE-2025-000124                      β”‚
β”‚  β€’ DEBIT:  Escrow           -9,500 TZS      β”‚
β”‚  β€’ CREDIT: Seller Wallet    +9,500 TZS      β”‚
β”‚                                             β”‚
β”‚  Entry: LE-2025-000125                      β”‚
β”‚  β€’ DEBIT:  Escrow             -500 TZS      β”‚
β”‚  β€’ CREDIT: Platform Revenue   +500 TZS      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

AFTER ESCROW RELEASE:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Buyer Wallet:     90,000 TZS  (no change) β”‚
β”‚  Escrow Account:        0 TZS  (-10,000)   β”‚
β”‚  Seller Wallet:    59,500 TZS  (+9,500)    β”‚
β”‚  Platform Revenue:  5,500 TZS  (+500)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

VERIFICATION (Double-Entry Rule):
Total Debits  = 10,000 + 9,500 + 500 = 20,000 TZS βœ“
Total Credits = 10,000 + 9,500 + 500 = 20,000 TZS βœ“

πŸ”’ Escrow Mechanism

Escrow Lifecycle

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              ESCROW ACCOUNT LIFECYCLE                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

   CREATION                HELD              RELEASED
      β”‚                     β”‚                     β”‚
      ↓                     ↓                     ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  PAYMENT β”‚         β”‚  WAITING β”‚         β”‚ DELIVERY β”‚
β”‚ PROCESSEDβ”‚ ──────→ β”‚   FOR    β”‚ ──────→ β”‚ CONFIRMEDβ”‚
β”‚          β”‚         β”‚ DELIVERY β”‚         β”‚          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β”‚ (Cancel/Dispute)
                           ↓
                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                     β”‚ REFUNDED β”‚
                     β”‚    OR    β”‚
                     β”‚ DISPUTED β”‚
                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Escrow Account Structure

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  EscrowAccountEntity                                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β€’ escrowNumber: "ESC-2025-000123"                   β”‚
β”‚  β€’ checkoutSessionId: UUID                           β”‚
β”‚  β€’ sessionDomain: PRODUCT | EVENT                    β”‚
β”‚  β€’ buyer: AccountEntity                              β”‚
β”‚  β€’ seller: AccountEntity                             β”‚
β”‚  β€’ totalAmount: 50,000 TZS                           β”‚
β”‚  β€’ platformFeePercentage: 5.00%                      β”‚
β”‚  β€’ platformFeeAmount: 2,500 TZS                      β”‚
β”‚  β€’ sellerAmount: 47,500 TZS                          β”‚
β”‚  β€’ status: HELD | RELEASED | REFUNDED | DISPUTED    β”‚
β”‚  β€’ ledgerAccountId: UUID                             β”‚
β”‚  β€’ createdAt: 2025-02-16 10:30:00                   β”‚
β”‚  β€’ releasedAt: null                                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Fee Calculation

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Escrow Fee Calculation                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                            β”‚
β”‚  Total Amount:        50,000 TZS           β”‚
β”‚  Platform Fee (5%):    2,500 TZS           β”‚
β”‚  ──────────────────────────────            β”‚
β”‚  Seller Receives:     47,500 TZS           β”‚
β”‚                                            β”‚
β”‚  Formula:                                  β”‚
β”‚  platformFee = total Γ— (5 / 100)           β”‚
β”‚  sellerAmount = total - platformFee        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Escrow Operations

1. Hold Money (Payment)

escrowService.holdMoney(session, payer, payee, amount)
     ↓
1. Create escrow entity
2. Calculate fees
3. Create escrow ledger account
4. Move money: payer wallet β†’ escrow
5. Create transaction history
     ↓
Result: Money held in escrow

2. Release Money (Delivery Confirmed)

escrowService.releaseMoney(escrowId)
     ↓
1. Validate escrow.status = HELD
2. Get seller wallet ledger account
3. Get platform revenue account
4. Split money:
   β€’ 95% β†’ seller wallet
   β€’ 5%  β†’ platform revenue
5. Update escrow.status = RELEASED
6. Create transaction history
     ↓
Result: Money distributed

3. Refund Money (Order Cancelled)

escrowService.refundMoney(escrowId)
     ↓
1. Validate escrow.status = HELD or DISPUTED
2. Get buyer wallet ledger account
3. Move money: escrow β†’ buyer wallet
4. Update escrow.status = REFUNDED
5. Create transaction history
     ↓
Result: Money returned to buyer

🎭 Domain-Specific Handling

Product Domain

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  PRODUCT CHECKOUT SESSION TYPES                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

1. REGULAR_DIRECTLY
   ════════════════
   β€’ Single product, direct purchase
   β€’ "Buy Now" button
   β€’ No cart involved
   
   Flow:
   User β†’ Product Page β†’ Buy Now β†’ Checkout β†’ Payment
                                                  ↓
                                         Create Order Immediately

2. REGULAR_CART
   ════════════════
   β€’ Multiple products from cart
   β€’ Cart-based purchase
   
   Flow:
   User β†’ Add to Cart β†’ View Cart β†’ Checkout β†’ Payment
                                                   ↓
                                         Create Order + Clear Cart

3. GROUP_PURCHASE
   ════════════════
   β€’ Group buying deal
   β€’ Wait for minimum participants
   β€’ Uses groupPrice (discounted)
   
   Flow:
   User β†’ Join/Create Group β†’ Payment (wallet only)
                                  ↓
                    Group Instance Created/Joined
                                  ↓
              Wait for group completion β†’ Create Orders

4. INSTALLMENT
   ════════════════
   β€’ Buy now, pay in installments
   β€’ Down payment required
   β€’ Monthly payments scheduled
   
   Flow:
   User β†’ Select Plan β†’ Pay Down Payment (wallet only)
                                  ↓
                    Installment Agreement Created
                                  ↓
              Schedule Future Payments β†’ Create Order (maybe)

Event Domain

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  EVENT CHECKOUT SESSION                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

TICKET TYPES:
═════════════
1. PAID - Regular paid ticket
2. FREE - Free entry ticket
3. DONATION - Pay-what-you-want ticket

Flow:
User β†’ Select Tickets β†’ Add Attendee Info β†’ Payment
                                               ↓
                      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                      ↓                             ↓
                 PAID TICKET                   FREE TICKET
                      ↓                             ↓
              Create Escrow              Skip Escrow (amount = 0)
              Move Money                           ↓
                      ↓                             ↓
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              ↓
     Publish PaymentCompletedEvent
              ↓
     EventPaymentCompletedListener
              ↓
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚ 1. Create Booking      β”‚
     β”‚ 2. Reserve Tickets     β”‚
     β”‚ 3. Generate QR Codes   β”‚
     β”‚ 4. Send to Attendees   β”‚
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“ Transaction History

Transaction History provides a user-friendly view of all financial activities.

Structure

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  TransactionHistory Entity                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β€’ transactionRef: "#2025T000123"                    β”‚
β”‚  β€’ account: AccountEntity (user)                     β”‚
β”‚  β€’ type: PRODUCT_PURCHASE | WALLET_TOPUP | ...       β”‚
β”‚  β€’ direction: DEBIT | CREDIT | NEUTRAL               β”‚
β”‚  β€’ amount: 50,000 TZS                                β”‚
β”‚  β€’ title: "Product Purchase"                         β”‚
β”‚  β€’ description: "Payment for order #..."             β”‚
β”‚  β€’ ledgerEntryId: UUID (link to ledger)              β”‚
β”‚  β€’ referenceType: "PRODUCT_CHECKOUT"                 β”‚
β”‚  β€’ referenceId: UUID                                 β”‚
β”‚  β€’ status: COMPLETED | PENDING | FAILED              β”‚
β”‚  β€’ createdAt: 2025-02-16 10:30:00                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Transaction Types

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  TRANSACTION TYPES                                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                    β”‚
β”‚  WALLET OPERATIONS:                                β”‚
β”‚  β€’ WALLET_TOPUP          (CREDIT)                  β”‚
β”‚  β€’ WALLET_WITHDRAWAL     (DEBIT)                   β”‚
β”‚                                                    β”‚
β”‚  PRODUCT PURCHASES:                                β”‚
β”‚  β€’ PRODUCT_PURCHASE      (DEBIT)   - paid          β”‚
β”‚  β€’ FREE_PRODUCT          (NEUTRAL) - free          β”‚
β”‚  β€’ CASH_PRODUCT_PAYMENT  (NEUTRAL) - cash          β”‚
β”‚  β€’ PRODUCT_REFUND        (CREDIT)  - refunded      β”‚
β”‚                                                    β”‚
β”‚  EVENT PURCHASES:                                  β”‚
β”‚  β€’ EVENT_TICKET_PURCHASE (DEBIT)   - paid ticket  β”‚
β”‚  β€’ FREE_EVENT_TICKET     (NEUTRAL) - free ticket  β”‚
β”‚  β€’ CASH_EVENT_TICKET     (NEUTRAL) - cash ticket  β”‚
β”‚  β€’ EVENT_TICKET_REFUND   (CREDIT)  - refunded     β”‚
β”‚                                                    β”‚
β”‚  SELLER OPERATIONS:                                β”‚
β”‚  β€’ SALE                  (CREDIT)  - money earned  β”‚
β”‚  β€’ SALE_REFUND           (DEBIT)   - refund issuedβ”‚
β”‚                                                    β”‚
β”‚  PLATFORM OPERATIONS:                              β”‚
β”‚  β€’ PLATFORM_FEE_COLLECTED (CREDIT) - platform fee β”‚
β”‚                                                    β”‚
β”‚  ESCROW OPERATIONS:                                β”‚
β”‚  β€’ ESCROW_HOLD           (DEBIT)   - for tracking β”‚
β”‚  β€’ ESCROW_RELEASE        (CREDIT)  - for tracking β”‚
β”‚  β€’ ESCROW_REFUND         (CREDIT)  - for tracking β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Direction Types

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  TRANSACTION DIRECTIONS                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                            β”‚
β”‚  DEBIT (βˆ’)                                 β”‚
β”‚  β€’ Money leaving your wallet               β”‚
β”‚  β€’ Displayed as negative amount            β”‚
β”‚  β€’ Examples: Purchases, Withdrawals        β”‚
β”‚                                            β”‚
β”‚  CREDIT (+)                                β”‚
β”‚  β€’ Money entering your wallet              β”‚
β”‚  β€’ Displayed as positive amount            β”‚
β”‚  β€’ Examples: Top-ups, Refunds, Sales       β”‚
β”‚                                            β”‚
β”‚  NEUTRAL (β—†)                               β”‚
β”‚  β€’ No wallet impact                        β”‚
β”‚  β€’ Used for: Free items, Cash payments     β”‚
β”‚  β€’ Amount shown but doesn't affect balance β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

🎨 Key Design Patterns

1. Strategy Pattern

Purpose: Handle domain-specific logic

Components:
β€’ SessionMetadataExtractor - Extract payee
β€’ PostPaymentHandler - Handle post-payment

Benefit: Easy to add new domains (Subscriptions, etc.)

2. Contract Interface (PayableCheckoutSession)

Purpose: Universal payment interface

Benefit:
β€’ Payment system domain-agnostic
β€’ Add new checkout types easily
β€’ Type-safe polymorphism

3. Observer Pattern (Events)

Purpose: Async processing after payment

Components:
β€’ PaymentCompletedEvent
β€’ ProductPaymentCompletedListener
β€’ EventPaymentCompletedListener

Benefit:
β€’ Non-blocking payment response
β€’ Decoupled order/booking creation
β€’ Easy to add new listeners

4. Service Layer Pattern

Purpose: Separation of concerns

Layers:
Controller β†’ Service β†’ Orchestrator β†’ Processor β†’ Core

Benefit:
β€’ Clear responsibilities
β€’ Easy testing
β€’ Maintainable code

5. Repository Pattern

Purpose: Data access abstraction

Components:
β€’ ProductCheckoutSessionRepo
β€’ EventCheckoutSessionRepo
β€’ EscrowAccountRepo
β€’ LedgerAccountRepo

Benefit:
β€’ Database-agnostic
β€’ Easy to mock for testing

πŸ” Security & Validation

Payment Validation Checklist

βœ“ Session status = PENDING_PAYMENT
βœ“ Session not expired
βœ“ Session belongs to authenticated user
βœ“ Wallet is active
βœ“ Sufficient balance
βœ“ Amount > 0
βœ“ No duplicate payment (no existing escrow)
βœ“ Payment method allowed for session type
βœ“ Inventory/tickets available

Transaction Safety

βœ“ All money movements in transactions
βœ“ Double-entry bookkeeping verified
βœ“ Atomic balance updates
βœ“ Idempotent operations
βœ“ Retry mechanism for failed operations
βœ“ Audit trail via transaction history

πŸ’³ Payment Scenarios & Special Cases

Overview of Payment Types

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              PAYMENT SCENARIO MATRIX                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                β”‚
β”‚  PAID (Regular Payment)                                        β”‚
β”‚  β€’ Amount > 0                                                  β”‚
β”‚  β€’ Wallet payment (current)                                    β”‚
β”‚  β€’ External payment (future: M-Pesa, cards, etc.)             β”‚
β”‚  β€’ Creates escrow                                              β”‚
β”‚  β€’ Platform fee collected (5%)                                 β”‚
β”‚                                                                β”‚
β”‚  FREE (No Payment)                                             β”‚
β”‚  β€’ Amount = 0                                                  β”‚
β”‚  β€’ No wallet deduction                                         β”‚
β”‚  β€’ No escrow created                                           β”‚
β”‚  β€’ No platform fee                                             β”‚
β”‚  β€’ Direct order/booking creation                               β”‚
β”‚                                                                β”‚
β”‚  CASH (Physical Payment)                                       β”‚
β”‚  β€’ Amount > 0                                                  β”‚
β”‚  β€’ Payment collected physically                                β”‚
β”‚  β€’ No wallet involvement                                       β”‚
β”‚  β€’ No escrow created                                           β”‚
β”‚  β€’ No ledger entry                                             β”‚
β”‚  β€’ Tracked in transaction history only                         β”‚
β”‚                                                                β”‚
β”‚  DONATION (Pay-What-You-Want)                                  β”‚
β”‚  β€’ Amount >= 0 (user decides)                                  β”‚
β”‚  β€’ Uses wallet payment                                         β”‚
β”‚  β€’ Creates escrow if amount > 0                                β”‚
β”‚  β€’ Platform fee collected if amount > 0                        β”‚
β”‚  β€’ Limited to 1 per user                                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Scenario 1: PAID TICKET/PRODUCT (Standard Flow)

USER JOURNEY:
═════════════
User selects paid ticket/product β†’ Checkout β†’ Pay with Wallet

PAYMENT FLOW:
═════════════

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 1: Checkout Session Created                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β€’ sessionType: REGULAR_DIRECTLY / EVENT_TICKET_PURCHASE     β”‚
β”‚  β€’ pricing.total: 50,000 TZS                                 β”‚
β”‚  β€’ status: PENDING_PAYMENT                                   β”‚
β”‚  β€’ paymentIntent.provider: "WALLET"                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 2: User Initiates Payment                              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  POST /checkout/{sessionId}/process-payment                  β”‚
β”‚       ↓                                                       β”‚
β”‚  PaymentOrchestrator.processPayment()                        β”‚
β”‚       ↓                                                       β”‚
β”‚  Validates: session.getTotalAmount() > 0 βœ“                   β”‚
β”‚  Routes to: WalletPaymentProcessor                           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 3: Wallet Payment Processing                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  WalletPaymentProcessor:                                     β”‚
β”‚  1. Check wallet balance: 100,000 TZS βœ“                     β”‚
β”‚  2. Extract payee (shop owner/event organizer)               β”‚
β”‚  3. Call escrowService.holdMoney()                           β”‚
β”‚       ↓                                                       β”‚
β”‚  Escrow Created:                                             β”‚
β”‚  β€’ escrowNumber: "ESC-2025-000123"                           β”‚
β”‚  β€’ totalAmount: 50,000 TZS                                   β”‚
β”‚  β€’ platformFee: 2,500 TZS (5%)                               β”‚
β”‚  β€’ sellerAmount: 47,500 TZS                                  β”‚
β”‚  β€’ status: HELD                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 4: Ledger Entry (Double-Entry Bookkeeping)             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Entry: LE-2025-000456                                       β”‚
β”‚  β€’ DEBIT:  Buyer Wallet Account    -50,000 TZS              β”‚
β”‚  β€’ CREDIT: Escrow Account          +50,000 TZS              β”‚
β”‚       ↓                                                       β”‚
β”‚  Account Balances Updated:                                   β”‚
β”‚  β€’ Buyer Wallet:  100,000 β†’ 50,000 TZS                      β”‚
β”‚  β€’ Escrow:             0 β†’ 50,000 TZS                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 5: Transaction History Created                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Transaction 1 (Buyer's View):                               β”‚
β”‚  β€’ ref: "#2025T000789"                                       β”‚
β”‚  β€’ type: PRODUCT_PURCHASE / EVENT_TICKET_PURCHASE            β”‚
β”‚  β€’ direction: DEBIT                                          β”‚
β”‚  β€’ amount: 50,000 TZS                                        β”‚
β”‚  β€’ title: "Product Purchase" / "Ticket Purchase"             β”‚
β”‚  β€’ status: COMPLETED                                         β”‚
β”‚       ↓                                                       β”‚
β”‚  Transaction 2 (Admin Tracking):                             β”‚
β”‚  β€’ type: ESCROW_HOLD                                         β”‚
β”‚  β€’ direction: DEBIT                                          β”‚
β”‚  β€’ amount: 50,000 TZS                                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 6: Session Updated & Event Published                   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β€’ session.status β†’ PAYMENT_COMPLETED                        β”‚
β”‚  β€’ session.escrowId β†’ escrow.id                              β”‚
β”‚  β€’ session.completedAt β†’ now()                               β”‚
β”‚       ↓                                                       β”‚
β”‚  PaymentCompletedEvent published                             β”‚
β”‚       ↓                                                       β”‚
β”‚  Async Listeners:                                            β”‚
β”‚  β€’ Create order/booking                                      β”‚
β”‚  β€’ Send notifications                                        β”‚
β”‚  β€’ Clear cart (if applicable)                                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 7: Response to User                                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  PaymentResponse:                                            β”‚
β”‚  β€’ success: true                                             β”‚
β”‚  β€’ status: SUCCESS                                           β”‚
β”‚  β€’ message: "Payment processed successfully"                 β”‚
β”‚  β€’ escrowNumber: "ESC-2025-000123"                           β”‚
β”‚  β€’ amountPaid: 50,000 TZS                                    β”‚
β”‚  β€’ platformFee: 2,500 TZS                                    β”‚
β”‚  β€’ sellerAmount: 47,500 TZS                                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

RESULT:
═══════
βœ“ Money moved from buyer to escrow
βœ“ Ledger balanced
βœ“ Transaction history recorded
βœ“ Order/booking created (async)
βœ“ User receives confirmation

Scenario 2: FREE TICKET/PRODUCT (Zero Payment)

USER JOURNEY:
═════════════
User selects free ticket/product β†’ Checkout β†’ Submit (no payment)

SPECIAL CHARACTERISTICS:
════════════════════════
β€’ pricing.total = 0 TZS
β€’ No wallet deduction
β€’ No escrow created
β€’ No platform fee
β€’ Direct completion

PAYMENT FLOW:
═════════════

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 1: Checkout Session Created                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β€’ ticketPricingType: FREE                                   β”‚
β”‚  β€’ pricing.total: 0 TZS                                      β”‚
β”‚  β€’ status: PENDING_PAYMENT (initial)                         β”‚
β”‚  β€’ paymentIntent: null (no payment needed)                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 2: Automatic Processing (No User Action)               β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  EventCheckoutServiceImpl.createCheckoutSession():           β”‚
β”‚       ↓                                                       β”‚
β”‚  Detects: ticket.ticketPricingType == FREE                   β”‚
β”‚       ↓                                                       β”‚
β”‚  Sets: session.status β†’ PAYMENT_COMPLETED (immediately)      β”‚
β”‚       ↓                                                       β”‚
β”‚  Calls: processFreeTicketCheckout(session)                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 3: Free Checkout Processing                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  processFreeTicketCheckout():                                β”‚
β”‚  1. session.markAsCompleted()                                β”‚
β”‚  2. session.completedAt β†’ now()                              β”‚
β”‚  3. Save session                                             β”‚
β”‚       ↓                                                       β”‚
β”‚  NO Escrow Creation βœ—                                        β”‚
β”‚  NO Wallet Deduction βœ—                                       β”‚
β”‚  NO Ledger Entry βœ—                                           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 4: Transaction History (Optional Tracking)             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  trackFreeTransaction():                                     β”‚
β”‚  β€’ ref: "#2025T000790"                                       β”‚
β”‚  β€’ type: FREE_PRODUCT / FREE_EVENT_TICKET                    β”‚
β”‚  β€’ direction: NEUTRAL (no balance impact)                    β”‚
β”‚  β€’ amount: 0 TZS                                             β”‚
β”‚  β€’ title: "Free Ticket Booking"                              β”‚
β”‚  β€’ ledgerEntryId: null (no ledger)                           β”‚
β”‚  β€’ status: COMPLETED                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 5: Event Published                                     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  PaymentCompletedEvent(                                      β”‚
β”‚    sessionId,                                                β”‚
β”‚    domain: EVENT,                                            β”‚
β”‚    session,                                                  β”‚
β”‚    escrow: null  ← NO ESCROW FOR FREE                        β”‚
β”‚  )                                                           β”‚
β”‚       ↓                                                       β”‚
β”‚  EventPaymentCompletedListener:                              β”‚
β”‚  β€’ Detects: escrow == null (free ticket)                    β”‚
β”‚  β€’ Creates booking                                           β”‚
β”‚  β€’ Reserves tickets                                          β”‚
β”‚  β€’ Generates QR codes                                        β”‚
β”‚  β€’ Sends confirmation emails                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 6: Response to User                                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  PaymentResponse:                                            β”‚
β”‚  β€’ success: true                                             β”‚
β”‚  β€’ status: SUCCESS                                           β”‚
β”‚  β€’ message: "Free ticket booking confirmed!"                 β”‚
β”‚  β€’ amountPaid: 0 TZS                                         β”‚
β”‚  β€’ escrowId: null                                            β”‚
β”‚  β€’ escrowNumber: null                                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

COMPARISON: FREE vs PAID
═════════════════════════

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Operation        β”‚    PAID     β”‚    FREE     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Wallet Check       β”‚     YES     β”‚     NO      β”‚
β”‚ Escrow Created     β”‚     YES     β”‚     NO      β”‚
β”‚ Ledger Entry       β”‚     YES     β”‚     NO      β”‚
β”‚ Platform Fee       β”‚     YES     β”‚     NO      β”‚
β”‚ Transaction Historyβ”‚     YES     β”‚  OPTIONAL   β”‚
β”‚ Order/Booking      β”‚  ASYNC      β”‚   ASYNC     β”‚
β”‚ User Balance Impactβ”‚     YES     β”‚     NO      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

RESULT:
═══════
βœ“ No money moved
βœ“ No escrow created
βœ“ Session marked completed immediately
βœ“ Booking created (async)
βœ“ User receives free ticket

Scenario 3: CASH PAYMENT (Physical Money)

USER JOURNEY:
═════════════
User pays cash at door/venue β†’ Staff creates checkout β†’ Marks as cash

SPECIAL CHARACTERISTICS:
════════════════════════
β€’ Amount > 0 (but not from wallet)
β€’ Payment collected physically (offline)
β€’ No wallet deduction
β€’ No escrow (money never in system)
β€’ No ledger entry (money outside system)
β€’ Tracked in transaction history for records
β€’ Used for: Door sales, in-person events

IMPLEMENTATION:
═══════════════

Option 1: Payment Method Detection (Current)
────────────────────────────────────────────
PaymentOrchestrator detects paymentMethod == CASH

Option 2: Pricing Detection (Alternative)
──────────────────────────────────────────
Session has special cashPaymentIndicator flag

PAYMENT FLOW:
═════════════

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 1: Checkout Session Created (Staff/User)               β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β€’ sessionType: REGULAR_DIRECTLY / EVENT_TICKET_PURCHASE     β”‚
β”‚  β€’ pricing.total: 20,000 TZS                                 β”‚
β”‚  β€’ paymentIntent.provider: "CASH" (or detected later)        β”‚
β”‚  β€’ status: PENDING_PAYMENT                                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 2: Payment Processing                                  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  POST /checkout/{sessionId}/process-payment                  β”‚
β”‚       ↓                                                       β”‚
β”‚  PaymentOrchestrator.processPayment():                       β”‚
β”‚       ↓                                                       β”‚
β”‚  Detects: paymentMethod == CASH                              β”‚
β”‚       ↓                                                       β”‚
β”‚  Routes to: handleCashCheckout(session)                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 3: Cash Checkout Processing                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  handleCashCheckout():                                       β”‚
β”‚  1. session.status β†’ COMPLETED (immediate)                   β”‚
β”‚  2. session.completedAt β†’ now()                              β”‚
β”‚  3. Save session                                             β”‚
β”‚       ↓                                                       β”‚
β”‚  NO Wallet Check βœ—                                           β”‚
β”‚  NO Escrow Creation βœ—                                        β”‚
β”‚  NO Ledger Entry βœ—                                           β”‚
β”‚  NO Balance Deduction βœ—                                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 4: Transaction History (Record Keeping)                β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  trackCashTransaction():                                     β”‚
β”‚  β€’ ref: "#2025T000791"                                       β”‚
β”‚  β€’ type: CASH_PRODUCT_PAYMENT / CASH_EVENT_TICKET           β”‚
β”‚  β€’ direction: NEUTRAL (no wallet impact)                     β”‚
β”‚  β€’ amount: 20,000 TZS                                        β”‚
β”‚  β€’ title: "Cash Product Payment"                             β”‚
β”‚  β€’ description: "Cash payment for product (Session: ...)"    β”‚
β”‚  β€’ ledgerEntryId: null (no ledger entry)                     β”‚
β”‚  β€’ status: COMPLETED                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 5: Event Published                                     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  PaymentCompletedEvent(                                      β”‚
β”‚    sessionId,                                                β”‚
β”‚    domain: PRODUCT/EVENT,                                    β”‚
β”‚    session,                                                  β”‚
β”‚    escrow: null  ← NO ESCROW FOR CASH                        β”‚
β”‚  )                                                           β”‚
β”‚       ↓                                                       β”‚
β”‚  Listener processes:                                         β”‚
β”‚  β€’ Creates order/booking                                     β”‚
β”‚  β€’ NO escrow release needed                                  β”‚
β”‚  β€’ NO platform fee collected                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 6: Response to User                                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  PaymentResponse:                                            β”‚
β”‚  β€’ success: true                                             β”‚
β”‚  β€’ status: SUCCESS                                           β”‚
β”‚  β€’ message: "Cash payment confirmed!"                        β”‚
β”‚  β€’ paymentMethod: CASH                                       β”‚
β”‚  β€’ amountPaid: 20,000 TZS                                    β”‚
β”‚  β€’ escrowId: null                                            β”‚
β”‚  β€’ escrowNumber: null                                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

USE CASES:
══════════
1. Event Door Sales
   β€’ User arrives at venue
   β€’ Pays cash at entrance
   β€’ Staff creates checkout & marks as cash
   β€’ User receives ticket immediately

2. In-Store Purchase
   β€’ Customer buys product in physical store
   β€’ Pays with physical cash
   β€’ Staff records sale in system
   β€’ Order created for tracking

3. COD (Cash on Delivery)
   β€’ User orders online
   β€’ Chooses cash payment
   β€’ Courier collects cash on delivery
   β€’ System updated after confirmation

COMPARISON: CASH vs WALLET
═══════════════════════════

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Operation        β”‚   WALLET    β”‚    CASH     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Money in System    β”‚     YES     β”‚     NO      β”‚
β”‚ Wallet Deduction   β”‚     YES     β”‚     NO      β”‚
β”‚ Escrow Created     β”‚     YES     β”‚     NO      β”‚
β”‚ Ledger Entry       β”‚     YES     β”‚     NO      β”‚
β”‚ Platform Fee       β”‚     YES     β”‚     NO*     β”‚
β”‚ Transaction Historyβ”‚     YES     β”‚     YES     β”‚
β”‚ Balance Impact     β”‚     YES     β”‚     NO      β”‚
β”‚ Use Case           β”‚  Online     β”‚  Physical   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

* Platform fee for cash could be handled separately
  in business reconciliation, outside the system

RESULT:
═══════
βœ“ No money in digital system
βœ“ No wallet/ledger impact
βœ“ Transaction recorded for tracking
βœ“ Order/booking created
βœ“ Physical cash collected separately

Scenario 4: DONATION TICKET (Pay-What-You-Want)

USER JOURNEY:
═════════════
User sees donation event β†’ Decides amount β†’ Pays via wallet

SPECIAL CHARACTERISTICS:
════════════════════════
β€’ Amount >= 0 (user decides)
β€’ Can be 0 (free) or any amount
β€’ Only available for EVENT domain (not products)
β€’ Wallet payment required (no external payments)
β€’ Limited to 1 ticket per person
β€’ No tickets for other attendees
β€’ Online purchase only (no door sales)

TICKET TYPE:
════════════
ticketPricingType: DONATION

PAYMENT FLOW:
═════════════

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 1: User Selects Donation Amount                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Donation UI:                                                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”‚
β”‚  β”‚ How much would you like to contribute?    β”‚              β”‚
β”‚  β”‚                                            β”‚              β”‚
β”‚  β”‚ Suggested: 5,000 TZS                       β”‚              β”‚
β”‚  β”‚                                            β”‚              β”‚
β”‚  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”          β”‚              β”‚
β”‚  β”‚ β”‚ 5,000  β”‚ β”‚ 10,000 β”‚ β”‚ 20,000 β”‚          β”‚              β”‚
β”‚  β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β”‚              β”‚
β”‚  β”‚                                            β”‚              β”‚
β”‚  β”‚ Or enter custom amount: [________] TZS    β”‚              β”‚
β”‚  β”‚                                            β”‚              β”‚
β”‚  β”‚ [ ] I'll just attend for free (0 TZS)     β”‚              β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 2: Checkout Session Created                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  CreateEventCheckoutRequest:                                 β”‚
β”‚  β€’ ticketTypeId: <donation-ticket-id>                        β”‚
β”‚  β€’ ticketsForMe: 1 (must be exactly 1)                       β”‚
β”‚  β€’ donationAmount: 15,000 TZS (user chose)                   β”‚
β”‚  β€’ otherAttendees: [] (must be empty)                        β”‚
β”‚       ↓                                                       β”‚
β”‚  Validation:                                                 β”‚
β”‚  βœ“ ticketPricingType == DONATION                             β”‚
β”‚  βœ“ totalQuantity == 1 (donation tickets limited)            β”‚
β”‚  βœ“ No other attendees                                        β”‚
β”‚  βœ“ SalesChannel == ONLINE_ONLY                               β”‚
β”‚       ↓                                                       β”‚
β”‚  Session Created:                                            β”‚
β”‚  β€’ ticketDetails.unitPrice: 0 TZS (ticket is free)           β”‚
β”‚  β€’ ticketDetails.donationAmount: 15,000 TZS                  β”‚
β”‚  β€’ pricing.total: 15,000 TZS                                 β”‚
β”‚  β€’ paymentIntent.provider: "WALLET" (forced)                 β”‚
β”‚  β€’ status: PENDING_PAYMENT                                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Step 3: Payment Branch (Based on Amount)                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        ↓                                     ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Amount = 0       β”‚              β”‚ Amount > 0       β”‚
β”‚ (Free Donation)  β”‚              β”‚ (Paid Donation)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        ↓                                     ↓
   [SAME AS FREE                      [SAME AS PAID
    TICKET FLOW]                       TICKET FLOW]
        ↓                                     ↓
   No Escrow                            Escrow Created
   No Wallet                            Wallet Deducted
   Direct Complete                      Platform Fee (5%)


SCENARIO A: USER DONATES 15,000 TZS
════════════════════════════════════

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Payment Processing (Paid Donation)                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  PaymentOrchestrator:                                        β”‚
β”‚  β€’ Detects: session.getTotalAmount() = 15,000 TZS > 0       β”‚
β”‚  β€’ Routes to: WalletPaymentProcessor                         β”‚
β”‚       ↓                                                       β”‚
β”‚  WalletPaymentProcessor:                                     β”‚
β”‚  β€’ Check balance: sufficient βœ“                               β”‚
β”‚  β€’ Extract payee: event organizer                            β”‚
β”‚  β€’ Create escrow: 15,000 TZS                                 β”‚
β”‚       ↓                                                       β”‚
β”‚  Escrow Created:                                             β”‚
β”‚  β€’ totalAmount: 15,000 TZS                                   β”‚
β”‚  β€’ platformFee: 750 TZS (5%)                                 β”‚
β”‚  β€’ sellerAmount: 14,250 TZS                                  β”‚
β”‚       ↓                                                       β”‚
β”‚  Ledger Entry:                                               β”‚
β”‚  β€’ DEBIT:  Buyer Wallet    -15,000 TZS                       β”‚
β”‚  β€’ CREDIT: Escrow          +15,000 TZS                       β”‚
β”‚       ↓                                                       β”‚
β”‚  Transaction History:                                        β”‚
β”‚  β€’ type: DONATION_EVENT_TICKET                               β”‚
β”‚  β€’ direction: DEBIT                                          β”‚
β”‚  β€’ amount: 15,000 TZS                                        β”‚
β”‚  β€’ title: "Donation Ticket"                                  β”‚
β”‚  β€’ metadata: { donationAmount: 15000, suggested: 5000 }      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜


SCENARIO B: USER CHOOSES FREE (0 TZS)
═══════════════════════════════════════

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Payment Processing (Free Donation)                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  PaymentOrchestrator:                                        β”‚
β”‚  β€’ Detects: session.getTotalAmount() = 0 TZS                 β”‚
β”‚  β€’ Routes to: handleFreeCheckout()                           β”‚
β”‚       ↓                                                       β”‚
β”‚  Free Processing:                                            β”‚
β”‚  β€’ NO wallet check                                           β”‚
β”‚  β€’ NO escrow                                                 β”‚
β”‚  β€’ NO ledger entry                                           β”‚
β”‚  β€’ Direct session completion                                 β”‚
β”‚       ↓                                                       β”‚
β”‚  Transaction History (Optional):                             β”‚
β”‚  β€’ type: FREE_EVENT_TICKET (or DONATION_EVENT_TICKET)        β”‚
β”‚  β€’ direction: NEUTRAL                                        β”‚
β”‚  β€’ amount: 0 TZS                                             β”‚
β”‚  β€’ title: "Free Donation Ticket"                             β”‚
β”‚  β€’ metadata: { donationAmount: 0, suggested: 5000 }          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

VALIDATION RULES FOR DONATION:
═══════════════════════════════

βœ“ Ticket type must be DONATION
βœ“ Quantity must be exactly 1
βœ“ No other attendees allowed
βœ“ Sales channel must be ONLINE_ONLY
βœ“ Payment method forced to WALLET (no cash, no external)
βœ“ maxQuantityPerUser enforced (1 per person)

Checked in: EventCheckoutValidations.validateDonationTicket()

COMPARISON: DONATION vs REGULAR PAID
═════════════════════════════════════

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Feature            β”‚   REGULAR   β”‚  DONATION   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Fixed Price          β”‚     YES     β”‚     NO      β”‚
β”‚ User Sets Amount     β”‚     NO      β”‚     YES     β”‚
β”‚ Can be Free (0)      β”‚     NO      β”‚     YES     β”‚
β”‚ Quantity Limit       β”‚  Variable   β”‚  1 Only     β”‚
β”‚ Other Attendees      β”‚   Allowed   β”‚   Blocked   β”‚
β”‚ Sales Channel        β”‚  Any        β”‚ Online Only β”‚
β”‚ Payment Method       β”‚  Any        β”‚ Wallet Only β”‚
β”‚ Escrow (if > 0)      β”‚     YES     β”‚     YES     β”‚
β”‚ Platform Fee (if >0) β”‚     YES     β”‚     YES     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

USE CASES:
══════════

1. Charity Event
   β€’ Event organizer hosts fundraiser
   β€’ Tickets free, but donations welcomed
   β€’ Users choose contribution amount
   β€’ Platform collects fee only on donations > 0

2. Community Event
   β€’ Local meetup or workshop
   β€’ Entry is free
   β€’ Optional donation to cover costs
   β€’ Organizer receives donations minus platform fee

3. Awareness Campaign
   β€’ NGO hosts event
   β€’ Free entry for all
   β€’ Donations support the cause
   β€’ Transparent tracking via transaction history

RESULT:
═══════
βœ“ Flexible payment (free or any amount)
βœ“ User decides contribution
βœ“ Platform fee only on donations > 0
βœ“ Limited to 1 per person
βœ“ Wallet payment only
βœ“ Same escrow/ledger logic if amount > 0

🎯 Payment Scenario Decision Tree

User Initiates Payment
         ↓
    β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”
    ↓         ↓
Amount > 0?  Amount = 0?
    ↓         ↓
   YES       [FREE FLOW]
    ↓         β€’ No escrow
    ↓         β€’ No wallet
    ↓         β€’ Complete immediately
    ↓         β€’ Transaction: NEUTRAL
Payment      
Method?
    ↓
    β”œβ”€ WALLET ────→ [PAID FLOW]
    β”‚               β€’ Check balance
    β”‚               β€’ Create escrow
    β”‚               β€’ Ledger entry
    β”‚               β€’ Platform fee (5%)
    β”‚               β€’ Transaction: DEBIT
    β”‚
    β”œβ”€ CASH ──────→ [CASH FLOW]
    β”‚               β€’ No wallet
    β”‚               β€’ No escrow
    β”‚               β€’ No ledger
    β”‚               β€’ Complete immediately
    β”‚               β€’ Transaction: NEUTRAL (tracking)
    β”‚
    └─ EXTERNAL ──→ [EXTERNAL FLOW]
                    β€’ (Not yet implemented)
                    β€’ M-Pesa / Cards / etc.
                    β€’ Would follow PAID flow

πŸ’° Money Movement Summary by Scenario

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              MONEY FLOW BY PAYMENT TYPE                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

PAID (Wallet):
═══════════════
    Buyer Wallet  ──[amount]──→  Escrow Account
                                       ↓
                       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                       ↓                               ↓
               Seller Wallet                   Platform Revenue
              [95% of amount]                   [5% of amount]

FREE:
═════
    (No money movement)
    User β†’ Direct Order/Booking

CASH:
═════
    (Physical money, outside system)
    User β†’ [Physical Cash] β†’ Seller/Organizer
    System: Records transaction for tracking only

DONATION (Amount > 0):
══════════════════════
    [Same as PAID flow]
    Buyer Wallet  ──[user-chosen amount]──→  Escrow Account
                                                   ↓
                                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                   ↓                               ↓
                           Organizer Wallet                Platform Revenue
                           [95% of donation]                [5% of donation]

DONATION (Amount = 0):
══════════════════════
    [Same as FREE flow]
    (No money movement)
    User β†’ Direct Booking

πŸ“Š Transaction History by Scenario

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         TRANSACTION HISTORY ENTRIES BY SCENARIO              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

PAID Purchase (Product):
════════════════════════
Buyer sees:
  β€’ Type: PRODUCT_PURCHASE
  β€’ Direction: DEBIT (-)
  β€’ Amount: 50,000 TZS
  β€’ Balance impact: YES

Seller sees (after escrow release):
  β€’ Type: SALE
  β€’ Direction: CREDIT (+)
  β€’ Amount: 47,500 TZS
  β€’ Balance impact: YES

Platform sees:
  β€’ Type: PLATFORM_FEE_COLLECTED
  β€’ Direction: CREDIT (+)
  β€’ Amount: 2,500 TZS


PAID Ticket (Event):
════════════════════
Buyer sees:
  β€’ Type: EVENT_TICKET_PURCHASE
  β€’ Direction: DEBIT (-)
  β€’ Amount: 30,000 TZS
  β€’ Balance impact: YES

Organizer sees (after escrow release):
  β€’ Type: SALE
  β€’ Direction: CREDIT (+)
  β€’ Amount: 28,500 TZS
  β€’ Balance impact: YES

Platform sees:
  β€’ Type: PLATFORM_FEE_COLLECTED
  β€’ Direction: CREDIT (+)
  β€’ Amount: 1,500 TZS


FREE Product:
═════════════
Buyer sees:
  β€’ Type: FREE_PRODUCT
  β€’ Direction: NEUTRAL (β—†)
  β€’ Amount: 0 TZS
  β€’ Balance impact: NO


FREE Ticket:
════════════
Buyer sees:
  β€’ Type: FREE_EVENT_TICKET
  β€’ Direction: NEUTRAL (β—†)
  β€’ Amount: 0 TZS
  β€’ Balance impact: NO


CASH Product:
═════════════
Buyer sees:
  β€’ Type: CASH_PRODUCT_PAYMENT
  β€’ Direction: NEUTRAL (β—†)
  β€’ Amount: 20,000 TZS
  β€’ Balance impact: NO
  β€’ Note: "Physical cash payment"


CASH Ticket:
════════════
Buyer sees:
  β€’ Type: CASH_EVENT_TICKET
  β€’ Direction: NEUTRAL (β—†)
  β€’ Amount: 15,000 TZS
  β€’ Balance impact: NO
  β€’ Note: "Cash payment at door"


DONATION (Paid):
════════════════
Buyer sees:
  β€’ Type: DONATION_EVENT_TICKET
  β€’ Direction: DEBIT (-)
  β€’ Amount: 10,000 TZS (user chose)
  β€’ Balance impact: YES
  β€’ Metadata: { suggested: 5000, donated: 10000 }

Organizer sees (after escrow release):
  β€’ Type: SALE
  β€’ Direction: CREDIT (+)
  β€’ Amount: 9,500 TZS
  β€’ Balance impact: YES


DONATION (Free):
════════════════
Buyer sees:
  β€’ Type: FREE_EVENT_TICKET (or DONATION_EVENT_TICKET)
  β€’ Direction: NEUTRAL (β—†)
  β€’ Amount: 0 TZS
  β€’ Balance impact: NO
  β€’ Metadata: { suggested: 5000, donated: 0 }

πŸ” Code Implementation Points

PaymentOrchestrator Detection Logic

@Override
@Transactional
public PaymentResponse processPayment(PaymentRequest request)
        throws ItemNotFoundException, RandomExceptions, BadRequestException {

    PayableCheckoutSession session = checkoutSessionService.findCheckoutSession(
        request.getCheckoutSessionId(),
        request.getSessionDomain()
    );

    // ========================================
    // SCENARIO DETECTION
    // ========================================
    
    // FREE Detection
    if (session.getTotalAmount().compareTo(BigDecimal.ZERO) == 0) {
        return handleFreeCheckout(session);
    }
    
    // Payment Method Detection
    PaymentMethod paymentMethod = determinePaymentMethod(session, request);
    
    // CASH Detection
    if (paymentMethod == PaymentMethod.CASH) {
        return handleCashCheckout(session);
    }
    
    // PAID (Wallet/External) Detection
    return routeToProcessor(session, paymentMethod);
}

Event Ticket Type Validation

// EventCheckoutValidations.validateTicketTypeAndPrice()

switch (ticketPricingType) {
    case FREE -> validateFreeTicket(ticket, request);
    case PAID -> validatePaidTicket(ticket, request);
    case DONATION -> validateDonationTicket(ticket, request);
}

private void validateDonationTicket(...) {
    // Total quantity must be exactly 1
    if (totalQuantity > 1) {
        throw new BadRequestException(
            "Donation tickets are limited to 1 per person");
    }
    
    // No other attendees
    if (request.getOtherAttendees() != null 
        && !request.getOtherAttendees().isEmpty()) {
        throw new BadRequestException(
            "Donation tickets cannot be purchased for other attendees");
    }
    
    // Online only
    if (ticket.getSalesChannel() != SalesChannel.ONLINE_ONLY) {
        throw new BadRequestException(
            "Donation tickets can only be purchased online");
    }
}

πŸ“Š Data Consistency

ACID Properties Maintained

Atomicity:
β€’ Ledger entries in transactions
β€’ Balance updates atomic
β€’ No partial money movements

Consistency:
β€’ Double-entry rule enforced
β€’ Total debits = Total credits
β€’ Balance integrity checks

Isolation:
β€’ Transaction-level isolation
β€’ No concurrent balance corruption

Durability:
β€’ All operations persisted
β€’ Audit trail maintained

πŸš€ Scalability Considerations

Async Processing

Payment β†’ Return immediately β†’ Process in background

Benefits:
β€’ Fast API response
β€’ Better user experience
β€’ Handles spike loads

Event-Driven Architecture

Payment success β†’ Publish event β†’ Multiple listeners

Benefits:
β€’ Loosely coupled
β€’ Easy to add features
β€’ Horizontal scaling

Separate Read/Write Models

Write: Ledger entries (normalized)
Read: Transaction history (denormalized)

Benefit:
β€’ Fast queries
β€’ Optimized for each use case

🎯 Summary

Key Strengths of the Architecture

  1. Universal Payment System

    • Handles multiple domains (Products, Events)
    • Easy to extend to new domains
  2. Financial Integrity

    • Double-entry bookkeeping
    • Escrow protection
    • Audit trail
  3. Scalability

    • Async processing
    • Event-driven
    • Loosely coupled
  4. Flexibility

    • Strategy pattern for domain logic
    • Multiple payment methods support
    • Multiple checkout types
  5. Security

    • Transaction-level safety
    • Validation at every step
    • Balance integrity

πŸ“š Key Classes Reference

CHECKOUT:
β€’ ProductCheckoutSessionEntity
β€’ EventCheckoutSessionEntity
β€’ PayableCheckoutSession (interface)

PAYMENT:
β€’ PaymentOrchestrator
β€’ WalletPaymentProcessor
β€’ ExternalPaymentProcessor

STRATEGY:
β€’ SessionMetadataExtractor
β€’ PostPaymentHandler

FINANCIAL:
β€’ EscrowService
β€’ LedgerService
β€’ WalletService
β€’ TransactionHistoryService

ENTITIES:
β€’ EscrowAccountEntity
β€’ LedgerAccountEntity
β€’ LedgerEntryEntity
β€’ WalletEntity
β€’ TransactionHistory

EVENTS:
β€’ PaymentCompletedEvent
β€’ ProductPaymentCompletedListener
β€’ EventPaymentCompletedListener

End of Documentation

This architecture provides a solid foundation for a multi-domain payment system with financial integrity, scalability, and extensibility built in from the ground up.