
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.