Files
sys-analysis-design/sa-output/e-commerce/02-architecture/adr/ADR-013_bat-dong-bo-hoa-khoi-tao-thanh-toan.md
Canhchimlac 343ad8bbbc save
2026-09-15 16:02:30 +07:00

31 KiB
Raw Permalink Blame History

ADR-013 — Bất đồng bộ hoá bước khởi tạo thanh toán trong checkout

Tên file: adr/ADR-013_bat-dong-bo-hoa-khoi-tao-thanh-toan.md

Status Proposed
Date 2026-09-15
Người quyết Tech Lead + PO (chưa ký thật — xem điều kiện Accepted ở §3/§7)
Người đề xuất SA (qua skill sa-2-architecture, hoạt động adr)
Điểm radar 7/10 (chấm theo decision-radar.md §2: Chi phí đảo ngược=2 (>4 tuần — đã lộ ra FE, đổi lại phải thiết kế lại màn hình chờ/polling và state machine) · Bán kính ảnh hưởng=2 (Cart&Order, Payment Service, FE, BA/SRS US-004, ICD) · Chạm QAS Must=2 (trực tiếp QAS-002) · Ràng buộc dài hạn=1 (ràng trong phạm vi thiết kế hiện tại, xét lại được khi có sandbox thật) · Tranh cãi=0 (lần đầu đề xuất, chưa tranh luận nhiều vòng) → 5–7 ⇒ ADR bắt buộc, dưới ngưỡng 8 nên không bắt buộc POC trước khi Accepted, nhưng vẫn cần bài đo xác nhận theo §6)
Supersedes — (không supersede ADR nào — luồng đồng bộ trước đó là một phần SAD §6.1 "duyệt từng phần" dưới ngoại lệ gate, chưa từng là một ADR riêng, nên không có ADR nguồn để supersede; SAD §6.1 được sửa trực tiếp kèm dẫn chiếu ADR-013, xem SAD_e-commerce_v1.0.md v1.3 Change Log)
Superseded by —
Liên quan ASR-004 · QAS-002 · DRV-02 · CON-04 · ARISK-03 · ADR-004 (idempotency/đối soát webhook) · ADR-005 (SQS FIFO/EventBridge) · ADR-011 (chính sách resilience đối tác ngoài) · OQ-034 (đã chọn hướng B, DEC-22)

⚠️ ADR bất biến sau khi Accepted. Muốn đổi quyết định thì viết ADR mới có Supersedes: ADR-013 và đổi trạng thái bản cũ thành Superseded by. Không sửa nội dung ADR đã Accepted, không xoá ADR đã Rejected.


0. Ghi chú Preflight & ngoại lệ gate — bắt buộc đọc trước

AG1 vẫn chưa ký thật; AG2 (gate của chính GĐ2) chưa tới hạn. Theo SKILL.md, hoạt động 1 (QAS) → 2 (ASR) → 3 (SAD) đã hoàn tất và được duyệt từng phần từ trước (DEC-12/14/16); hoạt động 4 (ICD) và 8 (FAIL) đã chạy (DEC-18…22). Điều kiện đầu vào cho việc viết ADR-013 (kết luận của FAIL §1.2, đã được điều phối dự án chọn Lựa chọn B qua OQ-034/DEC-22) đã đủ. Người dùng (vai điều phối dự án chạy thử) đã được cảnh báo lại và khẳng định muốn tiếp tục.

