Skip to content
GitLab
Projects Groups Topics Snippets
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Register
  • Sign in
  • A Ahad Mobile
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributor statistics
    • Graph
    • Compare revisions
  • Issues 0
    • Issues 0
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 1
    • Merge requests 1
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Deployments
    • Deployments
    • Environments
    • Releases
  • Packages and registries
    • Packages and registries
    • Package Registry
    • Terraform modules
  • Monitor
    • Monitor
    • Incidents
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Repository
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • holatech
  • Ahad Mobile
  • Merge requests
  • !34

fix(exchange-rates): cut rate fetching to one request and remove par-rate fallbacks

  • Review changes

  • Download
  • Patches
  • Plain diff
Merged Khanh Pham requested to merge fix/exchange-rates-quota-and-fallbacks into develop Aug 28, 2026
  • Overview 0
  • Commits 3
  • Pipelines 1
  • Changes 13

Vấn đề

Scheduler tỷ giá gọi provider theo full cross-product của mọi currency active — 1 HTTP request cho mỗi cặp có thứ tự, tức 10 × 9 = 90 request/giờ, trong khi UniRate free tier chỉ cho 200 request/ngày.

Hệ quả đo được trên staging:

# body 429 thật
{"error":"Rate limit exceeded","current_count":200,"limit":200,"current_tier":"free"}

# exchange_rates: 90 row, chỉ refresh được 2 lần đầu ngày
00:00 UTC → 80 row
01:00 UTC → 10 row   # chạm đúng 200
sau đó   → không gì cả

# log 07:01 UTC
[UniRateApiService] Exchange rates fetch completed: 0 successful, 90 errors

Quota cạn sau ~2 tiếng, 22/24 lần cron còn lại fail 100%, và mọi row vẫn is_active = true — không cách nào phân biệt với dữ liệu sống.

Kèm theo đó, 3 đường dẫn biến "không provider nào định giá được" thành một con số trông y như giá thật.

Thay đổi

1. d632562 — 1 request/lần chạy + guard staleness

Dùng batch endpoint của provider: GET /api/rates?from=USD trả toàn bộ target trong 1 lần. Từ 2160 → 24 request/ngày, và thêm currency không tốn thêm request nào.

Chỉ lưu cặp USD ↔ currency vì không có consumer nào đọc cặp không liên quan USD. Row sót lại từ cơ chế cũ bị is_active = false thay vì để trông như còn sống mãi.

Fetch fail thì không ghi và không deactivate gì cả — coi "không có response" là "không có tỷ giá" sẽ biến 1 request bị từ chối thành cả bảng không dùng được.

Guard tuổi rate đặt trong 2 method đọc để mọi consumer thừa hưởng tự động — guard đặt ở từng caller là guard mà caller tiếp theo sẽ quên. Ngưỡng 6 giờ, chịu được 5 lần chạy fail liên tiếp.

2. 93f2936 — bỏ fallback giá par

Chỗ Trước Sau
VisaFxApiService trả '1.00', và hằng số cấu hình cho USD/HKD trả null
assets.service ?? 1 → 50 EUR cộng vào tổng USD thành 50 USD loại khỏi tổng + ALERT
balance.service nhân trực tiếp, không check báo bằng USD + ALERT
GET /exchange-rates/* giá par 503

Endpoint tỷ giá còn ghi trong Swagger là "Rate is cached for 1 hour" — không có cache nào trong VisaFxApiService. Đã xoá lời khẳng định đó.

3. 3a14eff — bỏ route convert legacy

POST /transactions/convert chạy conversion thật + ghi ledger ở bất kỳ tỷ giá nào đang nằm trong bảng, không check tuổi. App đã chuyển sang /convert/quote + /convert/submit (định giá qua RampPriceService, ràng buộc user bằng quote đã ký). ConversionsService giữ nguyên, chỉ bỏ route.

Hai chỗ lệch so với kế hoạch ban đầu

?? 1 trong assets.service làm hai việc, không phải một. Nó vừa xử lý cặp identity (currency mặc định không có row cho chính nó) vừa che rate thiếu. Bỏ thẳng sẽ làm sai chính currency mặc định. Đã tách 2 trường hợp, test pin cả 3 nhánh.

balance.service hoá ra phải sửa. Nó gọi getUsdToFiatRate() cho display currency; strictNullChecks bắt ngay: null mới sẽ chảy vào BigNumber().times() → NaN ở mọi field số dư. Xử lý theo đúng doctrine đã ghi trong chính file đó (đừng để user mở app thấy trắng): trả số liệu USD, đặt currency: 'USD', log ALERT. Response tự khai currency nên client không thể gán nhãn sai.

Kiểm chứng

  • 1294 test pass, 0 fail (138 suite). Build sạch, lint sạch, tsc không thêm lỗi mới ở file impl.
  • 43 test mới, gồm 2 spec mới (unirate-api.service, visa-fx-api.service).
  • Không migration, không đụng FE.

Lưu ý: lần chạy yarn test đầu có 1 suite fail với 0 test fail — đó là artifact Node 24 SIGSEGV mà repo tự cảnh báo. Chạy lại 2 lần đều xanh hoàn toàn.

Rủi ro còn lại

Chưa verify response thật của /api/rates. Quota cạn tới 00:00 UTC. Đã xác nhận endpoint tồn tại (429 = qua routing, không phải 404) và implement theo shape trong docs, nhưng chưa parse response thật lần nào.

Fail an toàn: shape khác dự đoán → fetchRatesForBase trả null → không ghi gì, rate cũ giữ nguyên rồi bị withhold sau 6h. Xấu nhất là "không cập nhật được", không phải "cập nhật sai".

GET /assets đổi hành vi. Nó tính tổng theo accounts[0] — thứ tự do Postgres quyết định nên target currency là ngẫu nhiên. Nếu accounts[0] không phải USD, sau khi deactivate các cặp non-USD endpoint này sẽ loại gần hết currency khỏi tổng + log ALERT. FE không gọi endpoint này (chỉ /assets/fiat-balances) nên không ảnh hưởng app. accounts[0] là bug sẵn có, không sửa trong MR này.

Assignee
Assign to
Reviewers
Request review from
Time tracking
Source branch: fix/exchange-rates-quota-and-fallbacks