Chuyển tới nội dung chính

Hướng Dẫn Đồng bộ Order xuống Central Order View (COV)

Phiên bản: v1.5
Ngày phát hành: 11/03/2026

Changelog (v1.5):

  • Bổ sung order.seller_merchant_id (optional) để hỗ trợ filter/reporting theo seller trong mô hình marketplace/multi-seller.
  • Cập nhật secure_hash cho event order.created: append động seller_merchant_id theo thứ tự alphabet khi có giá trị.
  • Bổ sung Product Type BILLING_PAYMENT - Dịch vụ thanh toán hóa đơn.
  • Bổ sung Product Type PROMOTION_PURCHASE - Dịch vụ mua voucher/khuyến mãi.

Changelog (v1.4):

  • Bổ sung order.items[].category_codeorder.items[].category_name (optional) - mã và tên danh mục/ngành hàng, đồng bộ với Payment/init-payment.
  • Bổ sung order.items[].sub_items[].category_codeorder.items[].sub_items[].category_name (optional) - tương tự cho sub item.
  • Bổ sung order.category_codeorder.category_name (optional) cho case 0 SKU hoặc khi cần phân loại ở cấp đơn hàng.
  • Deprecated: order.items[].item_categoryorder.items[].sub_items[].item_category — ưu tiên dùng category_code + category_name. Field cũ vẫn được hỗ trợ để tương thích ngược.

Changelog (v1.3):

  • Bổ sung order.promotion_infos[].voucher_idorder.promotion_infos[].campaign_id (optional) - ID voucher và campaign trong hệ thống PnL, hỗ trợ tracking và audit trail.
  • Bổ sung order.promotion_infos[].campaign_name (optional) - Tên campaign/chiến dịch khuyến mãi, phục vụ hiển thị và báo cáo.

Changelog (v1.2): Ngày phát hành: 29/11/2025

  • Bổ sung order.business_unit_idorder.branch_id (optional) - các giá trị được thống nhất và khai báo để định danh ở hệ thống kế toán chung. Bắt buộc với PnL onboard SAP/BI/..., optional với các PnL khác
  • Thêm branch_idbusiness_unit_id vào secure_hash cho event order.created (append động vào cuối chuỗi nếu có giá trị) - tương tự như phía Payment.
  • Bổ sung order.image_url (optional) - ảnh minh họa cho order, dùng cho Order Card trên SuperApp.
  • Bổ sung order.attributes (optional) - thuộc tính mô tả order để hiển thị trên UI (pickup_location, dropoff_location, estimated_duration, delivery_date...). Đặc biệt hữu ích cho case 0 SKU khi không có items. Phân biệt với order.metadata (thông tin nghiệp vụ/quản lý).
  • Bổ sung Product Type AFTER_SALES (Hậu mãi Vinfast) - dịch vụ hậu mãi, bảo hành, sửa chữa xe VinFast.
  • Bổ sung order.transactions[].note (optional) - ghi chú/ngữ cảnh cho từng giao dịch (VD: mục đích thanh toán, lý do hủy, v.v.) để hỗ trợ audit trail và hiển thị trên UI.
  • Đổi tên Product Type từ VINFAST_PURCHASE thành CAR_PURCHASE - để generic hơn, không chỉ giới hạn cho VinFast.

Changelog (v1.1): Ngày phát hành: 25/11/2025

  • Bổ sung order.order_name, bắt buộc order.order_type, order.deeplink, cập nhật mô tả created_by.
  • order.items chuyển optional (support 0 SKU) + hướng dẫn hiển thị Order Card, thêm ảnh minh họa.
  • Mở rộng schema order.fees (metadata, description note) và order.promotion_infos (list).
  • Làm rõ format metadata, attributes, extra_data theo từng order_type.
  • Thêm order.pnl_order_status_label để PnL truyền tên trạng thái gốc phục vụ hiển thị trên SuperApp (nếu cần).
  • Cập nhật Order/Payment lifecycle (mermaid) để hỗ trợ reopen/partial adjust + note scenario GSM.
  • Thêm Retry Mechanism (section 8.6) và giải thích chiến lược retry với event_timestamp gốc/mới.
  • Định nghĩa domain values cho order.transactions[].status và các lưu ý khác về các field.

1. Tổng Quan

Central Order View (COV) là hệ thống tập trung thu thập, chuẩn hóa và đồng bộ trạng thái đơn hàng + thanh toán từ tất cả các PnL của Vingroup.

Kiến trúc tổng thể:

┌─────────────────────────────────────────────────────────────────────────┐
│ PnL SYSTEMS │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ VinFast │ │ VinHomes │ │ VinPearl │ │ GSM │ │ │ │
│ │ Order │ │ Order │ │ Order │ │ Order │ │ ... │ │
│ │ Backend │ │ Backend │ │ Backend │ │ Backend │ │ │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘. │
└───────┼─────────────┼─────────────┼─────────────┼─────────────┼────────┘
│ │ │ │ │
└─────────────┴─────────────┴─────────────┴─────────────┘

Webhooks (3 events)
order.created | order.updated | order.cancelled


┌───────────────────────────────────────────────────────┐
│ MULESOFT ESB │
│ (Routing, Authentication, Logging) │
└───────────────────────┬───────────────────────────────┘


┌───────────────────────────────────────────────────────┐
│ CENTRAL ORDER VIEW (COV) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Webhook │ │ Event │ │ Storage │ │
│ │ Ingestion │→ │ Processing │→ │ (Database) │ │
│ └─────────────┘ └──────────────┘ └──────────────┘ │
└───────────────────────┬───────────────────────────────┘

┌───────────┴───────────┐
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ V-PORTAL │ │ SUPER APP │
│ (Internal Portal) │ │ (Customer App) │
│ │ │ │
│ • Order tracking │ │ • My Orders │
│ • Dashboard │ │ • Order status │
│ • Analytics │ │ • Order history │
│ • Reports │ │ • Notifications │
└─────────────────────┘ └─────────────────────┘

PnL là Source of Truth → COV chỉ lưu snapshot theo sự kiện và hiển thị thông tin lên VPortal + SuperApp.

Bắt buộc đẩy 3 webhook sau:

EventKhi nào gửi
order.createdĐơn hàng mới được tạo thành công
order.updatedCó bất kỳ thay đổi nào (status, payment, items, fees, metadata…)
order.cancelledĐơn hàng bị hủy (bởi khách hoặc hệ thống)

2. Endpoint (qua MuleSoft)

Quan trọng: Tất cả requests phải gọi qua MuleSoft, không được gọi trực tiếp tới COV.

2.1 Internal (PnL)

Kết nối Internal dành cho các PnL trong Vingroup.

Base URL theo môi trường:

Môi trườngBase URL
DEV{MULE_INTERNAL_BASE_URL}/order/cov/
SIT{MULE_INTERNAL_BASE_URL}/sit/order/cov/
UAT{MULE_INTERNAL_BASE_URL}/uat/order/cov/
Production{MULE_INTERNAL_PROD_URL}/order/cov/

Webhook Endpoints:

Thay {BASE_URL} bằng Base URL tương ứng môi trường trong bảng trên.

POST {BASE_URL}/webhook/v1/orders/created
POST {BASE_URL}/webhook/v1/orders/updated
POST {BASE_URL}/webhook/v1/orders/cancelled

Lưu ý: Với môi trường Production, các bên kết nối sẽ được team Mule cấp key riêng (client_idclient_secret).


2.2 External (Đối tác)

Kết nối External dành cho các đối tác bên ngoài Vingroup. Chỉ hỗ trợ 2 môi trường: ProductionUAT.

Base URL theo môi trường:

Môi trườngBase URL
UAThttps://test-api.vingroup.net:7445/order/cov/
Productionhttps://api-cloud.vingroup.net/order/cov/

Webhook Endpoints:

UAT Environment:

POST https://test-api.vingroup.net:7445/order/cov/webhook/v1/orders/created
POST https://test-api.vingroup.net:7445/order/cov/webhook/v1/orders/updated
POST https://test-api.vingroup.net:7445/order/cov/webhook/v1/orders/cancelled

Production Environment:

POST https://api-cloud.vingroup.net/order/cov/webhook/v1/orders/created
POST https://api-cloud.vingroup.net/order/cov/webhook/v1/orders/updated
POST https://api-cloud.vingroup.net/order/cov/webhook/v1/orders/cancelled

Yêu cầu kết nối External:

  • Tất cả các request từ đối tác external cần cung cấp danh sách IPs để whitelist
  • Phía Mule sẽ cấp client_idclient_secret riêng cho từng đối tác
  • Có whitelist và limit access theo endpoint

3. Common Headers (bắt buộc)

3.1 Headers cho MuleSoft + COV

HeaderRequiredMô tảVí dụ
Content-TypeYesLuôn là JSONapplication/json
client_idYesMuleSoft client IDYOUR_CLIENT_ID
client_secretYesMuleSoft client secretYOUR_CLIENT_SECRET
X-API-KeyYesAPI Key do COV cấp riêng cho từng Business Unitak_live_a8b7c6d5e4f3g2h1i0j9k8l7m6n5o4p3
X-Request-IdYesUUID v4 duy nhất cho mỗi request (idempotency)550e8400-e29b-41d4-a716-446655440000

Ví dụ gọi webhook order.created qua MuleSoft:

curl --location '{MULE_INTERNAL_BASE_URL}/sit/order/cov/webhook/v1/orders/created' \
--header 'Content-Type: application/json' \
--header 'client_id: YOUR_CLIENT_ID' \
--header 'client_secret: YOUR_CLIENT_SECRET' \
--header 'X-API-Key: ak_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
--header 'X-Request-Id: 550e8400-e29b-41d4-a716-446655440000' \
--data '{
"secure_hash": "…",
"event_timestamp": 1731578400000,
"platform_code": "v-app",
"miniapp_id": "vinfast_sales",
"order": {
"order_id": "VF-ORDER-001",
"order_type": "EV_AND_CHARGING",
"status": "IN_PROGRESS"
}
}'

Lưu ý:

  • client_id, client_secret - dùng để authenticate với MuleSoft
  • X-API-Key - dùng để authenticate với COV (sau khi qua MuleSoft)
  • Mỗi PnL sẽ được cấp cả 2 bộ credentials: MuleSoft credentials (chung) + COV API Keys (theo VSF Merchants)

3.2 API Key & Secret Key

Quan trọng: COV sẽ cấp 1 cặp apiKey/secretKey cho mỗi VSF Merchant (mỗi Merchant map 1:1 với một mã SAP Partner Code thuộc Organization). Một Organization có thể có N cặp key nếu có N Merchant/SAP Partner Code. Để xác định được đơn hàng thuộc cơ sở/chi nhánh kinh doanh nào và sync đúng xuống SAP/các hệ thống phía sau, các PnL cần truyền business_unit_idbranch_id trong event order.created.

Lưu ý: Thông tin này chỉ cần truyền trong event order.created và không chỉnh sửa được sau khi đơn hàng đã được tạo.

Cấu trúc:

OrderBackend → 1 cặp (apiKey + secretKey)

Lưu ý:

  • Mỗi hệ thống OrderBackend của PnL sẽ được cấp 1 cặp apiKey/secretKey duy nhất
  • apiKey được dùng trong header X-API-Key để authenticate với COV
  • secretKey được dùng để tính secureHash cho tất cả các webhook events
  • business_unit_idbranch_id được truyền trong event order.created để COV và các hệ thống phía sau xác định được đơn hàng thuộc cơ sở/chi nhánh kinh doanh nào
  • API Key format: ak_{env}_{random_32_chars}
    • env: live (production) hoặc test (staging/UAT)
    • random_32_chars: chuỗi random 32 ký tự
    • Ví dụ: ak_live_a8b7c6d5e4f3g2h1i0j9k8l7m6n5o4p3

Quy trình cung cấp Key:

  1. PnL cung cấp thông tin:

    • Thông tin hệ thống OrderBackend cần tích hợp
    • Môi trường: Production hoặc Staging/UAT
  2. COV team cấp credentials:

    • MuleSoft credentials:
      • Với môi trường DEV/SIT/UAT: shared cho toàn bộ Organization
      • Với môi trường Production: team Mule sẽ cấp key riêng (client_idclient_secret) cho từng bên kết nối
      • client_id
      • client_secret
  • COV credentials:
    • 1 cặp apiKey/secretKey cho mỗi VSF Merchant (mỗi Merchant tương ứng 1 mã SAP Partner Code thuộc Organization). Một Organization có thể nhận N cặp key nếu có N Merchant/SAP Partner Code.
  1. PnL implementation (mô hình cấp key):
    • Onboard theo Organization. Một Organization có thể có nhiều mã SAP Partner Code (companyCode).
    • Mỗi SAP Partner Code map 1:1 với VSF Merchant.
    • Mỗi VSF Merchant được cấp 1 cặp apiKey/secretKey trên COV (tương ứng 1 mã SAP Partner Code).
    • Order Backend cần lưu:
      • 1 bộ MuleSoft credentials (shared theo môi trường)
      • N cặp apiKey/secretKey COV (mỗi cặp cho 1 VSF Merchant/SAP Partner Code)
    • Khi gửi webhook:
      • Dùng MuleSoft credentials để authenticate với MuleSoft
      • Chọn đúng apiKey của Merchant tương ứng để set header X-API-Key
      • Dùng secretKey của Merchant đó để tính secureHash
      • Optional: Truyền business_unit_idbranch_id trong event order.created để COV và các hệ thống phía sau xác định được đơn hàng thuộc cơ sở/chi nhánh kinh doanh nào. Lưu ý: Thông tin này chỉ cần truyền trong event order.created và không chỉnh sửa được sau khi đơn hàng đã được tạo.

4. Bảo mật – secureHash (HMAC-SHA256)

Mọi request phải có field secureHash (64 ký tự hex lowercase).

Công thức:

secure_hash = HMAC_SHA256(secret_key, string_to_sign).toHexLowerCase()
  • secret_key: do COV cấp cùng X-API-Key
  • string_to_sign: lấy giá trị của các field theo thứ tự alphabet của tên field, nối bằng | (pipe)

Lưu ý về event_timestamp:

  • Format: Unix timestamp (milliseconds) - long value
  • Ví dụ: 1731578400000 (tương đương 2025-11-14T10:00:00Z)

Các field tham gia string_to_sign:

order.created

Công thức: Các field được sắp xếp theo thứ tự alphabet của tên field:

Thứ tựField NameLấy giá trị từVí dụGhi chú
1branch_idorder.branch_idBR_HN_001(Optional) Chỉ append nếu có giá trị
2business_unit_idorder.business_unit_idBU_VINFAST_001(Optional) Chỉ append nếu có giá trị
3event_timestampevent_timestamp1731578400000Unix timestamp (milliseconds)
4grand_totalorder.amounts.grand_total36200000Số tiền user cần thanh toán
5miniapp_idminiapp_idvinfast_salesMiniApp ID trong SuperApp
6order_idorder.order_idVF-ORDER-001
7platform_codeplatform_codev-app
8seller_merchant_idorder.seller_merchant_idSELLER_MERCHANT_001(Optional) Chỉ append nếu có giá trị
9total_amountorder.amounts.total_amount36200000Tổng giá trị đơn hàng (trước KM)

Ví dụ string_to_sign:

Không có branch_id và business_unit_id:

1731578400000|36200000|vinfast_sales|VF-ORDER-001|v-app|36200000

Có branch_id, không có business_unit_id:

BR_HN_001|1731578400000|36200000|vinfast_sales|VF-ORDER-001|v-app|36200000

Có cả branch_id và business_unit_id:

BR_HN_001|BU_VINFAST_001|1731578400000|36200000|vinfast_sales|VF-ORDER-001|v-app|36200000

Có branch_id, business_unit_id và seller_merchant_id:

BR_HN_001|BU_VINFAST_001|1731578400000|36200000|vinfast_sales|VF-ORDER-001|v-app|SELLER_MERCHANT_001|36200000

Lưu ý:

  • Nếu có khuyến mãi thì grand_total < total_amount, nếu không có KM thì bằng nhau.
  • branch_id, business_unit_idseller_merchant_id chỉ cần truyền trong event order.created và chỉ append vào secure_hash khi có giá trị.

order.updated

