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 0
    • Merge requests 0
  • 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
  • !144

feat: notify users on every wallet money movement

  • Review changes

  • Download
  • Patches
  • Plain diff
Merged Hung Hoang requested to merge hunghoang/feat/transaction-notifications into develop Oct 26, 2026
  • Overview 7
  • Commits 11
  • Pipelines 12
  • Changes 114

What

Users learned about money moving in/out of their wallet only by opening History. This MR makes every user-visible +/- transaction create a notification (persisted row in the bell list + FCM push), and live-refreshes the app for P2P transfers.

Bug record: https://hola-tech.sg.larksuite.com/record/MzE9rVZ6ae5hBPcB9zklGqsmgQX

Coverage (11 new NotificationTypes)

Flow + / - Mechanism
P2P transfer sender -, receiver + new TransferCompletedEvent after the outer commit; both sides notified
Crypto deposit (Fireblocks) + listener on existing DEPOSIT_RECORDED; net amount, legacy asset id -> display code
Crypto withdrawal (Fireblocks) - new WITHDRAWAL_COMPLETED on the wasNotCompleted status edge (fires exactly once)
Card top-up - wallet new event after the stage-1 commit (both legs settled)
Card purchase - card Reap: emitted at clearing (booking), not the auth hold; KeySecure: on posted positive delta; both replay-gated
Card withdrawal settle + wallet existing socket event now also persists a row
Lending supply / close - / + listener on existing lending.operation.settled, resolves the operation row; customer kinds only
Lending borrow / repay / repay-refund + / - / + same listener
Fiat payout (bank) - on PAID, + on RETURN collector filled inside the DB transaction, emitted after commit; failed-before-paid releases emit nothing
Conversion (ramp USD/USDC) both legs emitted only when the COMPLETED verdict committed; exactly once across serialization retries

Dead paths verified and left untouched: legacy cards.service.ts top-up (400s before any ledger write since 0515fd34), legacy conversions.service.ts (no caller). Liquidation/write-off keep their existing dedicated notifications.

Live refresh

New transfer.completed socket frame to BOTH sides (be socket-events listener; fe socket handler invalidates balance + history + notifications queries) — the receiver previously had no live update at all. Other flows keep their existing socket frames; all notifications also refetch on FCM foreground.

Invariants

  • Every emit is after the booking commits and wrapped in try/catch — a listener can never roll back settled money.
  • Fires once per movement (status edges / verdict gating / replay guards), verified by dedicated specs.

Verification

  • BE: 3443 unit tests pass (only failures are the 75 pre-existing env-dependent ones in treasury.config / admin-lending.risk, identical on develop); ~150 new assertions across 13 new/updated suites, including emit-after-commit ordering and no-emit-on-rollback.
  • FE: socket suite 10/10; tsc clean; eslint clean.
  • Maestro on the iOS simulator against the repo fixture (.omx/artifacts/tx-notifications/): full flow green — home bell shows the unread badge -> notification list renders + and - rows -> Mark all as read clears the badge. Screenshots: home-bell-unread-badge.png, notifications-list.png, notifications-all-read.png, home-bell-cleared.png under ~/.maestro/tests/2026-10-26_082756/.

Deploy notes — sau khi resolve conflict với develop (fb2396a5)

  1. Chạy migration 1790600000000 trước khi bật code mới; giữ DB_SYNCHRONIZE=false. Index UQ_notifications_lending_booking phải tồn tại để cơ chế dedupe lending có hiệu lực.
  2. Nếu môi trường từng chạy bản round 2, chạy SQL chỉ đọc dưới đây trước khi tạo unique index để kiểm tra thông báo trùng. Nguồn: .omx/artifacts/mr144-round3/deploy-preflight.sql. Nếu truy vấn đầu trả về dữ liệu, cần reconcile trước migration; không tự động xoá lịch sử.
-- Read-only checks for an environment where an earlier build of MR !144 may have run.
-- This review did NOT run these against staging or production.
-- Before migration: this result should be empty. If not, reconcile deliberately; do not
-- delete financial/notification history automatically.
SELECT type, relation_id, params, COUNT(*) AS copies
FROM notifications
WHERE type IN (
  'LENDING_SUPPLY_COMPLETED', 'LENDING_BORROW_COMPLETED',
  'LENDING_REPAY_COMPLETED', 'LENDING_REPAY_REFUND_COMPLETED',
  'LENDING_WITHDRAW_COMPLETED'
) AND params IS NOT NULL
GROUP BY type, relation_id, params
HAVING COUNT(*) > 1;

-- After migration, before the new writer is activated: the expected index must exist.
SELECT schemaname, tablename, indexname, indexdef
FROM pg_indexes
WHERE indexname = 'UQ_notifications_lending_booking';
Edited Oct 26, 2026 by Hung Hoang
Assignee
Assign to
Reviewers
Request review from
Time tracking
Source branch: hunghoang/feat/transaction-notifications