Commit 37289e81 by tdgiang

Fix Sổ Bán Lẻ sale_status mapping: 1=success, 4=failed (was hardcoded to 4)

Correction to the earlier assumption (based on an ad-hoc test during
brainstorming) that sale_status=4 meant "paid". The actual mapping is
1=giao dịch thành công, 4=giao dịch thất bại - confirmed directly by the
user with real curl examples for both outcomes.

SobanleClient.changeSaleStatus now takes the status as an explicit
parameter instead of hardcoding 4. epayIPN now calls it for BOTH payment
outcomes (previously only on success), sending 1 or 4 to match the real
result - so a failed/refunded transaction's Sổ Bán Lẻ order also gets
marked accordingly instead of being silently left at "chờ xử lý" forever.
Co-Authored-By: 's avatarClaude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017KPzWwuTEeX2vXGXvyGn4q
parent 06f47170
......@@ -242,8 +242,10 @@ exports.epayIPN = function (req, res) {
return res.status(500).json({ code: "99", data: "DB_ERROR" });
}
if (newStatus === "success" && tx.sobanleOrderId) {
SobanleClient.changeSaleStatus(tx.sobanleOrderId, function (sobanleErr) {
if (tx.sobanleOrderId) {
// 1 = giao dịch thành công, 4 = giao dịch thất bại (theo Sổ Bán Lẻ).
var sobanleSaleStatus = newStatus === "success" ? 1 : 4;
SobanleClient.changeSaleStatus(tx.sobanleOrderId, sobanleSaleStatus, function (sobanleErr) {
if (sobanleErr) {
console.error(
"epayIPN: SobanleClient.changeSaleStatus failed for orderId=" + tx.sobanleOrderId + ":",
......
......@@ -131,10 +131,11 @@ function createSale(lines, customerData, callback) {
}
exports.createSale = createSale;
function changeSaleStatus(orderId, callback) {
// saleStatus: 1 = giao dịch thành công, 4 = giao dịch thất bại (theo Sổ Bán Lẻ).
function changeSaleStatus(orderId, saleStatus, callback) {
withAuthRetry(function (token, cb) {
var url = config.sobanle.base_url + "/sales/change-sale-status/" + orderId;
ApiRequest.patchOtherUrlWithHeader(url, { sale_status: 4 }, { Authorization: "Bearer " + token }, cb);
ApiRequest.patchOtherUrlWithHeader(url, { sale_status: saleStatus }, { Authorization: "Bearer " + token }, cb);
}, function (err) {
callback(err || null);
});
......
......@@ -11,8 +11,8 @@ dùng thẳng số tiền admin nhập, hệ thống tự động:
2. Dùng **tổng tiền thật** của đơn hàng vừa tạo (có thể thấp hơn số tiền
nhập tối đa 50.000đ) để tạo giao dịch thanh toán (payment-gate) như luồng
hiện tại.
3. Khi giao dịch thanh toán được xác nhận thành công (IPN từ provider), gọi
thêm API cập nhật trạng thái đơn hàng bên Sổ Bán Lẻ thành "đã thanh toán".
3. Khi có kết quả thanh toán từ provider (IPN), gọi thêm API cập nhật trạng
thái đơn hàng bên Sổ Bán Lẻ tương ứng: thành công hoặc thất bại.
Nguồn: `docs/readme.md` (yêu cầu gốc) + phần brainstorm làm rõ trong hội thoại.
......@@ -50,10 +50,12 @@ Admin nhập tên/SĐT/địa chỉ/số tiền dự kiến (form admin/transact
7. Trả paymentUrl như luồng hiện tại
epayIPN (đã có sẵn, admin.server.controller.js dòng 144-198)
→ khi cập nhật status thành "success" (sau updateOne):
SobanleClient.changeSaleStatus(tx.sobanleOrderId, sale_status: 4)
→ sau updateOne, nếu có sobanleOrderId, luôn gọi cập nhật trạng thái
(cả 2 chiều, không chỉ lúc thành công):
SobanleClient.changeSaleStatus(tx.sobanleOrderId, sale_status: 1 nếu status="success", 4 nếu "failed")
- sale_status: 1 = giao dịch thành công, 4 = giao dịch thất bại (theo Sổ Bán Lẻ)
- lỗi ở bước này: chỉ log, KHÔNG rollback giao dịch thanh toán (tiền
đã về tài khoản) — xem mục Rủi ro/Ngoài phạm vi
đã về tài khoản nếu thành công) — xem mục Rủi ro/Ngoài phạm vi
```
## 3. Thuật toán chọn sản phẩm (`pickProductCombo`)
......@@ -89,7 +91,7 @@ cho từng API. Các response shape dưới đây đã verify thật (không ph
| Đăng nhập lấy token | POST | `/login` | `{ login, password }` | `access_token` (top-level, không nằm trong `data`), `expires_in` (giây, TTL cache token — không cần decode JWT) |
| Lấy danh sách sản phẩm | GET | `/products?page=1&is_active=true&per_page=1000` | header `Authorization: Bearer <token>` | `data`: mảng sản phẩm thẳng (không lồng thêm cấp); field dùng: `id`, `price` (giá CHƯA thuế — verify: `price` = `original_price` cộng biên lợi nhuận, tách biệt thuế), `qty` (tồn kho — KHÔNG được chọn số lượng vượt quá field này), `is_active`, `tax: { id, name, rate }` |
| Tạo đơn hàng | POST | `/sales` | `{ warehouse_id, biller_id, account_id, currency_id, exchange_rate: "1", reference_no: null, is_internal_api: true, sale_status: 2, list_product: [{product_id, qty}], customer_data {...}, payment_receiver, payment_note, sale_note, staff_note }` | `data.id` (orderId), `data.grand_total` (orderTotal thật) |
| Cập nhật trạng thái đơn hàng | PATCH | `/sales/change-sale-status/{id}` | `{ sale_status: 4 }` (4 = đã thanh toán/hoàn tất) | `message` — gọi sau khi giao dịch thanh toán bên payment-gate xác nhận thành công |
| Cập nhật trạng thái đơn hàng | PATCH | `/sales/change-sale-status/{id}` | `{ sale_status: 1 }` (thành công) hoặc `{ sale_status: 4 }` (thất bại) | `message` — gọi sau khi epay IPN xác nhận kết quả thanh toán (cả 2 chiều thành công/thất bại) |
Ghi chú bảo mật: các ví dụ curl trong `docs/readme.md` chứa token/cookie
sống thật — không copy trực tiếp vào code hay commit. Client mới phải tự
......@@ -124,8 +126,8 @@ login lấy token qua API, không dùng token/cookie đã bị lộ trong tài l
- **Sửa** `app/controllers/admin.server.controller.js`:
- `createTransaction` — chèn bước gọi `SobanleClient` trước khi
`AdminTransaction.create`, đổi `amount` thành `orderTotal`.
- `epayIPN` — sau khi `updateOne` set `status: "success"` thành công, gọi
`SobanleClient.changeSaleStatus`.
- `epayIPN` — sau khi `updateOne` xác định kết quả (`success`/`failed`), gọi
`SobanleClient.changeSaleStatus` với `sale_status` tương ứng (1/4).
## 6. Xử lý lỗi
......@@ -135,7 +137,7 @@ login lấy token qua API, không dùng token/cookie đã bị lộ trong tài l
| API lấy sản phẩm lỗi | Dừng, trả lỗi cho admin |
| Không tìm được tổ hợp sản phẩm khớp sai số | Dừng, trả lỗi cho admin (`NO_PRODUCT_COMBO_MATCH`) |
| API tạo đơn hàng lỗi | Dừng, trả lỗi cho admin |
| API cập nhật trạng thái đơn hàng lỗi (sau khi đã thanh toán thành công) | Chỉ log lỗi, không rollback giao dịch — tiền đã về tài khoản |
| API cập nhật trạng thái đơn hàng lỗi (sau khi có kết quả IPN, dù thành công hay thất bại) | Chỉ log lỗi, không rollback giao dịch — nếu thành công, tiền đã về tài khoản |
## 7. Rủi ro / Ngoài phạm vi (chưa xử lý ở bước này)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment