Skip to content

v1.8 — 15/05/2026

Thay đổiSectionẢnh hưởng
Update smoke test Step 8 (E4.1 T+30): enforce DEC-027 — đơn prepaid_order.status='pending' chưa thanh toán → KHÔNG có lot trong DB; sau khi thanh toán đủ → 1 lot active với credited_amount = initial_amountE4.1QA, Ops

Checklist phát hành (Go-Live) - Ví KM 2 (Promotion Wallet 2)

Tham chiếu: PRD v1.8 + SOURCE_OF_TRUTH v1.6 | Ngày: 15/05/2026


File này dùng để làm gì: chốt gate trước deploy, pre-check, rollback và ký duyệt cho rollout Ví KM 2. Nên đọc trước: decision-brief.md -> E1) Cổng phát hành -> E2) Kiểm tra trước triển khai code -> E4) Các bước deploy.

Quy tắc ưu tiên: nếu có conflict với SOURCE_OF_TRUTH.md, ưu tiên SOURCE_OF_TRUTH.md. File này là owner của readiness, thứ tự deploy, rollback và ký duyệt vận hành.

Đầu vào chuẩn (Canonical Inputs)

FileVai tròNếu xung đột
SOURCE_OF_TRUTH.mdNguồn sự thật chuẩn + khóa giải phápƯu tiên cao nhất
decision-brief.mdTóm tắt phạm vi/rủi ro/handoffChỉ định hướng ưu tiên readiness
prd.mdScope, lifecycle, ràng buộc nghiệp vụTheo truth đã khóa
dev-spec.mdData/API/migration/task/securityTheo truth đã khóa
qa-test-plan.mdĐiều kiện vào/ra và regression oracleQA owner
handoff.mdKế hoạch, RACI, điểm còn mởhandoff.md là file chịu trách nhiệm

E1) Cổng phát hành

#Cổng kiểm traPhụ tráchĐiều kiện pass
1PO ký duyệt PRD và Decision BriefPOKhông còn critical gap ở FR/DEC/formula
2TL ký duyệt Đặc tả kỹ thuậtTech LeadIdempotency, FIFO lock, expiry guard, rollback được chốt
3QA pass P0/P1QA LeadD5 exit criteria pass, không còn bug critical/major
4Migration/metadata verify trên stagingBE DevTable/column/index/action/cron query verify thành công
5Smoke test payment/wallet/reportQA + POBán Gói Ví KM2, thanh toán, refund, expiry dry-run, print/fund/report pass
6Monitoring sẵn sàngTL + OpsLog/metric/alert cho payment, scheduler, expired balance đã cấu hình

E2) Kiểm tra trước triển khai code

BE Dev

  • [ ] Migration timestamp mới nhất được chốt cho walletecommerce.
  • [ ] wallet_type, payment_gateway, payment_gateway_wallet_type seed không trùng.
  • [ ] wallet_km2_config seed đúng 1 global row (branch_id IS NULL, disabled=true), có uq_wallet_km2_config_global_rowuq_wallet_km2_config_global_active.
  • [ ] Metadata không cấp direct select role user cho wallet_km2_lot / wallet_km2_lot_deduction; customer profile đọc qua action get_customer_km2_lots.
  • [ ] Dynamic Permission v2 seed/verify xong: module_permission_action, role_module.actions, portal, branch_mode cho KM2 đúng SOURCE_OF_TRUTH.
  • [ ] ModuleOperationMapping/auth mapping đã map các action: internal_configuration:update, customer_management:access/view_all, prepaid_order:create/payment, service_order:payment, product_order:payment, refund_request_management_submenu:access/create/update/approve/payment.
  • [ ] deduct_km2_payment có idempotency key theo order_id + payment attempt/request id.
  • [ ] refund_km2_wallet chạy trong luồng Yêu cầu hoàn tiền hiện có; label UI là "Hoàn ví KM2"; không còn metadata/action legacy cũ.
  • [ ] Payment action bỏ qua lot hết hạn bằng guard expired_at > NOW() trước khi lock/deduct.
  • [ ] Impact boundary đã được verify: KM1/report/fund/print/notification tách KM2, và các module không liên quan không bị mở scope hoặc đổi dữ liệu.
  • [ ] down.sql rollback được test trên staging clone.