DEC-23 (SA) — Viết ADR-013 và sửa nhất quán tối thiểu SAD §6.1/§8 + FAIL §1.2 cho e-commerce trong khi AG1 chưa ký thật và AG2 chưa tới hạn — tiếp nối DEC-01…22. Quyết định: Hiện thực hoá Lựa chọn B đã chọn ở OQ-034 (DEC-22) thành một ADR chính thức, mô tả đầy đủ luồng bất đồng bộ hoá khởi tạo thanh toán, và sửa SAD §6.1 (sequence diagram checkout) + SAD §8 (bảng ADR) + FAIL §1.2 (ngân sách timeout cộng dồn) cho khớp với quyết định này. Không sửa nội dung khác của SAD/FAIL ngoài phạm vi được giao. Giữ Confidence 🔴 cho tới khi: (a) AG1/AG2 ký thật, (b) Tech Lead + PO thật xác nhận ADR-013 (không chỉ điều phối dự án ký thay), (c) BA cập nhật SRS/AC cho US-004 Checkout theo trạng thái mới, (d) ICD lượt sau chốt endpoint/cơ chế FE lấy redirectUrl (polling hay SSE — OQ-041 mới). Người quyết: Điều phối dự án (đại diện PO, dự án chạy thử) — tiếp tục hoạt động dưới ngoại lệ; bản thân nội dung ADR-013 vẫn cần Tech Lead + PO thật ký trước khi Accepted. Radar (ước lượng cho việc tiếp tục dưới ngoại lệ, khác điểm radar 7/10 của chính ADR-013 ở trên): ~4 — chi phí đảo ngược thấp (đây là việc ghi tài liệu, không phải thi công) · bán kính ảnh hưởng: SAD/FAIL đã tồn tại, chỉ sửa đúng 2 mục theo yêu cầu người duyệt · chạm QAS-002 Must gián tiếp (đang sửa lại cách đạt nó, không phải hạ chuẩn) · không ràng buộc dài hạn tự thân (khác ADR-013 — bản thân việc chạy dưới ngoại lệ thì sửa được) · không tranh cãi mới, tiếp nối tiền lệ DEC-11…22 → ghi DEC-nn, không cần ADR riêng cho việc chạy dưới ngoại lệ. Hệ quả nếu không chấp nhận ngoại lệ: OQ-034 tiếp tục treo ở trạng thái "đã chọn hướng B nhưng chưa có ADR" — Dev không có tài liệu để thi công lại luồng checkout, SAD §6.1 và FAIL §1.2 tiếp tục mô tả một luồng đã biết là vượt ngân sách QAS-002 (~7%) mà không có phương án sửa chính thức.


1. Bối cảnh

FAIL_e-commerce_v1.0.md §1.2 (bảng ngân sách timeout cộng dồn đường găng checkout) phát hiện: ngay cả sau khi siết timeout VNPay/Momo xuống 1.500ms (thay 10s kế thừa CTX §4.2), tổng thời gian phản hồi POST /v1/checkout ở kịch bản worst-case cộng dồn đạt 3.220ms — vượt QAS-002 (≤3.000ms, mức Must) khoảng 7%. Nguyên nhân gốc: một mình bước "Payment Service gọi VNPay/Momo" (bước 8 trong bảng ngân sách) đã chiếm phần lớn ngân sách còn lại, trong khi ta không kiểm soát được SLA thật của đối tác ngoài (CON-04 — SLA VNPay/Momo/GHN/GHTK chưa xác nhận; ARISK-03 — chưa có sandbox thật để đo). Đây đúng là tình huống vi phạm D6: "timeout tầng ngoài phải lớn hơn timeout tầng trong" — ở luồng đồng bộ hiện tại, timeout tầng trong (đối tác thanh toán) đang lớn hơn timeout tầng ngoài (toàn bộ checkout).

DRV-02 (giỏ hàng ≥2 seller có nguy cơ tỷ lệ bỏ giỏ cao hơn ở bước checkout) là driver kinh doanh đứng sau — nếu checkout thường xuyên chậm hoặc timeout do đối tác thanh toán, rủi ro bỏ giỏ càng tăng đúng ở bước cuối cùng, nơi thiệt hại kinh doanh là lớn nhất (khách đã hoàn tất chọn hàng và tách đơn theo seller).

Ràng buộc đang chi phối:

Nguồn Nội dung
QAS-002 Latency checkout POST /v1/checkout: p95 ≤ 3 giây, mức Must — không đạt thì không được go-live theo định nghĩa Must của sa-2-architecture/SKILL.md
ASR-004 Webhook xác nhận thanh toán phải idempotent + có job đối soát bù — ép kiến trúc chấp nhận eventual consistency có giới hạn thời gian cho trạng thái thanh toán; ADR-013 mở rộng đúng tinh thần này sang cả bước khởi tạo thanh toán, không chỉ bước xác nhận
CON-04 SLA thật của đối tác thanh toán/vận chuyển ngoài chưa được xác nhận bằng hợp đồng
ARISK-03 Chưa có sandbox thật VNPay/Momo để đo latency init thật — mọi con số timeout hiện tại là đề xuất SA

Cái đã biết chắc / cái còn là giả định:

Điều 🟢 Đã kiểm chứng / 🔴 Giả định Bằng chứng
Tổng ngân sách worst-case cộng dồn của luồng đồng bộ hiện tại = 3.220ms > 3.000ms 🟢 Đã kiểm chứng (bằng tính toán cộng dồn, không phải đo thật) FAIL_e-commerce_v1.0.md §1.2 bảng 10 bước
Tách đường găng đồng bộ (bước 1–7, 10) khỏi bước khởi tạo thanh toán (bước 8) đưa tổng thời gian phản hồi xuống ≤3.000ms bất kể VNPay/Momo chậm bao lâu 🟢 Đã kiểm chứng bằng tính toán (1.670ms + 50ms = 1.720ms, còn dư ~1.280ms so với ngân sách) Xem bảng §2.3 dưới
VNPay/Momo hoàn tất init trong ≤10s ở phần lớn trường hợp khi không còn bị ép timeout ngắn 🔴 Giả định mới — ASM-34 Cần sandbox thật (ARISK-03) để xác minh; nếu sai, cửa sổ chờ của FE (§2.2) cần tăng hoặc coi thất bại sớm hơn
FE có thể triển khai polling hoặc kênh đẩy (SSE/WebSocket) để lấy redirectUrl sau khi có 🔴 Giả định — chưa có xác nhận năng lực FE hiện tại Cần Tech Lead FE xác nhận — xem OQ-041

2. Phương án đã cân nhắc

PA-A — Giữ đồng bộ, siết timeout hơn nữa

Mô tả Giảm timeout gọi VNPay/Momo trong nhánh đồng bộ xuống 1.200ms (thay vì 1.500ms đã đề xuất ở FAIL v1.0), đưa tổng ngân sách cộng dồn còn ~2.920ms, có margin ~80ms
Ưu Không đổi cấu trúc luồng, không đổi UX FE, không cần state machine mới, không cần BA viết lại SRS US-004
Nhược Chỉ là "ép con số" chứ không giải quyết nguyên nhân gốc — ta vẫn không kiểm soát được SLA thật của VNPay/Momo (CON-04/ARISK-03 chưa có sandbox). Nếu đối tác thật cần >1.200ms để phản hồi (rất có thể, vì 10s là con số kế thừa từ hợp đồng gốc, không phải p95 thật đo được), tỷ lệ timeout/thất bại init tăng, khách phải tự bấm "Thử lại" nhiều lần — đúng lúc rủi ro bỏ giỏ hàng cao nhất (DRV-02). Margin 80ms cũng rất mỏng, một dao động nhỏ ở bước 1 (network client→ALB, hiện đề xuất 150ms, ASM-31 chưa đo thật) đã có thể lại vượt ngân sách
Chi phí đảo ngược Thấp về mặt kỹ thuật (chỉ đổi số cấu hình) — nhưng rủi ro kinh doanh (bỏ giỏ hàng) không định lượng được trước và có thể đã gây thiệt hại trước khi phát hiện

PA-B — Bất đồng bộ hoá bước khởi tạo thanh toán (chọn)

Mô tả Tách bước gọi VNPay/Momo ra khỏi đường găng đồng bộ của POST /v1/checkout. Order/OrderSeller được tạo và commit ở trạng thái chờ, response trả về ngay cho client trong ngân sách đồng bộ (~1.720ms, xem §2.3); việc gọi VNPay/Momo và lấy redirectUrl diễn ra sau, bất đồng bộ. FE lấy redirectUrl qua một cơ chế riêng (polling hoặc kênh đẩy — OQ-041). COD không đổi (không phụ thuộc bước này)
Ưu QAS-002 được đảm bảo về mặt cấu trúc — không còn phụ thuộc vào SLA của bên thứ ba ta không kiểm soát được, đúng tinh thần D6. Hệ quả phụ tích cực: TX DB không còn phải giữ mở xuyên qua lời gọi mạng tới Payment Service (giải quyết một phần mối lo bulkhead đã nêu ở OQ-037/OQ-038 của FAIL)
Nhược Đổi UX checkout đáng kể (khách không nhận redirectUrl ngay, cần chờ/poll); thêm độ phức tạp state machine cho Order; cần BA viết lại SRS/AC cho US-004; cần ICD lượt sau chốt endpoint mới; cần xử lý "Order kẹt ở trạng thái chờ" nếu bước async không bao giờ hoàn tất (tương tự khoảng trống outbox đã ghi ở FAIL §7)
Chi phí đảo ngược Cao — >4 tuần nếu phải quay lại đồng bộ sau khi FE đã triển khai polling/kênh đẩy và BA đã cập nhật SRS theo trạng thái mới

PA-C — Giữ đồng bộ, chấp nhận vượt ngân sách QAS-002

Mô tả Không sửa gì, chấp nhận p95 lý thuyết worst-case 3.220ms, coi đây là biên an toàn hiếm khi chạm tới trong thực tế (đa số request sẽ nhanh hơn nhiều vì không phải mọi bước đều đồng thời chạm timeout tối đa)
Ưu Không tốn công sức thiết kế lại, không đổi UX, không đổi tài liệu
Nhược Vi phạm trực tiếp một QAS mức Must — theo định nghĩa Must ("không đạt thì không được go-live") và tiêu chí chặn của AG2 ("còn interface/QAS nào không đạt mà không có phương án"), đây không phải lựa chọn SA có thẩm quyền tự chấp nhận (D10) — chấp nhận rủi ro vi phạm một ràng buộc chất lượng đã cam kết là quyết định của PO/Tech Lead, không phải mặc định giữ nguyên trong im lặng
Chi phí đảo ngược Thấp về kỹ thuật, nhưng không phải là một quyết định — đây là không quyết định gì cả và để lại một QAS Must đã biết là không đạt trôi tới go-live

Bảng so sánh

Tiêu chí (sinh từ QAS-002/CON-04/DRV-02) PA-A PA-B PA-C
Đảm bảo QAS-002 (Must) bất kể SLA đối tác 🟡 Không chắc chắn (margin mỏng, phụ thuộc giả định 1.200ms) 🟢 Có (về mặt cấu trúc) 🔴 Không
Không phụ thuộc SLA đối tác ngoài chưa xác nhận (CON-04) 🔴 Vẫn phụ thuộc 🟢 Độc lập 🔴 Vẫn phụ thuộc
Chi phí thay đổi FE/BA 🟢 Không đổi 🔴 Đổi đáng kể 🟢 Không đổi
Rủi ro bỏ giỏ hàng do timeout init (DRV-02) 🟡 Có thể tăng, không định lượng được 🟢 Loại bỏ nguyên nhân này 🔴 Không xử lý
Đúng thẩm quyền quyết định (D10) ✅ SA có thể đề xuất, PO/Tech Lead chọn ✅ SA có thể đề xuất, PO/Tech Lead chọn ❌ Vi phạm Must trong im lặng, không phải lựa chọn hợp lệ để "không làm gì"

3. Quyết định

Chọn PA-B — Bất đồng bộ hoá bước khởi tạo thanh toán online (VNPay/Momo) trong POST /v1/checkout.

Vì sao: Đây là cách duy nhất trong ba phương án đảm bảo QAS-002 (Must) đạt được về mặt cấu trúc, không phụ thuộc vào một SLA đối tác ngoài mà CON-04/ARISK-03 xác nhận là chưa kiểm chứng được. PA-A chỉ giảm nhẹ rủi ro mà không loại bỏ nguyên nhân gốc; PA-C vi phạm trực tiếp một ràng buộc Must mà SA không có thẩm quyền tự chấp nhận thay Tech Lead/PO (D10).

Phạm vi áp dụng: Chỉ áp dụng cho phương thức thanh toán online qua cổng bên thứ ba (VNPay, Momo). COD không đổi — vì COD không gọi ra ngoài, xác nhận ngay trong luồng đồng bộ hiện có (bước 7 cũ), không có nguyên nhân gây vượt ngân sách. Áp dụng cho POST /v1/checkout (US-004, chưa có SRS/AC chính thức từ BA — xem §5 Việc phát sinh).

3.1 Mô tả luồng mới

Đường găng đồng bộ (không đổi thứ tự, chỉ dừng lại sớm hơn):

Bước Thành phần Timeout đề xuất (kế thừa FAIL §1.2) Cộng dồn
1 Client → ALB 150ms 150ms
2 ALB → Cart & Order 20ms 170ms
3 Xác thực/ownership (cache hit) 50ms 220ms
4 Kiểm tra/giữ tồn kho (in-process) 150ms 370ms
5 Áp dụng coupon/điểm (IF-021) 400ms 770ms
6 Ghi Order/OrderSeller/OrderItem (TX) — COMMIT ngay sau bước này, không giữ TX mở chờ Payment nữa 300ms 1.070ms
7 (chỉ áp dụng cho COD) Payment Service xác nhận COD, đồng bộ 600ms 1.670ms (nhánh COD)
10 Response serialization + trả về client 50ms 1.720ms (nhánh COD)