Trường hợp 1: Có grand_total và total_amount

Thứ tựField NameLấy giá trị từVí dụGhi chú
1event_timestampevent_timestamp1731582000000Unix timestamp (milliseconds)
2grand_totalorder.amounts.grand_total80200000Số tiền user cần thanh toán
3order_idorder.order_idVF-ORDER-001
4total_amountorder.amounts.total_amount80200000Tổng giá trị đơn hàng (trước KM)

string_to_sign:

1731582000000|80200000|VF-ORDER-001|80200000

Trường hợp 2: Không có amounts (không update giá)

Thứ tựField NameLấy giá trị từVí dụ
1event_timestampevent_timestamp1731582000000
2order_idorder.order_idVF-ORDER-001

string_to_sign:

1731582000000|VF-ORDER-001

order.cancelled

Thứ tựField NameLấy giá trị từVí dụ
1event_timestampevent_timestamp1731598200000
2order_idorder.order_idVF-ORDER-001

string_to_sign:

1731598200000|VF-ORDER-001

Ví dụ code tính secureHash (Go):

import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
"time"
)

func calculateSecureHash(secretKey string, fields ...string) string {
stringToSign := strings.Join(fields, "|")
h := hmac.New(sha256.New, []byte(secretKey))
h.Write([]byte(stringToSign))
return hex.EncodeToString(h.Sum(nil))
}

// Sử dụng cho order.created
secretKey := "your_secret_key"
eventTimestamp := time.Now().UnixMilli() // 1731578400000

// Sắp xếp fields theo thứ tự alphabet
fields := []string{}

// 1. branch_id (nếu có)
if branchId := order.BranchId; branchId != "" {
fields = append(fields, branchId)
}

// 2. business_unit_id (nếu có)
if businessUnitId := order.BusinessUnitId; businessUnitId != "" {
fields = append(fields, businessUnitId)
}

// 3. event_timestamp
fields = append(fields, fmt.Sprintf("%d", eventTimestamp))

// 4. grand_total
fields = append(fields, "36200000")

// 5. miniapp_id
fields = append(fields, "vinfast_sales")

// 6. order_id
fields = append(fields, "VF-ORDER-001")

// 7. platform_code
fields = append(fields, "v-app")

// 8. seller_merchant_id (nếu có)
if sellerMerchantId := order.SellerMerchantId; sellerMerchantId != "" {
fields = append(fields, sellerMerchantId)
}

// 9. total_amount
fields = append(fields, "36200000")

secureHash := calculateSecureHash(secretKey, fields...)

5. Chi Tiết 3 Webhook

LƯU Ý QUAN TRỌNG:

  1. Schema linh hoạt: Không phải tất cả PnL đều có đầy đủ thông tin như example. Mỗi PnL chỉ gửi những field có giá trị thực tế.

  2. Extensibility:

    • order.metadata - Lưu thông tin bổ sung của PnL (dạng key-value)
    • items[].attributes - Thuộc tính sản phẩm tùy chỉnh (dạng key-value)
    • promotion_infos[].extra_data - Thông tin bổ sung về promotion/voucher (dạng key-value)
    • Hệ thống COV sẽ lưu trữ và trả về nguyên trạng các metadata/attributes/extra_data này

Naming convention:

  • metadata: dùng cho thông tin mở rộng ở cấp order hoặc fee, thể hiện các thuộc tính nghiệp vụ/quản lý (dealer_code, sales_agent, vat_rate, contract_type...). Dùng cho mục đích quản lý và audit trail.
  • attributes: dùng cho thông tin mô tả chi tiết để hiển thị trên UI:
    • Ở cấp order: thuộc tính mô tả order (pickup_location, dropoff_location, estimated_duration, delivery_date...). Đặc biệt hữu ích cho case 0 SKU.
    • Ở cấp item/sub_item: thuộc tính sản phẩm (color, model, SKU, VIN, vehicle_type...).
  • extra_data: dùng cho các object đặc thù như promotion_infos[] hoặc các cấu trúc domain-specific khác cần bổ sung thêm key-value.
    Việc phân tách này giúp client hiểu được scope của từng dữ liệu và render đúng trên UI.
  1. miniapp_id: Bắt buộc gửi miniapp_id để xác định order được tạo từ MiniApp nào trong SuperApp ecosystem

  2. Chỉ gửi thông tin quan trọng: Tránh gửi dữ liệu dư thừa, chỉ gửi những field cần thiết cho nghiệp vụ

Ví dụ về tính linh hoạt:

// VinFast có thể gửi
{
"order": {
"items": [{
"attributes": {
"color": "red",
"variant": "Premium",
"vin_number": "VF8XXXXX"
}
}],
"metadata": {
"dealer_code": "VF_HN_001",
"sales_agent": "VN001"
}
},
"miniapp_id": "vinfast_sales"
}

// VinHomes có thể gửi
{
"order": {
"items": [{
"attributes": {
"project_name": "Vinhomes Ocean Park",
"apartment_code": "OP-A1-1234",
"floor": "15"
}
}],
"metadata": {
"broker_id": "BRK_001",
"viewing_date": "2025-11-20"
}
},
"miniapp_id": "vinhomes_booking"
}

5.1 order.created

POST /webhook/v1/orders/created

Required/Optional Fields:

Field PathTypeRequiredConstraintsIndexMô tả
secure_hashstring✅ Yes64 ký tự (hex)NoHMAC-SHA256 hash để verify
event_timestamplong✅ Yestimestamp (ms)YesThời điểm event
created_bystring✅ YesMax 128 ký tựNoID người tạo order. Yêu cầu: truyền đúng userId do VSF cung cấp cho MiniApp để SuperApp hiển thị & quản trị. Lưu ý: Với trường hợp đơn hàng do user tạo thì created_by chính là customer_id.
platform_codestring✅ YesMax 64 ký tựYesMã platformcode tại VSF (VD SuperApp v-app)
miniapp_idstring✅ YesMax 64 ký tựYesMiniApp ID trong SuperApp
orderobject✅ Yes--NoThông tin đơn hàng
order.order_idstring✅ YesMax 64 ký tựYesMã đơn hàng
order.order_typestring✅ YesMax 64 ký tựNoLoại đơn hàng - sử dụng một trong các Product Type enum values (xem section 8.3)
order.order_namestring❌ NoMax 256 ký tựNoTên hiển thị chung cho order (dùng cho case 0 SKU hoặc order summary)
order.category_codestring❌ NoMax 64 ký tựNoMã danh mục/ngành hàng ở cấp đơn hàng (optional; hữu ích cho case 0 SKU hoặc phân loại ở order-level)
order.category_namestring❌ NoMax 256 ký tựNoTên danh mục/ngành hàng ở cấp đơn hàng (optional; đi kèm order.category_code)
order.statusstring✅ Yes--NoTrạng thái đơn
order.pnl_order_statusstring✅ YesMax 64 ký tựNoMã trạng thái gốc của PnL (dạng code)
order.pnl_order_status_labelstring❌ NoMax 256 ký tựNoLabel hiển thị trạng thái theo hệ thống PnL (dùng cho UI SuperApp)
order.payment_statusstring✅ Yes--NoTrạng thái thanh toán
order.descriptionstring❌ No--NoMô tả đơn hàng
Customer
order.customerobject✅ Yes--NoThông tin khách hàng
order.customer.customer_idstring✅ YesMax 64 ký tựNoMã khách hàng. Yêu cầu: truyền đúng userId do VSF cung cấp cho MiniApp để SuperApp hiển thị & quản trị.
order.customer.namestring✅ YesMax 128 ký tựNoTên khách hàng
order.customer.phonestring❌ No10-11 sốNoSố điện thoại
order.customer.emailstring❌ NoĐịnh dạng emailNoEmail
Items
order.itemsarray❌ NoMin 1 itemNoDanh sách items (optional - hỗ trợ trường hợp 0 SKU)
order.items[].item_idstring✅ YesMax 64 ký tựNoMã item
order.items[].namestring✅ YesMax 256 ký tựNoTên item
order.items[].quantityinteger✅ Yes≥ 1NoSố lượng
order.items[].descriptionstring❌ No--NoMô tả item
order.items[].unit_pricenumber✅ Yes≥ 0NoĐơn giá
order.items[].total_pricenumber✅ Yes≥ 0NoTổng giá
order.items[].item_typestring❌ No--NoLoại item
order.items[].item_categorystring❌ No--NoDeprecated. Nhóm item; ưu tiên dùng category_code + category_name (đồng bộ với Payment). Vẫn hỗ trợ để tương thích ngược.
order.items[].category_codestring❌ NoMax 64 ký tựNoMã danh mục/ngành hàng (optional)
order.items[].category_namestring❌ NoMax 256 ký tựNoTên danh mục/ngành hàng (optional)
order.items[].currencystring✅ YesMax 8 ký tựNoĐơn vị tiền
order.items[].image_urlstring❌ NoURLNoẢnh minh họa
order.items[].attributesobject❌ Nokey-valueNoThuộc tính mở rộng
Sub Items
order.items[].sub_itemsarray❌ No--NoDanh sách sub item
order.items[].sub_items[].sub_item_idstring✅ YesMax 64 ký tựNoMã sub item
order.items[].sub_items[].namestring✅ YesMax 256 ký tựNoTên sub item
order.items[].sub_items[].quantityinteger✅ Yes≥ 1NoSố lượng sub item
order.items[].sub_items[].descriptionstring❌ No--NoMô tả sub item
order.items[].sub_items[].unit_pricenumber✅ Yes≥ 0NoĐơn giá sub item
order.items[].sub_items[].total_pricenumber✅ Yes≥ 0NoTổng giá sub item
order.items[].sub_items[].item_typestring❌ No--NoLoại sub item
order.items[].sub_items[].item_categorystring❌ No--NoDeprecated. Nhóm sub item; ưu tiên dùng category_code + category_name. Vẫn hỗ trợ để tương thích ngược.
order.items[].sub_items[].category_codestring❌ NoMax 64 ký tựNoMã danh mục/ngành hàng (optional)
order.items[].sub_items[].category_namestring❌ NoMax 256 ký tựNoTên danh mục/ngành hàng (optional)
order.items[].sub_items[].currencystring✅ YesMax 8 ký tựNoĐơn vị tiền sub item
order.items[].sub_items[].image_urlstring❌ NoURLNoẢnh sub item
order.items[].sub_items[].attributesobject❌ Nokey-valueNoThuộc tính mở rộng
Fees
order.feesarray❌ No--NoDanh sách phí
order.fees[].fee_codestring❌ NoMax 64 ký tựNoMã loại phí (PnL define; optional vì một số bên không có)
order.fees[].descriptionstring❌ No--NoMô tả phí (khuyến nghị nhập để hiển thị chi tiết cho user khi không có fee_code)
order.fees[].amountnumber✅ Yes≥ 0NoSố tiền phí
order.fees[].metadataobject❌ Nokey-valueNoThông tin bổ sung cho từng fee (VD: { "vat": "10%", "charge_type": "DELIVERY" })
Promotion
order.promotion_infosarray❌ No--NoDanh sách khuyến mãi/voucher áp dụng
order.promotion_infos[].voucher_codestring❌ NoMax 64 ký tựNoMã voucher
order.promotion_infos[].voucher_idstring❌ NoMax 64 ký tựNoID voucher trong hệ thống PnL
order.promotion_infos[].campaign_idstring❌ NoMax 64 ký tựNoID campaign/chiến dịch khuyến mãi
order.promotion_infos[].campaign_namestring❌ NoMax 255 ký tựNoTên campaign/chiến dịch khuyến mãi
order.promotion_infos[].discount_typestring❌ NoMax 32 ký tựNoLoại giảm giá
order.promotion_infos[].discount_valuenumber❌ No≥ 0NoGiá trị giảm giá
order.promotion_infos[].amount_before_discountnumber❌ No≥ 0NoTổng tiền trước giảm giá
order.promotion_infos[].discount_amountnumber❌ No≥ 0NoSố tiền giảm
order.promotion_infos[].amount_after_discountnumber❌ No≥ 0NoTổng tiền sau giảm giá
order.promotion_infos[].extra_dataobject❌ Nokey-valueNoDữ liệu mở rộng
Transactions
order.transactionsarray❌ No--NoDanh sách giao dịch
order.transactions[].transaction_idstring✅ YesMax 64 ký tựNoMã giao dịch
order.transactions[].providerstring✅ YesMax 64 ký tựNoĐơn vị thanh toán
order.transactions[].methodstring✅ YesMax 64 ký tựNoPhương thức thanh toán
order.transactions[].voucher_codestring❌ NoMax 64 ký tựNoMã voucher giao dịch
order.transactions[].amountnumber✅ Yes≥ 0NoSố tiền giao dịch
order.transactions[].statusstring✅ YesMax 32 ký tựNoTrạng thái giao dịch. Domain values khuyến nghị:
- PENDING: Đang chờ xử lý hoặc chờ user thanh toán
- COMPLETED: Thanh toán thành công
- FAILED: Thanh toán thất bại
- CANCELLED: Giao dịch bị hủy
order.transactions[].notestring❌ NoMax 512 ký tựNoGhi chú/ngữ cảnh cho giao dịch (VD: "Thanh toán lần 1 - đặt cọc", "Thanh toán bổ sung do đổi điểm đến", "Hủy do khách hàng yêu cầu", v.v.)
order.transactions[].captured_atlong❌ Notimestamp (ms)NoThời điểm thanh toán
Amounts
order.amountsobject✅ Yes--NoThông tin số tiền
order.amounts.currencystring✅ YesMax 8 ký tựNoĐơn vị tiền
order.amounts.total_amountnumber✅ Yes≥ 0YesTổng tiền đơn hàng
order.amounts.paid_amountnumber❌ No≥ 0, default: 0NoSố tiền đã thanh toán
order.amounts.refunded_amountnumber❌ No≥ 0, default: 0NoSố tiền đã hoàn
order.amounts.grand_totalnumber✅ Yes≥ 0YesTổng tiền cuối cùng (sau KM)
Metadata
order.metadataobject❌ Nokey-valueNoThông tin bổ sung về nghiệp vụ/quản lý (dealer_code, sales_agent, contract_type, vat_rate...). Dùng cho mục đích quản lý và audit trail.
order.attributesobject❌ Nokey-valueNoThuộc tính mô tả order để hiển thị trên UI (pickup_location, dropoff_location, estimated_duration, delivery_date...). Đặc biệt hữu ích cho case 0 SKU khi không có items.
Links
order.deeplinkstring✅ YesURLNoDeeplink mở order của MiniApp trên SuperApp, COV trả nguyên trạng
order.image_urlstring❌ NoURLNoẢnh minh họa cho order (dùng cho Order Card trên SuperApp)
Business Location
order.business_unit_idstring❌ NoMax 64 ký tựNoMã cơ sở kinh doanh (Business Unit ID). Lưu ý: Giá trị này được thống nhất và khai báo để định danh ở hệ thống kế toán chung. COV và các hệ thống phía sau (SAP, Payment Hub, BI...) sẽ dùng giá trị này để xác định được đơn hàng thuộc cơ sở kinh doanh nào, phục vụ hạch toán & báo cáo. Chỉ cần truyền trong event order.created, không chỉnh sửa được sau khi đơn hàng đã được tạo. Bắt buộc với PnL onboard SAP/Payment Hub/BI. Với PnL không cần sync xuống các hệ thống phía sau, có thể bỏ trống.
order.branch_idstring❌ NoMax 64 ký tựNoMã chi nhánh. Lưu ý: Giá trị này được thống nhất và khai báo để định danh ở hệ thống kế toán chung. COV và các hệ thống phía sau (SAP, Payment Hub, BI...) sẽ dùng giá trị này để xác định được đơn hàng thuộc chi nhánh nào, phục vụ hạch toán & báo cáo. Chỉ cần truyền trong event order.created, không chỉnh sửa được sau khi đơn hàng đã được tạo. Bắt buộc với PnL onboard SAP/Payment Hub/BI. Với PnL không cần sync xuống các hệ thống phía sau, có thể bỏ trống.
order.seller_merchant_idstring❌ NoMax 64 ký tựNoMã merchant của seller để phục vụ filter/reporting/hạch toán theo seller trong mô hình marketplace/multi-seller. Trường này dùng cho phân loại dữ liệu đơn hàng, không thay đổi luồng tiền settlement. Khuyến nghị chỉ truyền trong order.created để giữ tính ổn định dữ liệu filter.

