WELIFE Docs Swagger Admin Web

SMS eSMS — Ma trận · Sơ đồ · Logic audit (đầy đủ)

Domain: D-AUTH · WP: WP-AUTH-SMS-01 · WP-FE-AUTH-SMS-ESMS-01
Ngày: 2026-08-14 · Surface: CON + API + OWNER
E2E live: chưa chạy (docs-only) — OWNER chọn done | fail | blast ok + SĐT

Liên quan: STATUS · OWNER-GATES · e2e preflight · FE wire · ready keys


1. Verdict ma trận (snapshot)

Lớp Thành phần Status Evidence
Product Login SĐT brand WELIFE WIRED FE PhoneFirebaseScreen → Nest
API POST /api/v1/auth/otp/send LIVE prod validation 400 phone invalid
API POST /api/v1/auth/otp/verify LIVE JWT + findOrCreateByPhone
Provider VPS SMS_PROVIDER=esms READY ESMS keys SET · brand WELIFE
Provider Local SMS_PROVIDER=console DEV devOtp / log — không trừ credit
FE API URL mobile/.env.local → prod SET https://api.minhtien.cloud/api/v1
OWNER e2e Nhận SMS + login Home PENDING chờ done / fail / blast
Machine queue STUB→IMP EMPTY không invent WP

Điểm dừng agent: không blast OTP trừ khi OWNER gửi blast ok + SĐT E.164.


2. Ma trận traceability D-AUTH · SMS

ID Layer Path / Endpoint Role Status
SCR-AUTH-PHONE CON UI mobile/src/features/auth/screens/PhoneFirebaseScreen.tsx USER IMP eSMS
SCR-AUTH-GATE CON nav AuthGate.tsx entry=phone USER IMP
CTX-AUTH CON AuthContext.sendPhoneOtp / verifyPhoneOtp USER IMP
API-CLIENT CON AuthApi.sendOtp / verifyOtp USER IMP
EP-SEND API POST /auth/otp/send public + throttle 5/min IMP
EP-VERIFY API POST /auth/otp/verify public + throttle 10/min IMP
SVC-AUTH API AuthService.sendOtp / verifyOtp — IMP
SVC-SMS API SmsService.sendEsms — IMP
STORE-OTP API Redis/otpStore · TTL · lock — IMP
DTO API SendOtpDto @IsPhoneNumber('VN') — IMP
ENV-PROD Ops SMS_PROVIDER=esms · ESMS_* · brand WELIFE OWNER READY
ENV-LOCAL Ops SMS_PROVIDER=console · OTP_DEV_CODE DEV SET
E2E-OWNER Gate App prod → SMS → Home OWNER PENDING

Surfaces: CON (consumer phone) · API shared · DRV/PAR có thể reuse cùng endpoint với surface (chưa e2e riêng).


3. Sơ đồ kiến trúc (happy path)

sequenceDiagram
  autonumber
  actor U as User CON
  participant App as PhoneFirebaseScreen
  participant Ctx as AuthContext
  participant API as Nest /auth/otp
  participant Store as OTP Store Redis
  participant SMS as SmsService eSMS
  participant ES as rest.esms.vn

  U->>App: Nhập SĐT +84… · Gửi OTP
  App->>Ctx: sendPhoneOtp(+84…)
  Ctx->>API: POST /otp/send {phone, surface:CONSUMER}
  API->>API: validate VN phone · rate-limit · lock?
  API->>Store: set(phone, code, TTL)
  API->>SMS: sendOtp(phone, code)
  SMS->>ES: SendMultipleMessage_V4 (Brandname=WELIFE)
  ES-->>SMS: CodeResult=100 · SMSID
  SMS-->>API: {provider:esms, sent:true}
  API-->>Ctx: {success, smsProvider, smsSent, expiresInSec}
  Ctx-->>App: hint WELIFE · step=code
  ES-->>U: SMS OTP brand WELIFE

  U->>App: Nhập 6 số · Xác nhận
  App->>Ctx: verifyPhoneOtp(+84…, otp)
  Ctx->>API: POST /otp/verify
  API->>Store: get · match · del · clearFailures
  API->>API: findOrCreateByPhone · issueTokens
  API-->>Ctx: accessToken · refreshToken · user
  Ctx->>Ctx: setTokens · UsersApi.me · chat/push
  Ctx-->>App: session ready → Home

4. Sơ đồ quyết định OWNER reply

flowchart TD
  A[OWNER thử e2e trên app prod] --> B{Kết quả?}
  B -->|SMS eSMS done| C[PASS gate SMS]
  B -->|SMS eSMS fail: lỗi| D[Phân loại fail]
  B -->|blast ok + SĐT| E{Agent có SĐT E.164?}
  B -->|docs only / chưa thử| F[Giữ PENDING · không blast]

  C --> C1[Cập nhật STATUS SMS = PASS]
  C --> C2[Next: Pay hoặc MoMo]

  D --> D1{Fail ở đâu?}
  D1 -->|Gửi OTP API lỗi| D2[503 esms / 429 lock / 400 phone]
  D1 -->|Không nhận SMS| D3[Portal eSMS · brand · credit · SĐT]
  D1 -->|Sai OTP / hết hạn| D4[TTL · attempts · clock]
  D1 -->|Login xong nhưng không Home| D5[FE applySession / me]

  E -->|Có + blast ok| E1[Agent POST /otp/send 1 lần]
  E -->|Thiếu SĐT| E2[Hỏi lại SĐT · không gửi]
  E1 --> E3[OWNER đọc SMS · verify trên app hoặc curl]
  E3 --> B