FE Dev

  • [ ] Route settings dùng shell hiện có /s/internal-settings/promotion-wallet.
  • [ ] Report dashboard để sau PD-001; Phase 1/2 không hardcode route report chưa chốt.
  • [ ] Form state có dirty-state, confirm khi rời màn hình nếu chưa lưu.
  • [ ] Payment UI ẩn/hiện KM1/KM2 đúng toggle allow_combine_km1.

QA

  • [ ] Seed DS-001 tạo đủ: config, 2 Gói Ví KM2, wallet type, eligible/non-eligible item.
  • [ ] Test account có đủ tổ hợp permission: có/không có internal_configuration:update, service_order:payment, product_order:payment, customer_management:access/view_all, refund_request_management_submenu:access/create/update/approve/payment; branch scope rõ.
  • [ ] Grant/revoke permission cần refresh/relogin/refetch theo cơ chế Dynamic Permission hiện có và đã có test API no-leak.
  • [ ] Test race condition có 2 request thanh toán song song.
  • [ ] Chạy đủ TC-IMPACT-* trước UAT để xác nhận feature ảnh hưởng/không ảnh hưởng đúng biên đã khóa.

Gate kỹ thuật trước merge / UAT

GateKhi nào bắt buộc passPhụ tráchĐiều kiện pass tối thiểu
TG-001 Hoàn ví KM2Trước merge Phase 2 refundTL + FE + BE + QAYêu cầu hoàn tiền có option "Hoàn ví KM2", behavior refund_km2_wallet, không hard-code VND_PROMOTION, cập nhật đúng lot/deduction/audit
TG-002 Hard-code KM1/KM2Trước merge Phase 1 payment/invoice/report/fundTL + BE + FEGrep wallet_promotion / VND_PROMOTION / wallet_type_id đã phân loại; không còn occurrence chưa xử lý trong affected features
TG-003 FIFO/idempotency/expiryTrước merge deduct_km2_paymentTL + BECó transaction lock, idempotency key, guard expired_at > NOW(), retry/race test pass
TG-004 DB/Hasura/codegenTrước FE integration và UATBE + FEMigration/metadata/codegen pass; action/field KM2 tồn tại đúng contract dev-spec
TG-005 Dynamic Permission v2Trước UAT permissionTL + BE + FE + QASeed/action mapping đúng; FE ẩn/hiện đúng; API direct call thiếu quyền bị chặn
TG-006 Scheduler/rollback/monitoringTrước go-liveTL + Ops + BECron dry-run pass, rollback disable config/cron thử được, metric/log/alert có owner
TG-007 Impact boundary regressionTrước UAT sign-offQA + TLTC-IMPACT-* pass; affected/unaffected đúng biên đã khóa trong SOURCE_OF_TRUTH

handoff.md là owner chi tiết của TG-001..TG-007. Checklist này chỉ dùng để chặn merge/UAT/go-live.

E3) Kiểm tra trước deploy

Database / Hasura

  • [ ] Apply migration tạo wallet_km2_lot, wallet_km2_lot_deduction, wallet_km2_config.
  • [ ] Apply migration alter prepaid_card, product, service, invoice/report field nếu cần.
  • [ ] Apply Hasura metadata table permissions, action get_customer_km2_lots, action deduct_km2_payment, action refund_km2_wallet nếu dùng action riêng, cron wallet_km2_expire_lots.
  • [ ] Verify query:
    • SELECT id FROM wallet_type WHERE id = 'VND_PROMOTION_2';
    • SELECT COUNT(*) FROM wallet_km2_config WHERE branch_id IS NULL AND disabled = true AND deleted_at IS NULL; → trước enable phải = 1
    • SELECT COUNT(*) FROM wallet_km2_config WHERE branch_id IS NULL AND disabled = false AND deleted_at IS NULL; → trước enable phải = 0
    • SELECT COUNT(*) FROM payment_gateway_wallet_type WHERE payment_gateway_id = 'wallet_promotion_2';
    • Metadata check: không có select_permissions role user trong public_wallet_km2_lot.yamlpublic_wallet_km2_lot_deduction.yaml.