Request Body Example:

{
"secure_hash": "a8f5e2c1b9d4f3e7a6b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0",
"platform_code": "v-app",
"miniapp_id": "vinfast_sales",
"event_timestamp": 1731578400000,
"order": {
"order_id": "VF-ORDER-001",
"order_type": "EV_AND_CHARGING",
"order_name": "Đặt xe VinFast VF e34",
"category_code": "CAT_CAR_PURCHASE",
"category_name": "Car Purchase",
"status": "IN_PROGRESS",
"pnl_order_status": "VINFAST_WAITING_PAYMENT",
"pnl_order_status_label": "Đang chờ thanh toán (VinFast)",
"payment_status": "UNPAID",
"description": "Đặt xe VF e34 bản Premium",
"customer": {
"customer_id": "CUST_1001",
"name": "Nguyen Van A",
"phone": "0901234567",
"email": "[email protected]"
},
"delivery_address": {
"recipient_name": "Nguyen Van A",
"phone": "0901234567",
"address_line1": "123 Đường Láng",
"address_line2": "Tòa nhà FPT",
"ward": "Láng Thượng",
"city": "Hà Nội",
"country": "Vietnam",
"postal_code": "100000"
},
"items": [
{
"item_id": "ITEM001",
"name": "Đặt xe VF e34",
"quantity": 1,
"description": "Đặt cọc xe bản Premium",
"unit_price": 40000000,
"total_price": 40000000,
"item_type": "BOOKING",
"item_category": "VEHICLE",
"category_code": "CAT_ELECTRONICS",
"category_name": "Electronics",
"currency": "VND",
"image_url": "https://cdn.vinfast.vn/e34.png",
"attributes": {
"color": "red",
"variant": "Premium"
},
"sub_items": [
{
"sub_item_id": "SITM-01",
"name": "Gói phụ kiện cơ bản",
"item_category": "ACCESSORY",
"category_code": "CAT_ACCESSORY",
"category_name": "Accessory",
"price": 0,
"image_url": "https://cdn.vinfast.vn/e34.png"
}
]
}
],
"fees": [
{
"fee_code": "DELIVERY_FEE",
"description": "Phí giao xe tận nơi",
"amount": 200000,
"currency": "VND",
"metadata": {
"vat": "10%",
"charge_type": "DELIVERY"
}
}
],
"promotion_infos": [
{
"voucher_code": "VFSALE10",
"voucher_id": "VOUCHER_12345",
"campaign_id": "SUMMER2025",
"campaign_name": "Summer Sale 2025",
"discount_type": "PERCENTAGE",
"discount_value": 10,
"currency": "VND",
"amount_before_discount": 40000000,
"discount_amount": 4000000,
"amount_after_discount": 36000000,
"extra_data": {
"promotion_channel": "SuperApp",
"applied_by": "AUTO"
}
}
],
"transactions": [
{
"transaction_id": "TRX_123457",
"provider": "INTERNAL",
"method": "VOUCHER",
"currency": "VND",
"voucher_code": "SALE50M",
"amount": 50000000,
"status": "COMPLETED",
"note": "Thanh toán đặt cọc xe VF e34"
}
],
"amounts": {
"currency": "VND",
"total_amount": 36200000,
"grand_total": 36200000,
"paid_amount": 0,
"refunded_amount": 0
},
"attributes": {
"delivery_date": "2025-12-15",
"delivery_method": "Giao tận nơi"
},
"metadata": {
"sales_agent": "VN001",
"booking_channel": "App VinFast"
},
"deeplink": "superapp://miniapp/vinfast_sales/orders/VF-ORDER-001",
"image_url": "https://cdn.vinfast.vn/orders/vf-e34-order.png",
"business_unit_id": "BU_VINFAST_001",
"branch_id": "BR_HN_001",
"seller_merchant_id": "SELLER_MERCHANT_001"
}
}

Response Success (200 OK):

{
"code": 0,
"message": "Order created event received successfully",
"data": {
"correlationId": "5d83ced5-3ed8-457f-9669-33b9b3bb8736",
"timestamp": 1763094301067,
"orderId": "VF-ORDER-001",
"platformCode": "VINFAST"
}
}

Response Duplicate (200 OK):

{
"code": 0,
"message": "Event already processed",
"data": {
"correlationId": "5d83ced5-3ed8-457f-9669-33b9b3bb8736",
"timestamp": 1763094301067,
"orderId": "VF-ORDER-001",
"platformCode": "VINFAST"
}
}

5.2 order.updated (PATCH mode)

POST /webhook/v1/orders/updated

Mô tả:

Nhận event order.updated từ PnL khi có thay đổi về đơn hàng (status, payment, items, fees, metadata...).

LƯU Ý QUAN TRỌNG:

  1. Event chỉ gửi phần thay đổi (không cần gửi toàn bộ order)
  2. Nếu gửi items → Hệ thống DELETE toàn bộ items cũ và INSERT items mới
  3. Nếu gửi fees → Hệ thống DELETE toàn bộ fees cũ và INSERT fees mới
  4. Nếu gửi transactions → Hệ thống DELETE toàn bộ transactions cũ và INSERT transactions mới
  5. Nếu KHÔNG gửi items/fees/transactions → Giữ nguyên data cũ (không thay đổi)
  6. event_timestamp phải > updated_at trong DB → Reject nếu out-of-order (tránh ghi đè data mới bằng data cũ)

Update Mode:

ModeBehaviorUse Case
PATCHUpdate các field được gửi. Đặc biệt: nếu gửi items/fees/transactions → DELETE toàn bộ cũ + INSERT mớiCập nhật một phần (khuyến nghị)
REPLACEReplace từng object/array được gửi lên (xóa fields cũ không có trong payload)Sync lại một phần từ PnL

Required/Optional Fields:

Field PathRequiredTypeMô tả
secure_hash✅ YesstringHMAC-SHA256 hash
event_timestamp✅ YeslongUnix timestamp (milliseconds)
created_by✅ YesstringID của người/hệ thống thực hiện hành động update
order.order_id✅ YesstringOrder ID (để xác định order cần update)
update_mode❌ NostringPATCH (default) hoặc REPLACE
order.status❌ NostringUpdate nếu có thay đổi
order.payment_status❌ NostringUpdate nếu có thay đổi
order.amounts❌ NoobjectNếu gửi → phải có đầy đủ total_amount & grand_total
order.items❌ NoarrayNếu gửi → DELETE & INSERT toàn bộ
order.fees❌ NoarrayNếu gửi → DELETE & INSERT toàn bộ
order.transactions❌ NoarrayNếu gửi → DELETE & INSERT toàn bộ
order.attributes❌ NoobjectUpdate thuộc tính mô tả order nếu có thay đổi
order.metadata❌ NoobjectUpdate thông tin nghiệp vụ/quản lý nếu có thay đổi
order.image_url❌ NostringUpdate ảnh minh họa cho order nếu có thay đổi
order.*❌ NoanyCác fields khác - update theo mode

Note: created_by nên là ID chuẩn hoá (ví dụ: {ACCOUNT_ID} hoặc {SERVICE_NAME}) để phục vụ audit trail.

⚠️ Lưu ý đặc biệt:

  • Nếu update giá (amounts), bắt buộc phải gửi cả total_amountgrand_total để tính secure_hash
  • Nếu không update giá, không cần gửi amountssecure_hash không có total_amount & grand_total

Chi tiết Update Mode:

PATCH Mode (Khuyến nghị):

  • Scalar fields (status, payment_status, description...): Update nếu có trong payload, giữ nguyên nếu không có
  • Array fields (items, fees, transactions):
    • Nếu có trong payload → DELETE toàn bộ cũ + INSERT mới
    • Nếu không có trong payload → Giữ nguyên
  • Nested objects (amounts, customer, delivery_address): Merge với data cũ (giữ lại fields không gửi)

Ví dụ PATCH:

// DB hiện tại có: 
// - status="IN_PROGRESS"
// - customer={name: "A", phone: "123", email: "[email protected]", address: "HN", age: 30}
// - items=[ITEM001, ITEM002]

