# 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`):** ```mermaid 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ý.