PRD: Điểm thưởng khi đặt hàng
| Module | Bán hàng | PRD ID | PRD-PNT-001 |
| Trạng thái | Sẵn sàng dev | FEAT | PNT |
| Epic | — | Plane | BANA-1559 |
| Ngày | 2026-03-23 | Phiên bản | v1.0 |
| Gói | @nx/sale | URD | PNT |
| Surface | Backend | ||
| Phụ trách | Phát Nguyễn | ||
TL;DR
Đơn có gắn khách hàng thanh toán đủ → khách tự động tích điểm thưởng tính từ tổng đơn theo tỷ lệ quy đổi của merchant. Mỗi đơn chỉ cộng điểm đúng một lần, ghi vào sổ chỉ-đọc và cập nhật số dư ngay - thu ngân không cần thao tác thêm.
1. Context & Problem
Đơn hàng đã có thể gắn với khách hàng (URD-ORD-014) và đạt trạng thái cuối COMPLETED khi thanh toán đủ (URD-ORD-011), nhưng không có gì được tích lũy trên mối quan hệ đó - không có cách nào để tưởng thưởng việc mua lại. Những merchant muốn một cơ chế loyalty đơn giản kiểu "chi tiêu X, nhận một điểm" buộc phải theo dõi ngoài hệ thống hoặc bỏ qua, điều này không khả thi với các merchant bán lẻ và F&B quy mô HKD/SME mà BANA hướng tới.
Tính năng này lấp khoảng trống đó: đơn có gắn khách hàng và thanh toán đủ tự động cộng điểm qua luồng thanh toán thành công - một bản ghi sổ bất biến, idempotent theo từng đơn, kèm số dư cập nhật đồng thời.
2. Goals & Non-Goals
Goals
- Cộng điểm cho khách hàng khi đơn được thanh toán đủ và có khách hàng gắn kèm, móc vào cả luồng thanh toán thành công của đơn hàng lẫn của từng check.
- Tính điểm từ tổng đơn theo tỷ lệ quy đổi của từng merchant, làm tròn xuống thành điểm nguyên.
- Đảm bảo việc cộng điểm idempotent cho mỗi đơn - phát lại cùng một đơn đã hoàn tất không cộng thêm gì.
- Theo dõi một số dư điểm theo từng khách hàng từng merchant, được tăng cùng với bản ghi sổ trong một lần ghi nguyên tử duy nhất.
- Ghi nhận mỗi lần cộng điểm thành một bản ghi sổ điểm (earn) chỉ-đọc mang theo đơn, số điểm và tỷ lệ quy đổi.
Non-Goals
- Tiêu / dùng điểm - chỉ có loại bản ghi earn trong đợt này.
- Hoàn lại / đảo điểm khi refund / trả hàng (luồng refund bản thân nó là Non-Goal của module).
- Hạng, hết hạn, hệ số nhân, khuyến mãi, hay điểm thưởng theo chiến dịch.
- Một UI hướng khách hàng cho số dư hoặc sổ điểm.
3. Success Metrics
| Metric | Mục tiêu / tín hiệu |
|---|---|
| Độ phủ | Đơn có gắn khách hàng và thanh toán đủ tạo ra một lần cộng điểm (khi đã cấu hình tỷ lệ quy đổi) |
| Idempotency | Không có lần cộng trùng - tối đa một bản ghi sổ cho mỗi đơn |
| Tính toàn vẹn số dư | Số dư khách hàng luôn bằng tổng các bản ghi sổ earn của khách đó |
| Mức áp dụng | Số merchant cấu hình tỷ lệ quy đổi và tích lũy điểm |
4. Personas & Use Cases
| Persona | Mục tiêu trong tính năng này |
|---|---|
| Khách hàng | Tự động tích điểm trên mỗi lần mua hoàn tất |
| Thu ngân | Thu tiền như bình thường; điểm tự cộng mà không cần thao tác thêm |
| Chủ merchant | Vận hành một chương trình loyalty kiểu tích điểm đơn giản, phạm vi theo từng merchant |
Kịch bản chính: thu ngân gắn khách hàng → thu thanh toán đủ (đơn hoặc check cuối) → hệ thống tính điểm (làm tròn xuống), ghi một bản ghi earn và cộng số dư - idempotent, kể cả khi sự kiện bị phát lại.
5. User Stories
- Là một khách hàng, tôi muốn tích điểm khi đơn của tôi được thanh toán đủ, để các lần mua lại tạo nên số dư mà sau này tôi có thể được tưởng thưởng.
- Là một thu ngân, tôi muốn điểm tự động cộng khi thanh toán, để tôi không phải nhớ một bước loyalty thủ công.
- Là một chủ merchant, tôi muốn đặt một tỷ lệ quy đổi cho từng merchant, để tôi kiểm soát mức chi tiêu đổi được một điểm.
- Là một chủ merchant, tôi muốn một đơn nhất định chỉ cộng điểm một lần, để một sự kiện thanh toán bị phát lại không thể thổi phồng số dư.
- Là một chủ merchant, tôi muốn một sổ chỉ-đọc các lần cộng điểm, để mọi điểm trong số dư khách hàng đều truy ngược được về một đơn.
6. Functional Requirements
| # | Yêu cầu | URD ref |
|---|---|---|
| FR-1 | Cộng điểm cho khách hàng khi đơn trở thành thanh toán đủ và có khách hàng gắn kèm, kích hoạt từ cả luồng thanh toán thành công của đơn hàng lẫn của từng check | URD-PNT-001 |
| FR-2 | Tính điểm bằng tổng đơn chia cho tỷ lệ quy đổi, làm tròn xuống thành điểm nguyên; bỏ qua khi kết quả ≤ 0 | URD-PNT-001 |
| FR-3 | Đọc thiết lập tỷ lệ quy đổi điểm của từng merchant; bỏ qua khi không có thiết lập hoặc tỷ lệ không dương; mặc định 1000 | URD-PNT-001 |
| FR-4 | Việc cộng điểm idempotent cho mỗi đơn - dừng sớm nếu đơn đã có lần cộng điểm | URD-PNT-002 |
| FR-5 | Ghi nhận lần cộng điểm thành một bản ghi sổ điểm (earn) mang theo đơn, số điểm và tỷ lệ quy đổi | URD-PNT-001 |
| FR-6 | Tăng số dư điểm của khách hàng và ghi bản ghi sổ cùng nhau trong một lần ghi nguyên tử duy nhất; rollback khi lỗi | URD-PNT-003 |
| FR-7 | Phơi bày sổ ở chế độ chỉ-đọc (liệt kê, lấy theo id, lấy một, đếm), giới hạn trong merchant của chính người dùng; lần cộng điểm không bao giờ được ghi bởi client | URD-PNT-001..003 |
Toàn văn yêu cầu và tiêu chí chấp nhận nằm trong Orders URD. PRD này tham chiếu chúng thay vì lặp lại.
7. Non-Functional Requirements
| Lĩnh vực | Yêu cầu |
|---|---|
| Toàn vẹn dữ liệu | Bản ghi sổ và lần tăng số dư được ghi cùng nhau trong một lần ghi nguyên tử - không có thay đổi số dư mà thiếu bản ghi sổ tương ứng, và ngược lại |
| Idempotency | Tối đa một lần cộng cho mỗi đơn bất kể sự kiện thanh toán thành công kích hoạt bao nhiêu lần |
| Tính bất biến | Sổ chỉ được ghi qua luồng thanh toán thành công và là chỉ-đọc đối với client; các bản ghi không bị client chỉnh sửa |
| Tenancy & authz | Mọi thao tác giới hạn trong merchant của chính người dùng; đọc sổ được gác bởi permission đọc sổ điểm |
| Độ chính xác | Tổng đơn đọc với độ chính xác 4 chữ số thập phân; điểm là số nguyên |
| i18n | Nhãn/trạng thái hướng người dùng song ngữ (tiếng Anh & tiếng Việt) |
8. UX & Flows
Luồng cộng điểm không có UI riêng - nó chạy ngầm theo sự kiện thanh toán thành công. Sổ chỉ-đọc và số dư theo từng khách hàng được các màn hình Orders/Khách hàng ở front end dùng đến.
9. Data & Domain
| Entity | Vai trò |
|---|---|
| Bản ghi sổ điểm | Bản ghi sổ bất biến (loại earn) - đơn, số điểm, tỷ lệ quy đổi, scope theo khách hàng & merchant |
| Số dư điểm khách hàng | Số dư đang chạy theo từng khách hàng từng merchant, được tăng một thao tác duy nhất với mỗi lần cộng điểm |
| Thiết lập tỷ lệ quy đổi điểm | Thiết lập tỷ lệ quy đổi của từng merchant; mặc định 1000 |
| Order | Nguồn của tổng đơn và liên kết khách hàng dẫn dắt việc cộng điểm |
Chỉ ở mức khái niệm - toàn bộ schema và bất biến nằm trong sale domain model.
10. Dependencies & Assumptions
Phụ thuộc vào
- Vòng đời Sale Order (URD-ORD-011) - lần cộng điểm kích hoạt tại bước chuyển sang thanh toán đủ.
- Liên kết khách hàng (URD-ORD-014) - chỉ đơn có gắn khách hàng mới tích điểm.
- Tách hoá đơn (check splitting) (URD-CHK) - thanh toán thành công của sale-check là luồng kích hoạt thứ hai.
- Thiết lập tỷ lệ quy đổi điểm - cung cấp tỷ lệ quy đổi của từng merchant.
Giả định
- Đơn mang một tổng đã chốt tại thời điểm thanh toán thành công.
- Một merchant muốn tích điểm sẽ cấu hình tỷ lệ quy đổi; nếu thiếu, lần cộng điểm bị bỏ qua (mặc định 1000 chỉ áp dụng khi thiết lập tồn tại nhưng không có giá trị).
11. Risks & Open Questions
| Rủi ro / câu hỏi | Giảm thiểu / trạng thái |
|---|---|
| Cộng trùng do sự kiện thanh toán bị phát lại | Dừng sớm idempotency theo khóa đơn hàng |
| Sổ và số dư có thể lệch khi lỗi cục bộ | Cả hai được ghi cùng nhau trong một lần ghi nguyên tử; rollback khi lỗi |
| Refund / trả hàng sau khi đã cộng điểm | Ngoài phạm vi - chưa có đảo điểm; refund là Non-Goal của module |
| Chưa có luồng redemption | Chấp nhận trong đợt này; chỉ có earn, redemption là đợt tương lai |
| Merchant chưa cấu hình tỷ lệ thì không tích được điểm | Theo thiết kế - bỏ qua khi thiếu config hoặc tỷ lệ không dương |
12. Release Plan & Launch Criteria
| Khía cạnh | Kế hoạch |
|---|---|
| Phase | P2 - xem URD feature catalog |
| Rollout | Mọi merchant; không feature flag (vô hiệu cho đến khi merchant đặt tỷ lệ quy đổi) |
| Migration | Bản ghi sổ điểm mới và số dư điểm khách hàng |
| Tiêu chí ra mắt | Thanh toán đủ trên đơn có gắn khách hàng cộng đúng số điểm đã làm tròn; phát lại không cộng thêm; số dư bằng tổng sổ; đọc scope theo merchant và chỉ-đọc |
| Giám sát | Lượng cộng điểm theo từng merchant, tỷ lệ bỏ qua do idempotency, kiểm tra nhất quán số-dư-vs-sổ |
13. FAQ
Điểm được cộng chính xác khi nào? Khi một đơn có gắn khách hàng trở thành thanh toán đủ - qua luồng thanh toán thành công của đơn hàng hoặc của từng check.
Một đơn tích được bao nhiêu điểm? Tổng đơn chia cho tỷ lệ quy đổi, làm tròn xuống thành điểm nguyên, trong đó tỷ lệ quy đổi là thiết lập của merchant (mặc định 1000). Phần dư dưới một điểm bị bỏ.
Nếu merchant chưa cấu hình tỷ lệ thì sao? Không cộng điểm - lần cộng bị bỏ qua khi thiếu config hoặc tỷ lệ không dương.
Sự kiện thanh toán phát lại có cộng điểm gấp đôi không? Không - việc cộng điểm idempotent cho mỗi đơn; lần thử thứ hai cho cùng một đơn bị dừng sớm.
Khách hàng có thể tiêu hoặc đổi điểm không? Không trong đợt này - chỉ có loại bản ghi earn. Redemption, hạng và hết hạn là việc tương lai.
Client có thể ghi vào sổ không? Không - sổ là chỉ-đọc; lần cộng điểm chỉ được ghi bởi luồng thanh toán thành công.
References
- URD: Đơn hàng - Loyalty Points (vùng PNT)
- Liên quan: Sale Order · Check Splitting
- Module: Đơn hàng - URD
- Developer: @nx/sale · domain model