// Payload gửi:
{
"update_mode": "PATCH",
"order": {
"order_id": "VF-ORDER-001",
"status": "PAID",
"customer": {
"name": "B",
"email": "[email protected]"
},
"items": [ITEM003]
}
}

// Kết quả:
// - status="PAID" (updated)
// - customer={name: "B", phone: "123", email: "[email protected]", address: "HN", age: 30}
// → Merge: name & email updated, phone/address/age giữ nguyên
// - items=[ITEM003] (replaced)

REPLACE Mode:

  • Replace từng object/array được gửi - xóa fields cũ không có trong payload của object đó
  • Object không gửi → Giữ nguyên object đó
  • Chỉ các fields trong object được gửi mới bị replace

Ví dụ REPLACE:

// DB hiện tại có: 
// - status="IN_PROGRESS"
// - customer={name: "A", phone: "123", email: "[email protected]", address: "HN", age: 30}
// - items=[ITEM001, ITEM002]

// Payload gửi:
{
"update_mode": "REPLACE",
"order": {
"order_id": "VF-ORDER-001",
"status": "PAID",
"customer": {
"name": "B",
"email": "[email protected]"
// Chỉ gửi 2 fields
},
"items": [ITEM003]
}
}

// Kết quả:
// - status="PAID" (updated)
// - customer={name: "B", email: "[email protected]"}
// → Replace: phone/address/age bị XÓA vì không có trong payload
// - items=[ITEM003] (replaced)

So sánh:

Field TypePATCH ModeREPLACE Mode
Scalar (status)Update nếu cóUpdate nếu có
Object (customer)Merge - giữ fields không gửiReplace - xóa fields không gửi
Array (items)Replace toàn bộReplace toàn bộ

⚠️ Xử lý Out-of-Order Events:

COV sử dụng event_timestamp để đảm bảo tính nhất quán:

  • Event với timestamp cũ hơn sẽ bị reject - không được ghi đè lên event mới hơn

  • Ví dụ:

    T1: order.updated gửi lúc 10:00:00 (event_timestamp = 1731582000000) → SUCCESS
    T2: order.updated gửi lúc 09:59:00 (event_timestamp = 1731581940000) → REJECT
  • Best Practice:

    • Luôn dùng timestamp thực tế khi event xảy ra
    • Đảm bảo clock sync giữa các server
    • Xem chi tiết về xử lý retry trong section 8.6 (Retry Mechanism)

Request Body Example:

{
"secure_hash": "b9e2a7f3c8d1e4f6a5b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9",
"event_timestamp": 1731582000000,
"update_mode": "PATCH",
"created_by": "user:VINFAST_BACKOFFICE_ID_001",
"order": {
"order_id": "VF-ORDER-001",
"status": "PAID",
"pnl_order_status": "VINFAST_CONFIRMED",
"payment_status": "PARTIAL_PAID",
"amounts": {
"currency": "VND",
"total_amount": 81500000,
"grand_total": 81500000,
"paid_amount": 20000000,
"refunded_amount": 0
},
"items": [
{
"item_id": "ITEM001",
"name": "Đặt xe VF e34",
"quantity": 2,
"unit_price": 40000000,
"total_price": 80000000,
"item_type": "BOOKING",
"item_category": "VEHICLE",
"category_code": "CAT_ELECTRONICS",
"category_name": "Electronics",
"currency": "VND",
"attributes": {
"variant": "Plus",
"color": "white"
},
"sub_items": [
{
"sub_item_id": "SITM-02",
"name": "Gói phụ kiện cao cấp",
"item_category": "ACCESSORY",
"category_code": "CAT_ACCESSORY",
"category_name": "Accessory",
"price": 2000000,
"image_url": "https://cdn.vinfast.vn/accessory-premium.png"
}
]
},
{
"item_id": "ITEM003",
"name": "Bảo hiểm thân xe 1 năm",
"item_category": "SERVICE",
"category_code": "CAT_SERVICE",
"category_name": "Service",
"quantity": 1,
"unit_price": 1200000,
"total_price": 1200000,
"currency": "VND"
}
],
"fees": [
{
"fee_code": "DELIVERY_FEE",
"description": "Phí giao xe tận nơi (cập nhật)",
"amount": 300000,
"currency": "VND"
}
],
"metadata": {
"sales_agent": "VN001",
"booking_channel": "App VinFast",
"note": "Khách hàng nâng cấp từ bản Premium lên bản Plus"
},
"image_url": "https://cdn.vinfast.vn/orders/vf-e34-order-updated.png"
}
}

Response Success (200 OK):

{
"code": 0,
"message": "Order updated event received successfully",
"data": {
"correlationId": "32e60e55-35d0-4358-9b24-c4dd82bc19f8",
"timestamp": 1763094396179,
"orderId": "VF-ORDER-001",
"platformCode": "VINFAST"
}
}

5.3 order.cancelled

POST /webhook/v1/orders/cancelled

Required/Optional Fields:

Field PathRequiredTypeMô tả
secure_hash✅ YesstringHMAC-SHA256 hash
event_timestamp✅ YeslongUnix timestamp (milliseconds)
created_by✅ YesstringID của người/hệ thống thực hiện hành động cancel
order.order_id✅ YesstringOrder ID cần cancel
order.status✅ YesstringPhải là "CANCELLED"
order.pnl_order_status✅ YesstringStatus chi tiết (VD: CANCELLED_BY_CUSTOMER)
order.payment_status✅ YesstringTrạng thái thanh toán sau cancel
order.cancellation_reason❌ NostringLý do hủy
order.cancelled_by❌ NostringNgười/hệ thống hủy
order.metadata❌ NoobjectThông tin bổ sung

Note: created_by đại diện cho ID của actor thực hiện hủy (ví dụ: {ACCOUNT_ID}, {SERVICE_NAME}) để phục vụ audit trail.

Request Body Example:

{
"secure_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"event_timestamp": 1731598200000,
"created_by": "user:CUSTOMER_ID_001",
"order": {
"order_id": "VF-ORDER-001",
"status": "CANCELLED",
"pnl_order_status": "CANCELLED_BY_CUSTOMER",
"payment_status": "REFUNDED"
}
}

Response Success (200 OK):

{
"code": 0,
"message": "Order cancelled event received successfully",
"data": {
"correlationId": "76fc562e-ed99-4edc-a25d-791d7c0d751c",
"timestamp": 1763094456237,
"orderId": "VF-ORDER-001",
"platformCode": "VINFAST"
}
}

6. Trạng Thái Chuẩn tại COV

6.1 Order Status

StatusMô tả
IN_PROGRESSĐơn hàng mới tạo, đang nằm trong pipeline xử lý của PnL.
PAIDĐơn hàng đã thanh toán thành công toàn bộ giá trị yêu cầu.
SHIPPEDĐơn hàng đang trong quá trình vận chuyển tới khách hàng.
FULFILLEDĐơn hàng đã giao đến khách hàng thành công (đã hoàn tất giao/nhận).
COMPLETEDĐơn hàng đã hoàn tất cả thanh toán lẫn xử lý nghiệp vụ liên quan.
CANCELLEDĐơn hàng bị đối tác/PnL hủy, không tiếp tục xử lý nữa.

Order lifecycle đề xuất:

Reopen case: Khi order đã PAID nhưng phát sinh điều chỉnh (tăng giá, đổi dịch vụ...), PnL có thể chuyển PAID → IN_PROGRESS để xử lý lại trước khi hoàn tất. Ví dụ: cuốc xe GSM tăng từ 100K lên 150K → order status chuyển về IN_PROGRESS để thu thêm 50K rồi mới quay lại flow PAID → ....

6.2 Payment Status

StatusMô tả
UNPAIDĐơn hàng chưa phát sinh khoản thanh toán nào.
PAIDThanh toán đã diễn ra thành công (đã nhận đủ tiền).
FAILEDGiao dịch thanh toán thất bại và không ghi nhận tiền.
PARTIAL_PAIDKhách hàng chỉ thanh toán một phần giá trị đơn hàng.
REFUNDEDToàn bộ số tiền đã được hoàn trả về cho khách hàng.