Backend

  • [ ] wallet-api deploy có action handler + scheduler handler.
  • [ ] ecommerce-api deploy có payment max% validation, order confirm balance check, refund logic.
  • [ ] pkg/store mapping/balance skip list đã thêm wallet_promotion_2 / VND_PROMOTION_2.
  • [ ] Log structured events cho lot created/deducted/expired/refunded.

Frontend

  • [ ] Admin settings KM2 load/save được.
  • [ ] PrepaidCard form có wallet_target, expiry_months.
  • [ ] Prepaid order summary hiện đúng dòng/cột KM2 conditional.
  • [ ] Payment order ẩn/hiện KM2 theo config, balance, eligible item.
  • [ ] Customer profile hiện KM2 card/tab/lot state.

E4) Các bước deploy

E4.0) Tóm tắt — bảng cũ giữ làm overview

#BướcPhụ tráchCách kiểm chứngRollback
1Backup DB trước migrationOpsBackup job completedRestore backup nếu migration lỗi nghiêm trọng
2Apply DB migrations wallet + ecommerceBE DevVerify SQL E3 passChạy down.sql nếu chưa có data production
3Apply Hasura metadataBE DevMetadata reload không lỗiRevert metadata commit trước đó
4Deploy wallet-apiBE Dev/healthz, action dry-run, scheduler dry-runRollback image trước
5Deploy ecommerce-apiBE DevPayment smoke test staging/prod canaryRollback image trước
6Deploy FE adminFE DevRoute settings/payment/customer load đúngRevert build trước
7Bật config KM2PO + AdminSettings save, payment method visible, disabled=false đúng 1 global rowTắt disabled=true cho config/payment method

E4.1) Migration Runbook minute-by-minute (BẮT BUỘC chạy đúng kịch bản)

Mục tiêu: Day-0 deploy không cần Ops hỏi TL từng bước. Mọi command + rollback action phải in trước, có người ký duyệt từng giai đoạn.

Maintenance window: 60 phút sau giờ thấp điểm (gợi ý 23:00-00:00 Asia/Ho_Chi_Minh, sau khi POS đóng ca tối). Tổng deploy kế hoạch ~45 phút, dự phòng 15 phút buffer.

Tham gia bắt buộc: Ops (lead), TL (approve gate), BE Dev (execute), FE Dev (deploy FE), QA (smoke), PO (bật config cuối).

