Mục tiêu
Nạp tiền pháp định (cash deposit) qua Airwallex Global Account: mỗi user có một tài khoản ngân hàng nhận tiền riêng cho mỗi currency, tiền vào được ghi sổ tự động qua webhook.
Không dùng SDK airwallex-payment-react-native — SDK đó là checkout thẻ/ví (Payment Acceptance), tiền không chảy vào Global Account. Không dùng QR: bank Mỹ/EU không quét QR để chuyển khoản.
Backend (be/src/modules/airwallex)
-
Mở Global Account — get-or-create theo
(user_id, currency_id), cóUNIQUEconstraint ở DB +request_idunique gửi lên Airwallex, nên gọi lại bao nhiêu lần cũng chỉ ra một account. -
Webhook — chỉ ghi có (credit) khi
deposit.settled;deposit.reversedđảo bút toán. Có khoá chống race giữa settle và reverse. -
Ledger — deposit map về ví user qua
global_account_id; transaction + ledger_entries ghi trong một transaction DB. - Reconciliation cron — quét event kẹt, deposit bị bỏ sót và lệch số dư.
- Admin — endpoint liệt kê và gán thủ công các deposit không khớp được account nào.
- Fiat balances — endpoint số dư pháp định + ledger entries nội bộ hiện trong lịch sử giao dịch hợp nhất.
Danh sách currency (USD, EUR, GBP, HKD, SGD) lấy từ catalog; mapping currency → country và rail (ACH/SEPA/FPS/FAST…) đã đối chiếu với sandbox thật.
Frontend (fe/src/components/deposit)
Ba màn: Select currency → Add funds → Transfer details.
- Chọn currency chỉ điều hướng, không tạo account — thoát ra không phát sinh gì.
- Vào Add funds mới tự mở account nếu chưa có, kèm skeleton trên account card. Lỗi thì hiện thông báo + nút Retry, không kẹt loading vô hạn.
-
Idempotency hai lớp:
useRefbật trước khi request bay đi (chặn remount/StrictMode double-invoke) + get-or-create ở backend. Có test bật StrictMode để chứng minh; gỡ guard là test đỏ. - Transfer details: số tiền làm headline, rail hiện dạng pill, bấm cả dòng để copy, Copy all + Share.
- Cả ba màn ăn theo theme của app (
dark:classes +Headerdùng chung), không tự vẽ bảng màu riêng.
Kiểm thử
- BE: 27 test module airwallex xanh.
- FE: 14 suite / 105 test xanh,
tsc --noemitsạch, lint 0 error. - Chạy thật trên simulator với sandbox Airwallex: mở account, nhận đủ field chuyển khoản, thử 6 lần vào/ra và 2 lần bấm đồng thời — vẫn đúng một account.
Cần lưu ý trước khi merge
-
Webhook subscription: cần vào dashboard Airwallex xác nhận webhook đã bật và đã subscribe
deposit.settled,deposit.reversed,global_account.*. API key hiện tại không đọc được danh sách subscription nên chưa tự kiểm tra được. - Beneficiary name không phải tên user mà là tên đối tác ngân hàng. Bản này đã bỏ dòng giải thích điều đó cho gọn màn hình — nếu người dùng thắc mắc thì nên thêm lại một dòng hint nhỏ dưới ô Beneficiary name.
- Nhánh đang behind develop 18 commit, cần rebase/merge develop trước khi merge.