Ghi chú: order.statusorder.payment_status vẫn độc lập nhưng cần gửi đồng thời để COV thể hiện chính xác dòng đời đơn hàng và trạng thái thanh toán. Đơn có thể đã giao xong nhưng chưa thu đủ tiền, hoặc đã thanh toán nhưng vẫn đang vận chuyển. COV cần cả hai dimension để render nhận biết chính xác trạng thái chung của đơn hàng.

Scenario GSM (Xanh Taxi): User đặt cuốc 100K và thanh toán đủ (order.status = PAID, payment_status = PAID). Sau đó đổi điểm đến khiến giá tăng 150K. Trước khi thu thêm 50K, PnL nên:

  • Chuyển order.status từ PAIDIN_PROGRESS (đơn mở lại để xử lý).
  • Chuyển payment_status từ PAIDPARTIAL_PAID (vì mới thu 100K/150K).
  • Khi thu đủ 50K còn lại, cập nhật lại payment_status = PAID và tiếp tục lifecycle (IN_PROGRESSPAID → ...).

Điều này giúp UI hiển thị chính xác tình trạng thực tế và tránh nhầm lẫn cho khách hàng.

Payment lifecycle đề xuất:

Partial adjust: PAID → PARTIAL_PAID không chỉ dùng cho hoàn tiền một phần mà còn cho các trường hợp tăng/giảm giá trị order sau khi đã thanh toán (ví dụ: thu thêm 50K cuốc xe GSM). Sau khi thu đủ, chuyển lại payment_status = PAID.

Lưu ý quan trọng:

  • payment_statusorder_statusđộc lập nhưng có mối liên hệ
  • Ví dụ: Order có thể ở FULFILLED nhưng payment_status vẫn là PARTIAL_PAID (giao hàng trước, thanh toán sau)
  • Khi cập nhật trạng thái, cần gửi cả hai fields order.statusorder.payment_status để COV hiểu rõ tình trạng

7. Response Format

Tất cả response đều theo format chuẩn:

{
"code": 0,
"message": "Success message or error description",
"data": {
// Response data object
}
}

7.1 Success Response

code = 0: Request thành công

{
"code": 0,
"message": "Order created successfully",
"data": {
"order_id": "VF-ORDER-001",
"status": "SUCCESS",
"processed_at": 1731578405000
}
}

7.2 Duplicate Response (Idempotent)

code = 0: Event đã được xử lý trước đó

{
"code": 0,
"message": "Event already processed",
"data": {
"order_id": "VF-ORDER-001",
"status": "DUPLICATE",
"original_processed_at": 1731578405000
}
}

7.3 Error Response

code != 0: Request thất bại

{
"code": 400,
"message": "Validation error: Missing required field",
"data": {
"error_code": "VALIDATION_ERROR",
"field": "order.order_id",
"constraint": "required"
}
}

8. Common Specifications

8.1 Idempotency

  • Hệ thống đảm bảo idempotent cho tất cả webhooks
  • Gửi cùng 1 event nhiều lần → chỉ xử lý 1 lần duy nhất
  • Response 200 OK với status DUPLICATE nếu event đã được xử lý trước đó
  • Idempotency key: X-Request-Id + X-Api-Key Ví dụ flow:
Request #1: X-Request-Id = "550e8400-e29b-41d4-a716-446655440000"
→ Response: 200 OK, status = "SUCCESS"

Request #2: X-Request-Id = "550e8400-e29b-41d4-a716-446655440000" (same)
→ Response: 200 OK, status = "DUPLICATE"

8.2 Error Responses

Lưu ý quan trọng về Response Codes:

  • code (trong response body): Implementation code - mã lỗi chi tiết 4 digits của COV system
  • HTTP Status Code: Mã HTTP chuẩn để PnL xử lý retry logic Ví dụ:
{
"code": 4101, // ← Implementation code (chi tiết)
"message": "Invalid secure_hash",
"data": { ... }
}
// HTTP Status: 401
Error TypeHTTP Statuscode (Implementation)Mô tảAction
Success2000Request thành công✅ Continue
Validation Error4004000Dữ liệu không hợp lệ (thiếu field, sai format, etc.)Kiểm tra lại request body và retry
Invalid API Key4014201X-API-Key không hợp lệ hoặc đã hết hạnLiên hệ COV team để cấp lại key
Invalid secureHash4014101secure_hash không khớpKiểm tra lại secret_key và cách tính hash
Duplicate X-Request-Id4094002X-Request-Id đã được sử dụng cho event khácGenerate UUID mới và retry
Stale Event4094003event_timestamp cũ hơn updated_at hiện tạiKhông retry, event đã bị outdated
Rate Limit Exceeded4294290Vượt quá giới hạn request (default: 100 req/min)Chờ 1 phút và retry
Internal Server Error5005000Lỗi hệ thống COVRetry với exponential backoff

Error Response Format:

{
"code": 4000, // Implementation code (chi tiết lỗi)
"message": "Validation error: Missing required field",
"data": {
"error_code": "VALIDATION_ERROR",
"field": "order.order_id",
"constraint": "required"
}
}
// HTTP Status: 400

Error Response Examples:

Invalid secureHash:

{
"code": 4101,
"message": "Invalid secureHash",
"data": {
"error_code": "INVALID_SECURE_HASH",
"expected_fields": "event_timestamp|grand_total|miniapp_id|order_id|platform_code|total_amount"
}
}
// HTTP Status: 401
**Invalid API Key:**
```json
{
"code": 4201,
"message": "Invalid or expired API Key",
"data": {
"error_code": "INVALID_API_KEY",
"api_key_prefix": "ak_live_xxxx..."
}
}
// HTTP Status: 401

Duplicate X-Request-Id:

{
"code": 4002,
"message": "X-Request-Id already used for different event",
"data": {
"error_code": "DUPLICATE_REQUEST_ID",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"original_event": "order.created",
}
}
// HTTP Status: 409

8.3 Product Type Enum (Order Type)

Mô tả: Product Type Enum này vừa là enum phân loại sản phẩm/dịch vụ, đồng thời cũng chính là danh sách các giá trị được phép cho field order.order_type. PnL cần sử dụng một trong các enum values dưới đây khi gửi webhook.

Danh sách Product Type / Order Type:

TênOrder Type (Enum Value)Mô tả
Di chuyểnTRANSPORTATIONCác dịch vụ liên quan đến vận tải hành khách, gọi xe, chia sẻ xe
Vận chuyểnLOGISTICSDịch vụ giao hàng, logistics, vận chuyển hàng hóa
Xe điện & Trạm sạcEV_AND_CHARGINGỨng dụng liên quan đến xe điện, quản lý xe, tìm kiếm và đặt trạm sạc
Mua xe VinfastVINFAST_PURCHASEDịch vụ tư vấn, đặt cọc, mua bán, hợp đồng xe điện VinFast
Hậu mãi VinfastAFTER_SALESDịch vụ hậu mãi, bảo hành, sửa chữa xe VinFast
Bất động sảnREAL_ESTATEDịch vụ mua bán, cho thuê bất động sản, tìm kiếm nhà đất
Cư dânRESIDENTIAL_SERVICESCác dịch vụ hỗ trợ sinh hoạt hàng ngày, quản lý gia đình
Du lịchTRAVELDịch vụ đặt phòng khách sạn, tour du lịch, vé máy bay
Giải tríENTERTAINMENTTrò chơi, âm nhạc, phim ảnh, nội dung giải trí số
Bán lẻ & thương mại điện tửCOMMERCECác dịch vụ bán lẻ, thương mại tổng hợp (MyVincom)
Giáo dụcEDUCATIONKhóa học trực tuyến, luyện thi, nền tảng học tập
Y tếHEALTHCAREDịch vụ tư vấn sức khỏe, đặt lịch khám bác sĩ, mua thuốc
Mua xe VinfastCAR_PURCHASEDịch vụ tư vấn, đặt cọc mua bán, hợp đồng xe điện VinFast
Hậu mãi VinfastAFTER_SALESHậu mãi Vinfast
Thanh toán hóa đơnBILLING_PAYMENTDịch vụ thanh toán hóa đơn
Mua voucher/khuyến mãiPROMOTION_PURCHASEDịch vụ mua voucher/khuyến mãi
Tiện ích khácOTHER_UTILITIESCác dịch vụ không nằm trong các danh mục trên

Ví dụ sử dụng:

{
"order": {
"order_id": "VF-ORDER-001",
"order_type": "EV_AND_CHARGING",
...
}
}

8.4 Metadata và Attributes Format theo Order Type

Mô tả: Thông tin order được enrich và hiển thị trên mobile SuperApp theo từng order_type. Mỗi order_type sẽ có format/chuẩn riêng cho order.metadata, order.attributesitems[].attributes để hệ thống có thể hiển thị dữ liệu cho khách hàng chính xác.

Order Card UI Component

Yêu cầu quan trọng:

  • PnL bắt buộc phải mapping đúng order_type và tuân thủ format thống nhất cho metadata, order.attributesitems[].attributes theo từng loại đơn hàng
  • Giá trị trong attributesmetadatađộng theo từng order_type, theo dạng key-value và theo nghiệp vụ của các bên
  • Để hiển thị đúng trên Mobile, các team cần cung cấp các field chính (key) để hệ thống biết extract key nào để hiển thị lên mobile
  • COV sẽ sử dụng các thông tin này để render Order Card trên mobile theo đúng chuẩn thiết kế
  • Đặc biệt với case 0 SKU: Khi order không có items, cần sử dụng order.attributes để cung cấp thông tin mô tả order cho Order Card

Các trường hợp sử dụng:

  • Order Card hiển thị: Tên dịch vụ (order.order_type), trạng thái đơn hàng (order.status), thông tin sản phẩm (items[].name, items[].image_url), thuộc tính (items[].description, items[].attributes), tổng tiền (order.amounts.total_amount)
  • 0 SKU: Order không có items cụ thể, hiển thị order.order_nameorder.attributes (thuộc tính mô tả order như pickup_location, dropoff_location, estimated_duration...)
  • 0 SKU + category: Khuyến nghị truyền thêm order.category_codeorder.category_name để phân loại đơn hàng ở cấp order (không phụ thuộc item).
  • 1 SKU: Order có 1 item, hiển thị đầy đủ thông tin sản phẩm từ items[].attributes
  • > 1 SKU: Order có nhiều items, hiển thị dạng collapse/expand với "Xem thêm"

Lưu ý:

  • Format chi tiết và danh sách các field chính (key) cần truyền trong metadata, order.attributesitems[].attributes cho từng order_type sẽ được cung cấp trong tài liệu riêng hoặc thông qua DevCenter
  • PnL cần liên hệ với team COV để được hướng dẫn các key bắt buộc cần truyền cho order_type của mình để hệ thống có thể extract và hiển thị đúng trên mobile
  • Việc không cung cấp đầy đủ các key chính hoặc không tuân thủ format có thể dẫn đến hiển thị sai hoặc thiếu thông tin trên mobile
  • Phân biệt metadata vs attributes ở order level:
    • order.metadata: Thông tin nghiệp vụ/quản lý (dealer_code, sales_agent, contract_type, vat_rate...) - không hiển thị trực tiếp trên Order Card
    • order.attributes: Thuộc tính mô tả để hiển thị trên UI (pickup_location, dropoff_location, estimated_duration, delivery_date...) - hiển thị trên Order Card

Ví dụ minh họa:

// VINFAST_PURCHASE - Mua xe VinFast
{
"order": {
"order_type": "VINFAST_PURCHASE",
"items": [{
"name": "VinFast VF 7",
"attributes": {
"model": "VF7",
"variant": "Premium",
"color": "Xanh SM",
"vin_number": "VF7XXXXX"
}
}],
"metadata": {
"dealer_code": "VF_HN_001",
"dealer_name": "Đại lý VinFast Hà Nội",
"sales_agent": "VN001",
"contract_type": "PURCHASE"
}
}
}

// TRANSPORTATION - Gọi xe
{
"order": {
"order_type": "TRANSPORTATION",
"items": [{
"name": "Gọi xe",
"attributes": {
"vehicle_type": "CAR",
"distance": "15.5 km"
}
}],
"attributes": {
"pickup_location": "123 Đường ABC",
"dropoff_location": "456 Đường XYZ",
"estimated_duration": "25 phút"
},
"metadata": {
"driver_id": "DRV_001",
"booking_channel": "SuperApp"
}
}
}

// TRANSPORTATION - Gọi xe (0 SKU case)
{
"order": {
"order_type": "TRANSPORTATION",
"order_name": "Gọi xe từ A đến B",
"category_code": "CAT_TRANSPORTATION",
"category_name": "Transportation",
"attributes": {
"pickup_location": "123 Đường ABC",
"dropoff_location": "456 Đường XYZ",
"estimated_duration": "25 phút",
"vehicle_type": "CAR",
"distance": "15.5 km"
},
"metadata": {
"driver_id": "DRV_001",
"booking_channel": "SuperApp"
}
}
}

8.5 Retry Strategy (Khuyến nghị)

HTTP StatuscodeRetry?Strategy
2000NoSuccess - không cần retry
4004000NoFix validation và gửi lại
4014101NoFix secureHash và gửi lại
4014201NoFix API Key và gửi lại
4094002YesGenerate UUID mới và retry ngay
4094503NoStale event - không retry
4294901YesWait 60s → retry
5005000YesExponential backoff (1s, 2s, 4s, 8s, 16s)

8.6 Retry Mechanism - Xử lý Sync Thất Bại

Vấn đề: MiniApp BE tạo/update đơn thành công nhưng không sync thành công sang COV (mất mạng, lỗi hệ thống, timeout, etc.). Trong trường hợp này:

  • Khách hàng thấy đơn trên MiniApp nhưng không thấy trên SuperApp
  • Đơn hàng tồn tại trong hệ thống PnL nhưng chưa có trong COV hoặc dữ liệu không đồng bộ

Hai chiến lược Retry:

Chiến lược 1: Retry với Event Timestamp gốc (Giữ nguyên thứ tự)

Mục đích: Giữ nguyên thứ tự event, chỉ sync lại event đã bị lỗi.

Cách làm:

  • Sử dụng event_timestamp gốc (thời điểm event thực sự xảy ra)
  • COV sẽ accept nếu chưa có event nào với timestamp đó
  • Nhược điểm: Nếu đã có event mới hơn trong COV → sẽ bị reject do "stale event"

Ví dụ:

T1 (10:00:00): Update order → event_timestamp = 1731582000000
T2 (10:00:01): Gửi webhook → FAILED (mất mạng)
T3 (10:05:00): Retry với event_timestamp = 1731582000000 (gốc) → SUCCESS

Chiến lược 2: Retry với Event Timestamp mới (Ghi đè dữ liệu)

Mục đích: Ghi đè dữ liệu hiện tại trong COV bằng dữ liệu từ event bị lỗi.

Cách làm:

  • Sử dụng event_timestamp mới (timestamp hiện tại khi retry)
  • COV sẽ accept và ghi đè lên event cũ hơn
  • Lưu ý: Chỉ dùng khi muốn force sync lại dữ liệu, không giữ nguyên thứ tự event

Ví dụ:

T1 (10:00:00): Update order → event_timestamp = 1731582000000
T2 (10:00:01): Gửi webhook → FAILED (mất mạng)
T3 (10:05:00): Update order mới → event_timestamp = 1731582300000 → SUCCESS (đã sync)
T4 (10:10:00): Retry event 10:00 với event_timestamp = 1731582600000 (mới) → SUCCESS (ghi đè dữ liệu 10:05)

Khi nào dùng chiến lược nào:

  • Chiến lược 1 (timestamp gốc): Khi muốn giữ nguyên thứ tự event, chỉ sync lại event bị lỗi
  • Chiến lược 2 (timestamp mới): Khi muốn force sync lại dữ liệu từ event bị lỗi, ghi đè lên dữ liệu hiện tại

Best Practice:

  • Implement retry queue với exponential backoff
  • Lưu trữ event gốc với metadata (event_timestamp gốc, payload, retry_count)
  • Retry tối đa N lần (ví dụ: 5 lần) với timestamp gốc
  • Nếu vẫn fail sau N lần → có thể chuyển sang dùng timestamp mới để force sync
  • Monitor và alert khi có nhiều event retry liên tục