(nhánh VNPay/Momo dừng đường găng đồng bộ ngay sau bước 6 + bước 10, không đi qua bước 7 đồng bộ nữa — xem bảng dưới)

Bước Thành phần Timeout Cộng dồn (nhánh VNPay/Momo)
1–6 (giống bảng trên) — 1.070ms
10 Response serialization + trả về client — trả orderId + status: PENDING_PAYMENT_INIT, chưa có redirectUrl 50ms 1.120ms

Kết luận: cả hai nhánh đều nằm sâu dưới QAS-002 (≤3.000ms) — nhánh VNPay/Momo còn dư ~1.880ms margin so với ngân sách, đủ bù đắp mọi sai lệch chưa đo thật ở bước 1/3/4/5 (ASM-27, ASM-28, ASM-31).

Bất đồng bộ (sau khi đã trả response, không tính vào QAS-002):

stateDiagram-v2
    [*] --> PENDING_PAYMENT_INIT: TX commit (bước 6), response 201 trả về (bước 10)
    PENDING_PAYMENT_INIT --> PAYMENT_INIT_READY: Payment Service gọi VNPay/Momo thành công, có redirectUrl
    PENDING_PAYMENT_INIT --> PAYMENT_INIT_FAILED: Hết retry / timeout / lỗi từ VNPay/Momo
    PAYMENT_INIT_FAILED --> PENDING_PAYMENT_INIT: Khách chọn "thử lại" (retry init, cùng Idempotency-Key)
    PAYMENT_INIT_FAILED --> COD: Khách chọn chuyển sang COD (nếu seller/sản phẩm hỗ trợ)
    PAYMENT_INIT_READY --> [*]: Khách hoàn tất thanh toán trên trang VNPay/Momo (luồng webhook — ADR-004, không đổi)

Cơ chế trigger bước async: Cart & Order publish message PaymentInitRequested (message group = seller_id, dedupe theo eventId, cùng cơ chế SQS FIFO đã có — ADR-005) ngay sau khi commit TX (bước 6), cùng lúc với việc publish OrderPlaced đã có sẵn (không phải luồng mới hoàn toàn, tái dùng hạ tầng message backbone hiện có). Payment Service (CMP-11) consume message này, gọi VNPay/Momo với timeout 10s kế thừa nguyên trạng CTX §4.2 (không cần ép ngắn nữa, vì không còn nằm trên đường găng phản hồi người dùng), cập nhật trạng thái, publish PaymentInitReady/PaymentInitFailed.

Idempotency của bước async: Idempotency-Key gốc của POST /v1/checkout (ICD §3.3, ADR-004) được truyền kèm message PaymentInitRequested — nếu Payment Service consume message này nhiều lần (redelivery do visibility timeout, ASM-29), lời gọi VNPay/Momo không bị lặp lại sau khi đã có gatewayTransactionRef (kiểm tra trạng thái hiện tại trước khi gọi lại — check- then-act, cùng nguyên tắc đã áp dụng cho GHN/GHTK ở FAIL FM-03/04).

Timeout tổng thể của giai đoạn async (góc nhìn UX, không phải ràng buộc kiến trúc cứng): đề xuất coi là "quá lâu, cần hành động" nếu sau 15 giây kể từ khi tạo Order mà vẫn ở PENDING_PAYMENT_INIT (10s timeout đối tác + margin xử lý/hàng đợi) — khi đó FE nên chuyển sang trạng thái cho phép khách chủ động thao tác (xem §3.2) thay vì chờ vô thời hạn. Đây là con số đề xuất SA, gắn OQ-042 (thời gian giữ Order ở PENDING_PAYMENT_INIT trước khi coi là "kẹt" và cần job dọn dẹp/hủy tự động).

3.2 Cơ chế FE lấy redirectUrl — đề xuất, chưa chốt

Hai lựa chọn kỹ thuật cho FE nhận biết khi PAYMENT_INIT_READY sẵn sàng:

Cơ chế Mô tả Đánh đổi
Polling (đề xuất mặc định) FE gọi lặp lại một endpoint trạng thái (đề xuất GET /v1/orders/{orderId}/payment-init-status, chưa chốt tên — ICD lượt sau) mỗi ~1 giây, tối đa ~15 giây Đơn giản, không cần hạ tầng mới (WebSocket/SSE), hoạt động tốt qua mọi proxy/CDN hiện có; tốn thêm request lặp lại (tải nhỏ, ngắn hạn, không đáng kể so với QAS-009)
SSE / WebSocket Server đẩy sự kiện khi trạng thái đổi Trải nghiệm mượt hơn (không có độ trễ polling interval), nhưng cần thêm thành phần hạ tầng (kết nối lâu dài, cân bằng tải phù hợp), tăng độ phức tạp vận hành mà đội chưa xác nhận có kinh nghiệm (OQ-010 vẫn mở)

🔴 Đây là đề xuất, không phải quyết định cuối — ADR-013 chỉ chốt việc bất đồng bộ hoá, không chốt cơ chế truyền tải cụ thể. Ghi OQ-041 (mới), chờ Tech Lead (kèm ý kiến FE) xác nhận; hệ quả phương án ngược: chọn polling mà tải cao bất thường có thể tăng số request nhỏ không cần thiết (giảm thiểu bằng interval hợp lý + dừng poll sau 15s hoặc khi có kết quả); chọn SSE/WebSocket mà đội chưa có kinh nghiệm vận hành production kênh kết nối lâu dài có thể phát sinh sự cố khó chẩn đoán đúng lúc ra mắt.

3.3 Hành vi khi khởi tạo thất bại (PAYMENT_INIT_FAILED)

Khi hết retry / timeout ở bước async (VNPay/Momo không phản hồi được trong 10s, hoặc trả lỗi): Order chuyển PAYMENT_INIT_FAILED. FE (qua polling/kênh đẩy) nhận trạng thái này và cho khách hai lựa chọn, không tự động rollback/huỷ Order:

  1. Thử lại cùng phương thức — gọi lại endpoint khởi tạo (đề xuất tái dùng cùng Idempotency-Key, ICD lượt sau chốt contract cụ thể) → quay lại PENDING_PAYMENT_INIT.
  2. Chuyển sang COD (nếu seller/sản phẩm trong đơn hỗ trợ COD) hoặc chọn phương thức online khác (VNPay ⇄ Momo) — tái sử dụng cùng Order/OrderSeller đã tạo, chỉ đổi phương thức thanh toán, không tạo Order mới (tránh trùng đơn, giữ đúng ownership tồn kho đã reserve ở bước 4).

🔴 Chi tiết endpoint/contract cho hai thao tác trên chưa chốt — thuộc hoạt động icd lượt sau (xem §5 Việc phát sinh).


4. Phương án bị loại và lý do

Phương án Loại vì Gắn với Điều kiện nào thì xét lại
PA-A (siết timeout xuống 1.200ms, giữ đồng bộ) Không loại bỏ nguyên nhân gốc (phụ thuộc SLA đối tác chưa xác nhận, CON-04); margin còn lại (~80ms) quá mỏng so với các con số chưa đo thật khác trong cùng ngân sách (ASM-27/28/31) CON-04, ARISK-03 Nếu có sandbox thật và VNPay/Momo xác nhận SLA hợp đồng ≤1.000ms p99, có thể xét lại PA-A như một tối ưu bổ sung (không nhất thiết thay thế PA-B, có thể kết hợp: PA-B đã chọn + timeout đối tác chặt hơn nếu SLA cho phép)
PA-C (giữ đồng bộ, chấp nhận vượt QAS-002) Vi phạm trực tiếp một QAS mức Must mà SA không có thẩm quyền tự chấp nhận thay Tech Lead/PO (D10); không phải "không làm gì" là an toàn — đây là để một rủi ro đã biết trôi tới go-live trong im lặng QAS-002 (Must) Chỉ xét lại nếu PO/Tech Lead chính thức hạ mức QAS-002 từ Must xuống Should/Could kèm lý do nghiệp vụ — đây là quyết định PO, không phải kỹ thuật

"Giữ đồng bộ vì đơn giản hơn cho FE" là lý do có thật (chi phí thi công thấp hơn) — nhưng không đủ để thắng một ràng buộc Must đã lượng hoá; ghi nhận ở đây để không ai đề xuất lại PA-C mà không đọc lý do đã bị loại.


5. Hệ quả