5. Logic audit — từng bước

5.1 FE send

Bước Logic Pass nếu Fail nếu
1 National ≥9 digits → +84 + digits canSend=true Nút disabled
2 sendPhoneOtp(e164) 200 + success Network / 4xx / 5xx → error text
3 Hint smsProvider=esms / devOtp local Không có hint nhưng vẫn vào step code nếu 200
4 UI step=code · resend 59s Stay phone

Không còn: firebaseStartPhoneLogin trên màn phone.

5.2 API send (AuthService.sendOtp)

Check Rule HTTP
Prod + OTP_DEV_CODE Cấm 403
Lock remaining Redis lock 429 + lockRemainingSec
Phone DTO @IsPhoneNumber('VN') 400
Throttle 5 / 60s 429
Code Random 6 số (prod) hoặc OTP_DEV_CODE (non-prod) —
Store otpStore.set(phone, {code, expires, surface}, ttl) —
SMS SmsService.sendOtp 503 nếu provider fail
Response smsProvider · smsSent · otpBackend · không devOtp trên prod 200

5.3 eSMS adapter (SmsService.sendEsms)

Field Value
Phone normalize 8490… (normalizeVnPhone)
Content WELIFE OTP: {code}. Hieu luc {TTL}s…
Brandname ESMS_BRAND_NAME / WELIFE
SmsType ESMS_SMS_TYPE default 2
Success CodeResult === "100"
Fail HTTP non-OK / JSON bad / CodeResult≠100 → 503

5.4 API verify (AuthService.verifyOtp)

Check Rule HTTP
Lock Đang khóa 429
Match record tồn tại · chưa hết hạn · code===otp else 401 + đếm fail
Max attempts OTP_MAX_ATTEMPTS (default 5) lock → 429
Success del OTP · clearFailures · findOrCreateByPhone · issueTokens 200 JWT
Surface CONSUMER default · driver/partner flags trong body —

5.5 FE verify

Bước Logic
verifyPhoneOtp AuthApi.verifyOtp → applySession (tokens · me · chat · push)
Success AuthGate thấy user → MainTabs
Fail error trên màn OTP

6. Ma trận outcome OWNER (điền khi có kết quả)

Outcome message Agent hành động STATUS SMS Next
SMS eSMS done Ghi PASS · đóng gate SMS e2e PASS Pay / MoMo
SMS eSMS fail: … Phân loại §5 · đề xuất fix (không blast) FAIL + root cause OWNER retry / fix env
blast ok + +84… 1 lần POST /otp/send · báo smsProvider/smsSent · không log OTP PARTIAL→chờ verify OWNER nhập OTP
docs-only / chưa thử Giữ PENDING · cập nhật docs này PENDING OWNER thử app

Hiện tại: docs-only → PENDING.


7. Ma trận fail triage (checklist)

Triệu chứng Layer Kiểm tra
«phone must be a valid phone number» DTO E.164 +84 + 9 số
429 SĐT tạm khóa Store Đợi lockRemainingSec
503 Gửi SMS thất bại (esms) eSMS VPS keys · CodeResult · credit portal
200 nhưng không có SMS eSMS / telco Brand WELIFE · SmsType · SĐT blacklist
smsProvider=console trên app Env App đang trỏ local API → đổi prod URL
Sai OTP User / TTL TTL 300s · không copy nhầm
Verify OK nhưng kẹt splash FE Tokens · UsersApi.me · AuthGate

8. Env split (audit bảo mật)

Key api/.env / VPS mobile EXPO_PUBLIC Git
SMS_PROVIDER ✅ ❌ example only
ESMS_API_KEY / SECRET ✅ ❌ cấm
ESMS_BRAND_NAME ✅ WELIFE ❌ example
OTP_DEV_CODE local only · cấm prod ❌ —
EXPO_PUBLIC_API_URL — ✅ prod URL —

9. So sánh cũ vs mới

Trước (Firebase Phone) Sau (WP-FE-AUTH-SMS-ESMS-01)
Gửi OTP Firebase Auth Nest → eSMS
Brand SMS Google / Firebase WELIFE
Verify Firebase idToken → /auth/firebase Nest OTP → JWT trực tiếp
Phụ thuộc bridge EXPO_PUBLIC_FIREBASE_BRIDGE Không (phone entry luôn)
Local Firebase test numbers console + devOtp

Email / Google vẫn có thể dùng Firebase (tuỳ bridge) — không đụng trong WP này.


10. Agent runbook (sau khi OWNER reply)

IF message contains "SMS eSMS done":
  → STATUS SMS = PASS · report PASS · stop blast
ELIF message starts "SMS eSMS fail":
  → map symptom → §7 · propose fix · no invent WP
ELIF message contains "blast ok" AND phone matches /^\+84\d{9,10}$/:
  → ONE POST prod /auth/otp/send · report smsProvider/smsSent · never print OTP
ELSE:
  → ask clarification · no send

11. File đụng (code đã IMP)


Footer

Domain: D-AUTH
WP: WP-AUTH-SMS-01 · WP-FE-AUTH-SMS-ESMS-01
Surfaces: CON · API · OWNER
Status: WIRED · e2e PENDING (docs-only)
Local: N/A (audit docs)
Next: SMS eSMS done | fail:… | blast ok + +84…