MốcActionOwnerVerifyRollback action nếu fail
T-30Pre-flight check: confirm staff chi nhánh đã đóng ca; kiểm tra backup job nightly chạy thành công 24h quaOpsBackup status completed; POS dashboard không có đơn pendingHoãn deploy; reschedule
T-15Snapshot DB ad-hoc (cả wallet + ecommerce DB)OpsSnapshot ID record được; size > 0— (chưa làm gì)
T-10Đóng cờ feature flag globally wallet_km2_config.disabled=true (đảm bảo seed) + announce maintenance window qua Slack #all-staffTLSELECT disabled FROM wallet_km2_config = trueHoãn
T-5TL ký bắt đầu deploy; Ops post message vào Slack #deploy-logTLSlack message timestamp
T+0Step 1 — Apply migrations (đảm bảo thứ tự): wallet DB trước (lot, payment_attempt, deduction, config + seed VND_PROMOTION_2), sau đó ecommerce DB (ALTER prepaid_card, ALTER product, ALTER service, seed payment_method row wallet_promotion_2)BE Dev\dt wallet_km2_* show 4 tables; \d prepaid_card có cột wallet_target + expiry_monthspsql -f wallet/down.sql && psql -f ecommerce/down.sql; restore snapshot T-15 nếu data corrupt
T+5Step 2 — Reload Hasura metadata từ commit ref đã chuẩn bịBE Devhasura metadata apply exit 0; Hasura console /v1/metadata không lỗi; check action deduct_km2_payment + get_customer_km2_lots + refund_km2_wallet đăng ký đúnggit checkout {prev_metadata_sha} && hasura metadata apply
T+8Step 3 — Verify GraphQL schemaBE Devquery { wallet_km2_lot { id } wallet_km2_config { disabled } } trả empty result không lỗi schemaRevert metadata + rerun apply
T+10Step 4 — Deploy wallet-api image new tagBE Dev + Ops/healthz 200; log không có panic; curl -X POST .../actions/dry-run-scheduler trả 200kubectl rollout undo deployment/wallet-api; verify image trở về tag cũ
T+15Step 5 — Deploy ecommerce-api image new tagBE Dev + Ops/healthz 200; smoke test endpoint /api/payment/health trả OKkubectl rollout undo deployment/ecommerce-api
T+20Step 6 — Cron metadata trigger wallet_km2_expire_lots đăng ký với schedule 5 0 * * * (Asia/Ho_Chi_Minh) nhưng KHÔNG enable (one_off run only cho test)BE DevHasura cron list show trigger pausedXoá cron trigger qua Hasura UI
T+25Step 7 — Deploy FE admin (release tag mới có route settings + chip Ví KM 2 + popup customer KM2 + dialog refund KM2)FE DevTrang /s/internal-settings/promotion-wallet load không 500; toggle KM 2 hiện; chưa bật vẫn hiển thị banner readinessRevert FE build trước (CDN purge cache)
T+30Step 8 — Smoke test bắt buộc (QA driven) trên production canary với khách test_km2_user_1:
(a) Admin tạo Gói Test 100k → 1tr KM 2 (expiry_months=1)
(b) Bật allow_promo_wallet_2=true cho 1 dịch vụ test
(c) POS bán 1 đơn qty=1 nhưng KHÔNG thanh toán → expect prepaid_order.status='pending'SELECT COUNT(*) FROM wallet_km2_lot WHERE user_id='test_km2_user_1' = 0 (DEC-027: lot chỉ tạo khi khách trả ≥ 1 đồng); sau đó thanh toán đầy đủ 100k → expect 1 lot active với credited_amount = initial_amount
(d) Verify migration data integrity: không có lot orphan; wallet_km2_config đúng 1 row với disabled=true
QA + BE4 case pass; lot count đúng theo từng sub-step (c)Nếu fail bất kỳ case → escalate TL → rollback toàn bộ E5.1
T+40Step 9 — Enable cron trigger wallet_km2_expire_lots (đổi từ pausedactive)BE Devhasura cron-trigger list show activePause lại
T+42Gate quyết định bật config: TL + PO confirm tất cả smoke test pass + monitoring dashboard không alert criticalTL + POSlack message ký duyệtSkip Step 10, kết thúc maintenance window với config vẫn disabled=true; KM 2 sẽ bật trong release nhỏ kế tiếp
T+45Step 10 — Bật wallet_km2_config.disabled=false qua SCR-01 Settings UI (PO/Admin click toggle "Bật Ví KM 2 cho POS" sau khi readiness 4 bước pass)PO + AdminSELECT disabled FROM wallet_km2_config WHERE branch_id IS NULL = false; POS thấy chip Ví KM 2UPDATE wallet_km2_config SET disabled=true (rollback nhanh nhất)
T+50Step 11 — End maintenance window, post #all-staff rằng KM 2 đã liveOpsSlack message
T+55Theo dõi monitoring dashboard 1 giờ tiếp theo; QA chạy 5 đơn KM 2 thật ở chi nhánh pilotOps + QADashboard 0 critical alert; 5 đơn passTắt config nếu phát hiện bug

E4.2) Decision tree khi gặp lỗi