Hệ quả tích cực

  • QAS-002 (Must) được đảm bảo về mặt cấu trúc cho mọi request VNPay/Momo — không còn phụ thuộc SLA đối tác ngoài mà ta không kiểm soát (CON-04/ARISK-03), đúng nguyên tắc D6.
  • Loại bỏ hoàn toàn tình huống giữ transaction DB mở xuyên qua một lời gọi mạng ra ngoài container (TX nay commit ngay sau bước 6) — giảm trực tiếp rủi ro cạn connection pool đã nêu ở FAIL OQ-037/OQ-038, dù không đóng hẳn hai OQ đó (bulkhead vẫn cần cho các phụ thuộc khác).
  • Payment Service có thể dùng lại nguyên trạng timeout 10s kế thừa CTX §4.2 cho VNPay/Momo mà không cần "ép" xuống 1.500ms như đề xuất tạm ở FAIL v1.0 — giảm rủi ro timeout giả (false timeout) khi đối tác chỉ chậm tạm thời.

Hệ quả tiêu cực phải sống chung

  • UX checkout đổi đáng kể: khách không nhận redirectUrl ngay trong response đầu tiên, cần chờ một khoảng thời gian ngắn (kỳ vọng thường vài trăm ms tới vài giây, tối đa đề xuất 15s trước khi coi là bất thường) — cần màn hình chờ/trạng thái mới ở FE, chưa thiết kế ở lượt này.
  • Thêm độ phức tạp: state machine 4 trạng thái mới cho Order (PENDING_PAYMENT_INIT → PAYMENT_INIT_READY / PAYMENT_INIT_FAILED → …), thêm 1 loại message (PaymentInitRequested) cần DLQ/retry riêng (tương tự các message khác đã có ở ADR-005).
  • Khoảng trống chưa xử lý: nếu message PaymentInitRequested bị mất hoặc Payment Service không bao giờ xử lý (sự cố), Order kẹt vĩnh viễn ở PENDING_PAYMENT_INIT — cần job quét dọn (cùng bản chất với khoảng trống outbox đã ghi ở FAIL §7, chưa thiết kế chi tiết, thuộc hoạt động dat/inf sắp tới). Ghi OQ-042.

Cái quyết định này khoá lại

Muốn đổi về sau thì Tốn
Quay lại luồng đồng bộ hoàn toàn (bỏ polling/kênh đẩy) Thiết kế lại FE (bỏ màn hình chờ), viết ADR mới Supersedes: ADR-013, cập nhật lại SRS US-004 lần nữa, và chỉ khả thi nếu có sandbox thật chứng minh SLA đối tác đủ nhanh (ARISK-03 đã đóng)
Đổi cơ chế polling ↔ SSE/WebSocket (sau khi đã chọn một trong hai ở OQ-041) Đổi tầng truyền tải FE↔BE, không đổi state machine Order — chi phí trung bình, không cần ADR mới (thuộc ICD, không phải quyết định kiến trúc cấu trúc)

Việc phát sinh

Việc Chủ Hạn Ghi ở đâu
Cập nhật SAD §6.1 (sequence diagram checkout) + §8 (bảng ADR) theo ADR-013 SA Đã làm cùng lượt (SAD v1.3) SAD_e-commerce_v1.0.md
Cập nhật FAIL §1.2 (ngân sách timeout cộng dồn, tách đường găng đồng bộ khỏi bước async) SA Đã làm cùng lượt (FAIL v1.1) FAIL_e-commerce_v1.0.md
Chốt endpoint/contract: trạng thái polling, retry init, chuyển COD SA (hoạt động icd lượt sau) Trước khi Dev thi công US-004 ICD_e-commerce_v1.0.md (bổ sung IF mới)
Viết/cập nhật SRS/AC cho US-004 Checkout với trạng thái PENDING_PAYMENT_INIT/PAYMENT_INIT_READY/PAYMENT_INIT_FAILED BA Trước khi Dev thi công ba-output/e-commerce/03-specification/SRS_US004_*.md (chưa tồn tại)
Thiết kế job quét Order kẹt ở PENDING_PAYMENT_INIT quá hạn (OQ-042) SA (hoạt động dat/inf lượt sau) Trước AG2 DAT_e-commerce_v1.0.md (chưa tồn tại)
Thêm fitness function đo p95 checkout thật ≤3s bất kể VNPay/Momo chậm SA/QA (GĐ3) GĐ3 FIT-23 (mới, xem §6)

6. Cách kiểm chứng quyết định này được tuân thủ

