Chuyển đến nội dung chính

Lesson 8: Digital Wallet Architecture - E-Wallet & Balance Management

Digital Wallet architecture: account types, balance management, top-up/withdrawal flows. Wallet-to-wallet transfers. Escrow accounts and hold balances.

🏗️ Architecture — Lesson 8 Lesson 8: Digital Wallet Architecture - E-Wallet & Balance Management

FinTech & Payment Platform Architecture

Part 3: Digital Wallet & Ledger System

xdev.asia

Lesson 8: Digital Wallet Architecture - E-Wallet & Balance Management

Introduction

Digital Wallet is the heart of every FinTech super app. In Vietnam, MoMo, ZaloPay, ShopeePay have turned e-wallets into the most popular payment methods. This article will analyze the architecture of a professional Digital Wallet Platform.


1. Wallet Architecture Overview

1.1 Account Types

Wallet Account Hierarchy:
├── User Wallet
│   ├── Main Balance (available for payment)
│   ├── Hold Balance (reserved/pending)
│   ├── Bonus Balance (promotions, non-withdrawable)
│   └── Credit Balance (lending, BNPL)
│
├── Merchant Wallet
│   ├── Settlement Balance
│   ├── Fee Balance
│   └── Reserve Balance (for chargebacks)
│
├── Platform Wallet
│   ├── Fee Collection Account
│   ├── Float Account (pooled user funds)
│   └── Escrow Account
│
└── System Wallet
    ├── Suspense Account (unresolved transactions)
    ├── Reconciliation Account
    └── Interest Pool Account

1.2 Core Components

┌─────────────────────────────────────────────────────────────┐
│                 WALLET SERVICE                               │
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │   Account    │  │   Balance    │  │   Transaction    │  │
│  │   Manager    │  │   Engine     │  │   Processor      │  │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────────┘  │
│         │                 │                  │               │
│  ┌──────▼───────┐  ┌──────▼───────┐  ┌──────▼───────────┐  │
│  │   KYC        │  │   Hold       │  │   Transfer       │  │
│  │   Validator  │  │   Manager    │  │   Engine         │  │
│  └──────────────┘  └──────────────┘  └──────────────────┘  │
│                                                              │
│  ┌──────────────────────────────────────────────────────┐   │
│  │              Event Store (Kafka)                      │   │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

2. Balance Management

2.1 Balance Calculation Strategy

Strategy 1: Snapshot + Delta (Recommended)
─────────────────────────────────────────
Snapshot (cached): 1,000,000 VND (at timestamp T)
+ Credit: +200,000 (top-up)
+ Credit: +50,000 (cashback)
- Debit: -150,000 (payment)
─────────────────────────────
Current Balance: 1,100,000 VND

Every N transactions or periodically → create new snapshot

2.2 Concurrent Balance Updates

// Optimistic locking approach
@Entity
@Table(name = "wallet_balances")
public class WalletBalance {
    @Id
    private UUID walletId;

    @Column(name = "available_balance")
    private long availableBalance; // In smallest currency unit

    @Column(name = "hold_balance")
    private long holdBalance;

    @Version
    private long version; // Optimistic lock

    public void debit(long amount) {
        if (availableBalance < amount) {
            throw new InsufficientFundsException();
        }
        this.availableBalance -= amount;
    }

    public void credit(long amount) {
        this.availableBalance += amount;
    }

    public void placeHold(long amount) {
        if (availableBalance < amount) {
            throw new InsufficientFundsException();
        }
        this.availableBalance -= amount;
        this.holdBalance += amount;
    }

    public void releaseHold(long amount) {
        this.holdBalance -= amount;
        this.availableBalance += amount;
    }
}

2.3 Database Design