Migration step nào fail?
├─ T+0 (DB migrate) → restore snapshot T-15 + reschedule
├─ T+5 (Hasura metadata) → git revert + rerun
├─ T+10 / T+15 (BE deploy) → kubectl rollout undo
├─ T+25 (FE deploy) → revert build + CDN purge
├─ T+30 (smoke test) →
│   ├─ Migration data corrupt → restore snapshot
│   ├─ Action không trả đúng → revert BE deploy
│   └─ FE render lỗi → revert FE deploy
├─ T+40 (cron) → pause + investigate, không block deploy
└─ T+45 (bật config) → UPDATE disabled=true ngay; lock incident

E4.3) Tiêu chí "đã deploy thành công" (ký bởi TL + PO)

  • [ ] Tất cả 11 step Status = ✅
  • [ ] wallet_km2_config đúng 1 row global, disabled=false (sau Step 10)
  • [ ] 4 table mới + 5 ALTER + payment_method seed verified
  • [ ] Hasura action deduct_km2_payment + get_customer_km2_lots + refund_km2_wallet reachable
  • [ ] Cron wallet_km2_expire_lots active
  • [ ] Grafana dashboard KM2 Operations xanh (không critical alert)
  • [ ] 5 đơn pilot pass trong 1 giờ đầu
  • [ ] TL + PO ký Slack #deploy-log

Kiểm tra ngay sau deploy

  • [ ] Admin bật KM2 và lưu config thành công.
  • [ ] Permission smoke: user bị revoke không thấy menu/button KM2 và gọi API trực tiếp bị chặn; user được grant thấy đúng sau refresh/relogin.
  • [ ] Sau khi bật: SELECT COUNT(*) FROM wallet_km2_config WHERE branch_id IS NULL AND disabled = false AND deleted_at IS NULL; = 1.
  • [ ] Tạo 1 Gói Ví KM2 test, bán cho customer test, tạo lot thành công.
  • [ ] Staff chỉ xem được KM2 lot của khách được phân quyền; gọi action với khách ngoài phạm vi bị chặn.
  • [ ] Thanh toán đơn DV bằng KM2 với eligible item, wallet amount và lot balance giảm đúng.
  • [ ] Refund đơn DV trả lại lot đúng deduction records.
  • [ ] Tạo Yêu cầu hoàn tiền loại "Hoàn ví KM2", duyệt qua màn hiện có, lot/wallet/refund method cập nhật đúng.
  • [ ] Chạy scheduler dry-run/limited batch, log không có error.
  • [ ] Fund/print/report không mất data và không gộp nhầm KM1/KM2.

Theo dõi Day-1

  • [ ] km2_deduction_error_total = 0 trong 2 giờ đầu.
  • [ ] km2_fifo_duration_seconds p99 < 500ms.
  • [ ] km2_scheduler_error_total = 0 sau lần cron đầu.
  • [ ] km2_expired_balance_total không tăng bất thường trong ngày đầu.
  • [ ] Log không có trùng deduction cho cùng order_id + request id.

E5) Khôi phục (Rollback)

Tình huốngCách rollbackGhi chú
FE lỗi UI nhưng BE/DB ổnRevert FE buildGiữ config KM2 disabled nếu cần
Payment action lỗi trừ tiềnDisable KM2 config + rollback ecommerce-api/wallet-apiĐối chiếu wallet_km2_lot_deduction trước khi sửa data
Scheduler expire saiDisable cron trigger wallet_km2_expire_lotsKhông xóa lot; TL/BE review log và chạy correction script riêng
Migration lỗi trước khi có dataChạy down.sqlChỉ dùng khi chưa có giao dịch KM2 production
Data đã có giao dịch KM2Không drop table trực tiếpDùng feature disable + hotfix/compensation, có TL approve

E6) Ký duyệt

Nhóm ký duyệtNgườiNgàyTrạng thái
Nghiệp vụPOChờ duyệt
Kỹ thuậtTech LeadChờ duyệt
QAQA LeadChờ duyệt
Vận hànhOps/TLChờ duyệt