Cách kiểm Công cụ Chạy ở đâu FIT
Đo p95 POST /v1/checkout (nhánh VNPay/Momo) ≤3.000ms ngay cả khi giả lập VNPay/Momo phản hồi chậm (chaos, ép độ trễ 9–10s ở bước async) — xác nhận response không bị ảnh hưởng k6 + chaos injection (giả lập độ trễ đối tác) Staging FIT-23 (mới, cộng thêm vào danh sách FIT-16…22 đã đề xuất ở FAIL §8)
Xác nhận không có Order nào giữ TX DB mở quá bước 6 (kiểm tra thời lượng transaction qua log/APM) Kiểm tra APM/log transaction duration Staging + production sau go-live FIT-24 (mới)
Xác nhận message PaymentInitRequested idempotent — gửi trùng không gọi lại VNPay/Momo khi đã có gatewayTransactionRef Test tích hợp: publish trùng message, xác nhận chỉ 1 lời gọi gateway Staging FIT-25 (mới)

Cả ba FIT-23/24/25 là ứng viên, chưa viết thật (thuộc GĐ3 sa-3-enablement, cùng nhóm với FIT-16…22 đã đề xuất ở FAIL §8) — không giả vờ đã kiểm chứng.


7. Điều kiện xét lại

Dấu hiệu Ngưỡng Ai theo dõi
Tỷ lệ Order rơi vào PAYMENT_INIT_FAILED sau go-live > 5% tổng số checkout VNPay/Momo trong 1 tuần (đề xuất, chưa xác nhận — cần baseline thật sau go-live) Tech Lead + PO
Thời gian trung bình từ PENDING_PAYMENT_INIT → PAYMENT_INIT_READY > 5 giây p95 (gần chạm ngưỡng "kẹt" 15s ở §3.1) SRE
Sandbox VNPay/Momo thật cho thấy SLA p99 ổn định ≤ 1.000ms Có báo cáo SLA chính thức từ đối tác Tech Lead (đóng ARISK-03)
Phản hồi người dùng về UX chờ/poll (khảo sát/CSKH) cho thấy trải nghiệm xấu đáng kể Ngưỡng định tính, cần PO đánh giá PO

Điều kiện chuyển Accepted: Tech Lead + PO thật (không phải ký thay dưới ngoại lệ DEC-01) xác nhận nội dung §3 (mô tả luồng), đồng thời BA xác nhận đã cập nhật SRS/AC cho US-004 theo trạng thái mới. Radar 7/10 (dưới ngưỡng 8) nên không bắt buộc POC trước Accepted, nhưng khuyến nghị mạnh chạy FIT-23 (chaos đo p95) ít nhất một lần ở staging trước khi go-live, vì đây là ràng buộc chất lượng mức Must.


8. Tham chiếu

  • POC: (không bắt buộc — radar 7/10, dưới ngưỡng 8)
  • Bài đo: FIT-23/24/25 (ứng viên, chưa chạy — xem §6)
  • Tài liệu ngoài: CTX_e-commerce_v1.0.md §4.2 (timeout đối tác kế thừa 10s)
  • Thảo luận: FAIL_e-commerce_v1.0.md §1.2 (phát hiện xung đột ngân sách), OQ-034/DEC-22 (quyết định chọn Lựa chọn B, 2026-09-15, điều phối dự án thay mặt Tech Lead/PO)

9. Review log (không đổi Status)

Ngày Người review Vai trò Quyết định Lý do chưa chuyển Accepted
2026-09-15 Điều phối dự án (thay mặt Tech Lead, chế độ chạy thử) Tech Lead (ký thay, ngoại lệ DEC-01) Reviewed (Proposed giữ nguyên) — nội dung §3 (state machine PENDING_PAYMENT_INIT/PAYMENT_INIT_READY/PAYMENT_INIT_FAILED, timeout async 10s, idempotency, hành vi thất bại) đủ làm cơ sở thiết kế tiếp (ICD/DAT/FE) Chưa có chữ ký thật Tech Lead + PO (§7 điều kiện Accepted); BA chưa viết SRS/AC cho US-004 (OQ-034)

Đây là ghi nhận review nội dung, không phải "sign" theo nghĩa gate: Status giữ nguyên Proposed. Xem 00-index/ADL_e-commerce.md (Change Log — Review 2026-09-15) và ADL §8 "Việc phải làm" (mục #9) cho điều kiện chuyển Accepted. Confidence tổng thể của lượt review: 🔴 — AG2 chưa ký.