Tạo SecureHash cho đơn hàng
Khi tạo đơn hàng từ Mini App, Order Backend của Mini App cần sinh mã secureHash để đảm bảo tính toàn vẹn dữ liệu của đơn hàng trước khi gọi initPayment().
Quy trình tạo đơn hàng
- User: Xác nhận đặt hàng trên Mini App (sau khi đã chọn sản phẩm & phương thức thanh toán)
- Mini App: Gọi API tạo đơn hàng xuống Order Backend của Mini App
- Order Backend (Mini App):
- Dựa vào
secretKeyđược cấp từ Payment Service, sinh mãsecureHashcho đơn hàng - Trả về
orderId,referenceId,orderCreatedAt,amount(số tiền cần thanh toán) cùngsecureHash(và các thông tin cần thiết) cho Mini App
- Dựa vào
- Mini App: Sử dụng thông tin nhận được (bao gồm
secureHash) để gọiinitPayment()
Công thức tạo SecureHash
Chuỗi chữ ký đảm bảo toàn vẹn dữ liệu của order được gen ra từ Order Backend (Mini App) theo công thức cơ bản (giữ backward-compatible với version cũ):
HMAC-SHA-256(secretKey, rawDataString(orderId + '|' + referenceId + '|' + amount + '|' + currency + '|' + createdAt))
Khi cần truyền thêm branchId / businessUnitId / sellerMerchantId trong request initPayment, Order Backend sẽ append động các giá trị này vào cuối chuỗi trước khi ký:
- Nếu có
branchId(không cóbusinessUnitId):rawDataString = orderId|referenceId|amount|currency|createdAt|branchId
- Nếu có cả
branchIdvàbusinessUnitId:rawDataString = orderId|referenceId|amount|currency|createdAt|branchId|businessUnitId
- Nếu có thêm
sellerMerchantId:rawDataString = orderId|referenceId|amount|currency|createdAt|branchId|businessUnitId|sellerMerchantId
- Nếu có thêm
paymentType: append sausellerMerchantId(nếu có), và saubusinessUnitId/branchIdnếu không cósellerMerchantId- Ví dụ:
orderId|referenceId|amount|currency|createdAt|branchId|businessUnitId|sellerMerchantId|paymentType
- Ví dụ:
- Nếu request
initPaymentcó thêmskipHolding, appendskipHoldingvào cuối cùng saupaymentType- Ví dụ:
orderId|referenceId|amount|currency|createdAt|branchId|businessUnitId|sellerMerchantId|paymentType|skipHolding
- Ví dụ:
- Nếu request
initPaymentSessioncó thêmmaxVpointAmount, append vào cuối cùng sauskipHolding(nếu có)- Ví dụ:
orderId|referenceId|amount|currency|createdAt|branchId|businessUnitId|sellerMerchantId|paymentType|skipHolding|maxVpointAmount - Nếu không có
skipHolding:orderId|referenceId|amount|currency|createdAt|branchId|businessUnitId|sellerMerchantId|paymentType|maxVpointAmount
- Ví dụ:
Các tham số
| Tham số | Kiểu dữ liệu | Mô tả |
|---|---|---|
| orderId | string | ID của đơn hàng trong hệ thống Order Backend (unique identifier) |
| referenceId | string | ID tham chiếu thanh toán của đơn hàng |
| amount | number | Số tiền cần thanh toán (số nguyên, không có phần thập phân) |
| currency | string | Đơn vị tiền tệ theo chuẩn ISO 4217 (VD: VND, USD, EUR) |
| createdAt | number | Unix timestamp in milliseconds (UTC) - thời điểm tạo đơn hàng |
| branchId | string | (Optional) Mã chi nhánh. Nếu có truyền trong initPayment.orderInfo.branchId thì phải append vào chuỗi ký ở cuối sau createdAt. |
| businessUnitId | string | (Optional) Mã cơ sở kinh doanh. Nếu có truyền trong initPayment.orderInfo.businessUnitId thì phải append vào chuỗi ký sau branchId (nếu có). |
| sellerMerchantId | string | (Optional) Mã merchant của seller. Nếu có truyền trong initPayment.sellerMerchantId thì phải append vào chuỗi ký sau businessUnitId (nếu có) và trước paymentType. |
| paymentType | string | (Optional) "2D" hoặc "3D". Nếu có truyền trong initPayment.paymentType thì phải append vào cuối cùng của chuỗi ký (hoặc trước skipHolding nếu request có field này). |
| skipHolding | boolean | (Optional) Nếu có truyền trong initPayment.skipHolding thì append vào cuối cùng sau paymentType. |
| maxVpointAmount | number | (Optional) Số tiền tối đa (VND, minor unit) user được tiêu bằng VPoint. Nếu có truyền trong initPaymentSession.orderInfo.maxVpointAmount thì append vào cuối cùng sau skipHolding (hoặc sau paymentType nếu không có skipHolding). |
| secretKey | string | Key bí mật được cấp bởi Payment Service khi đăng ký tích hợp |
Sample Code để tạo SecureHash
JavaScript (Node.js / Browser) - sử dụng HMAC-SHA-256 (Web Crypto API)
Mô tả ngắn (tiếng Việt): sử dụng HMAC-SHA-256 với secretKey làm key bí mật để ký chuỗi dữ liệu raw (định dạng: orderId|referenceId|amount|currency|createdAt). Luôn thực hiện việc này ở backend (không được đưa secretKey ra frontend).
// Example HMAC-SHA-256 using Web Crypto API
async function getExpectedHash(data, hashKey) {
const encoder = new TextEncoder()
const keyData = encoder.encode(hashKey)
const message = encoder.encode(data)
// Import the key for HMAC-SHA256
const cryptoKey = await crypto.subtle.importKey(
'raw', // raw key material
keyData, // key bytes
{ name: 'HMAC', hash: 'SHA-256' },
false, // non-extractable
['sign'], // usage
)
// Compute HMAC-SHA256 signature
const signature = await crypto.subtle.sign('HMAC', cryptoKey, message)
// Convert signature to hex string
const hashArray = Array.from(new Uint8Array(signature))
const expectedHash = hashArray
.map(b => b.toString(16).padStart(2, '0'))
.join('')
console.log('data =', data)
console.log('expectedHash =', expectedHash)
return expectedHash
}
// Example test (không có branchId/businessUnitId)
getExpectedHash(
'OrderId202510091|Reference_202510091|100000|VND|1640995200000',
'sk_dev_f37eg0odr34qf16iufi96he41nfo',
).then(h => console.log('Result =', h))
// Example test (có branchId + businessUnitId)
getExpectedHash(
'OrderId202510091|Reference_202510091|100000|VND|1640995200000|BR_HN_001|BU_VINFAST_001',
'sk_dev_f37eg0odr34qf16iufi96he41nfo',
).then(h => console.log('Result with BU/Branch =', h))
// Example test (có branchId + businessUnitId + paymentType)
getExpectedHash(
'OrderId202510091|Reference_202510091|100000|VND|1640995200000|BR_HN_001|BU_VINFAST_001|SELLER_MERCHANT_001|3D',
'sk_dev_f37eg0odr34qf16iufi96he41nfo',
).then(h => console.log('Result with BU/Branch/sellerMerchantId/paymentType =', h))
// Example test (có maxVpointAmount — initPaymentSession)
getExpectedHash(
'OrderId202510091|Reference_202510091|100000|VND|1640995200000|BR_HN_001|BU_VINFAST_001|SELLER_MERCHANT_001|3D|300000',
'sk_dev_f37eg0odr34qf16iufi96he41nfo',
).then(h => console.log('Result with maxVpointAmount =', h))
Mini App sẽ sử dụng thông tin này (đặc biệt là secureHash) để gọi initPayment().
Lưu ý quan trọng
secretKeylà key bí mật được cấp bởi Payment Service khi đăng ký tích hợp thanh toán- Cần bảo mật
secretKeyvà KHÔNG BAO GIỜ public ra ngoài hoặc lưu ở frontend secretKeychỉ được sử dụng ở Order Backend (server-side)
- Thứ tự các trường trong raw data string phải chính xác:
orderId|referenceId|amount|currency|createdAt amountphải là số nguyên (VD: 1699000000 VND, không phải 1699000000.00)createdAtphải là Unix timestamp in milliseconds (UTC)- Không được có khoảng trắng thừa trong raw data string
- Sử dụng ký tự
|(pipe) để phân tách các trường
- Test kỹ việc generate
secureHashvới nhiều test case khác nhau - Verify rằng
secureHashđược tạo ra giống nhau cho cùng một bộ dữ liệu - Log lại raw data string để debug khi cần thiết (nhưng không log
secretKey)
Liên kết liên quan
- Tích hợp thanh toán - Luồng thanh toán tổng quan
- Xử lý kết quả thanh toán (IPN) - Nhận và xử lý IPN message