CREATE TABLE wallet_accounts (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id         UUID NOT NULL,
    account_type    VARCHAR(30) NOT NULL, -- 'MAIN', 'BONUS', 'CREDIT'
    currency        VARCHAR(3) NOT NULL DEFAULT 'VND',
    status          VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
    kyc_level       INT NOT NULL DEFAULT 0,
    daily_limit     BIGINT NOT NULL,
    monthly_limit   BIGINT NOT NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE wallet_balances (
    wallet_id           UUID PRIMARY KEY REFERENCES wallet_accounts(id),
    available_balance   BIGINT NOT NULL DEFAULT 0,
    hold_balance        BIGINT NOT NULL DEFAULT 0,
    total_credited      BIGINT NOT NULL DEFAULT 0,
    total_debited       BIGINT NOT NULL DEFAULT 0,
    version             BIGINT NOT NULL DEFAULT 0,
    updated_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT positive_balance CHECK (available_balance >= 0),
    CONSTRAINT positive_hold CHECK (hold_balance >= 0)
);

CREATE TABLE wallet_transactions (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    wallet_id       UUID NOT NULL REFERENCES wallet_accounts(id),
    type            VARCHAR(30) NOT NULL, -- 'TOP_UP', 'PAYMENT', 'TRANSFER', 'WITHDRAWAL'
    direction       VARCHAR(10) NOT NULL, -- 'CREDIT', 'DEBIT'
    amount          BIGINT NOT NULL,
    balance_after   BIGINT NOT NULL,
    reference_id    UUID,
    reference_type  VARCHAR(50),
    description     TEXT,
    metadata        JSONB DEFAULT '{}',
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_wallet_txn_wallet_date
    ON wallet_transactions(wallet_id, created_at DESC);

3. Top-up & Withdrawal

3.1 Top-up Flow

Customer ──► Select top-up method ──► Payment Gateway
                                           │
                                    ┌──────▼──────┐
                                    │ Bank/Card   │
                                    │ Charge      │
                                    └──────┬──────┘
                                           │ Success
                                    ┌──────▼──────┐
                                    │ Credit      │
                                    │ Wallet      │
                                    └──────┬──────┘
                                           │
                                    ┌──────▼──────┐
                                    │ Notify      │
                                    │ Customer    │
                                    └─────────────┘

3.2 Wallet-to-Wallet Transfer

@Transactional
public TransferResult transfer(TransferRequest request) {
    var sender = walletRepository.findWithLock(request.getSenderId());
    var receiver = walletRepository.findWithLock(request.getReceiverId());

    // Validate
    sender.validateTransfer(request.getAmount());
    limitChecker.check(sender, request.getAmount());

    // Execute double-entry
    sender.debit(request.getAmount());
    receiver.credit(request.getAmount());

    // Save both
    walletRepository.save(sender);
    walletRepository.save(receiver);

    // Record transactions
    var debitTxn = WalletTransaction.debit(sender, request.getAmount());
    var creditTxn = WalletTransaction.credit(receiver, request.getAmount());
    transactionRepository.saveAll(List.of(debitTxn, creditTxn));

    // Publish event
    eventBus.publish(new TransferCompletedEvent(request));

    return TransferResult.success(debitTxn.getId());
}

4. Wallet Limits & KYC

4.1 Limit Structure (according to SBV regulations)

KYC Level 0 (chưa xác minh):
├── Số dư tối đa: 10,000,000 VND
├── Giao dịch/ngày: 5,000,000 VND
└── Giao dịch/tháng: 20,000,000 VND

KYC Level 1 (xác minh cơ bản):
├── Số dư tối đa: 50,000,000 VND
├── Giao dịch/ngày: 30,000,000 VND
└── Giao dịch/tháng: 100,000,000 VND

KYC Level 2 (xác minh đầy đủ - eKYC):
├── Số dư tối đa: 100,000,000 VND
├── Giao dịch/ngày: 100,000,000 VND
└── Giao dịch/tháng: 300,000,000 VND

Summary

Digital Wallet needs to balance between performance (concurrent access) and correctness (no money):

  • Optimistic locking for balance updates
  • Double-entry for every transaction
  • Limit enforcement according to KYC level
  • Event-driven for async processing

Next article: Double-Entry Ledger System — accounting engine design for FinTech.