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

第 7 課:對帳與結算引擎

设计一个自动对账系统。用于交易协调的匹配算法。结算引擎和支付处理。處理差異和異常。

🏗️ 建築 — 第 7 課 第 7 課:對帳與結算引擎

金融科技與支付平台架構

第 2 部分:核心支付引擎

亞洲開發網

第 7 課:對帳與結算引擎

簡介

對帳是內部系統與PSP/銀行之間匹配交易的過程,以確保財務資料的準確性。這是一個關鍵功能,但在支付系統中經常被忽略。


1. 什麼是和解?

1.1 概述

Reconciliation = So khớp records giữa 2+ hệ thống

Internal Records (Our System)    External Records (PSP/Bank)
├── payment_001: 100,000 VND     ├── txn_abc: 100,000 VND     ✓ Match
├── payment_002: 200,000 VND     ├── txn_def: 200,000 VND     ✓ Match
├── payment_003: 150,000 VND     ├── (missing)                ✗ Unmatched
├── (missing)                    ├── txn_ghi: 50,000 VND      ✗ Extra
└── payment_004: 300,000 VND     └── txn_jkl: 310,000 VND     ✗ Mismatch

1.2 和解類型

類型描述頻率
交易偵察符合每筆交易每日
定居點偵察比賽總結算每日/每週
平衡偵察匹配帳戶餘額每日
費用偵察依合約查看費用每月
跨系統偵察內部服務之間即時

2. 協調架構

┌─────────────────────────────────────────────────────────────┐
│              RECONCILIATION ENGINE                           │
│                                                              │
│  ┌────────────┐     ┌──────────────┐     ┌───────────────┐ │
│  │   File     │     │   Matching   │     │  Exception    │ │
│  │  Ingester  │────►│   Engine     │────►│  Manager      │ │
│  └────────────┘     └──────────────┘     └───────────────┘ │
│       │                    │                     │           │
│  ┌────▼────┐         ┌────▼────┐          ┌─────▼─────┐   │
│  │ Parser  │         │ Rules   │          │ Workflow  │   │
│  │ (CSV,   │         │ Engine  │          │ (Manual   │   │
│  │  SFTP,  │         │         │          │  Review)  │   │
│  │  API)   │         │         │          │           │   │
│  └─────────┘         └─────────┘          └───────────┘   │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐ │
│  │              Reporting & Dashboard                     │ │
│  └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

2.1 檔案攝取

@Service
public class ReconciliationFileIngester {

    // PSPs provide settlement files in various formats
    public List<ExternalTransaction> ingest(PSPFileConfig config) {
        return switch (config.getFormat()) {
            case CSV -> parseCsv(config);
            case SFTP_CSV -> downloadAndParseCsv(config);
            case API -> fetchViaApi(config);
            case EXCEL -> parseExcel(config);
        };
    }

    // VNPay sends CSV via SFTP
    private List<ExternalTransaction> downloadAndParseCsv(PSPFileConfig config) {
        var file = sftpClient.download(
            config.getHost(), config.getPath(),
            config.getCredentials());
        return csvParser.parse(file, VNPayTransactionMapper.class);
    }
}

2.2 配對引擎

@Service
public class TransactionMatcher {

    public ReconciliationResult match(
            List<InternalTransaction> internal,
            List<ExternalTransaction> external) {

        var result = new ReconciliationResult();
        var externalMap = external.stream()
            .collect(Collectors.toMap(
                ExternalTransaction::getPspReference, Function.identity()));

        for (var txn : internal) {
            var ext = externalMap.remove(txn.getPspReference());

            if (ext == null) {
                result.addUnmatched(txn); // Missing on PSP side
            } else if (!txn.getAmount().equals(ext.getAmount())) {
                result.addMismatch(txn, ext); // Amount mismatch
            } else if (!txn.getCurrency().equals(ext.getCurrency())) {
                result.addMismatch(txn, ext); // Currency mismatch
            } else {
                result.addMatched(txn, ext); // Perfect match
            }
        }

        // Remaining external transactions = not in our system
        for (var ext : externalMap.values()) {
            result.addExtra(ext);
        }

        return result;
    }
}

3.異常處理

3.1 例外類型

Reconciliation Exceptions:
├── MISSING_EXTERNAL: In our system, not in PSP
│   → PSP may not have processed yet (timing)
│   → PSP failed silently
│
├── MISSING_INTERNAL: In PSP, not in our system
│   → Our system crashed before recording
│   → Duplicate on PSP side
│
├── AMOUNT_MISMATCH: Different amounts
│   → Partial capture not reflected
│   → FX rate difference
│   → Fee deducted at PSP
│
└── STATUS_MISMATCH: Different statuses
    → Async update not received
    → Webhook missed

3.2 解決工作流程

Exception Detected
    │
    ▼
Auto-resolution possible?
    │
    ├── YES: Apply auto-fix
    │   ├── Timing issue → Wait and re-check
    │   ├── Missed webhook → Query PSP for status
    │   └── Known pattern → Apply standard fix
    │
    └── NO: Create manual review task
        ├── Assign to operations team
        ├── Set SLA (24h for critical, 72h for normal)
        └── Escalate if unresolved

4. 結算與支付

4.1 商家支付流程

Daily Settlement:
1. Aggregate captured transactions per merchant
2. Calculate fees (platform fee, PSP fee)
3. Calculate net payout amount
4. Create payout record
5. Submit bank transfer
6. Update settlement status

Payout Schedule:
├── T+1: Next business day (standard)
├── T+0: Same day (premium merchants)
├── Weekly: Every Monday (small merchants)
└── On-demand: Instant payout (with fee)

4.2 支付實施

@Service
public class PayoutService {

    @Scheduled(cron = "0 0 8 * * MON-FRI") // 8:00 AM weekdays
    public void processPayouts() {
        var pendingPayouts = payoutRepository
            .findByStatus(PayoutStatus.PENDING);

        for (var payout : pendingPayouts) {
            try {
                // Transfer via bank API
                var transferResult = bankApi.transfer(
                    payout.getMerchantBankAccount(),
                    payout.getNetAmount(),
                    payout.getReference());

                payout.markProcessed(transferResult.getReference());
                payoutRepository.save(payout);

                notificationService.notifyMerchant(
                    payout.getMerchantId(),
                    "Payout processed: " + payout.getNetAmount());

            } catch (BankApiException e) {
                payout.markFailed(e.getMessage());
                payoutRepository.save(payout);
                alertService.alert("Payout failed: " + payout.getId());
            }
        }
    }
}

總結

對帳與結算是金融科技營運的支柱:

  • 內部記錄和 PSP 資料之間的自動匹配
  • 異常管理,具有自動解決和手動審核功能
  • 結算批次每天進行費用計算
  • 付款處理與銀行集成

下一篇文章:數位錢包架構-電子錢包和餘額管理。