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

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

  1. 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)
  2. Mini App: Gọi API tạo đơn hàng xuống Order Backend của Mini App
  3. Order Backend (Mini App):
    • Dựa vào secretKey được cấp từ Payment Service, sinh mã secureHash cho đơn hàng
    • Trả về orderId, referenceId, orderCreatedAt, amount (số tiền cần thanh toán) cùng secureHash (và các thông tin cần thiết) cho Mini App
  4. Mini App: Sử dụng thông tin nhận được (bao gồm secureHash) để gọi initPayment()

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ả branchIdbusinessUnitId:
    • 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 sau sellerMerchantId (nếu có), và sau businessUnitId/branchId nếu không có sellerMerchantId
    • Ví dụ: orderId|referenceId|amount|currency|createdAt|branchId|businessUnitId|sellerMerchantId|paymentType
  • Nếu request initPayment có thêm skipHolding, append skipHolding vào cuối cùng sau paymentType
    • Ví dụ: orderId|referenceId|amount|currency|createdAt|branchId|businessUnitId|sellerMerchantId|paymentType|skipHolding
  • Nếu request initPaymentSession có thêm maxVpointAmount, append vào cuối cùng sau skipHolding (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

Các tham số

Tham sốKiểu dữ liệuMô tả
orderIdstringID của đơn hàng trong hệ thống Order Backend (unique identifier)
referenceIdstringID tham chiếu thanh toán của đơn hàng
amountnumberSố tiền cần thanh toán (số nguyên, không có phần thập phân)
currencystringĐơn vị tiền tệ theo chuẩn ISO 4217 (VD: VND, USD, EUR)
createdAtnumberUnix timestamp in milliseconds (UTC) - thời điểm tạo đơn hàng
branchIdstring(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.
businessUnitIdstring(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ó).
sellerMerchantIdstring(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.
paymentTypestring(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).
skipHoldingboolean(Optional) Nếu có truyền trong initPayment.skipHolding thì append vào cuối cùng sau paymentType.
maxVpointAmountnumber(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).
secretKeystringKey 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

Bảo mật
  • secretKey là 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 secretKeyKHÔNG BAO GIỜ public ra ngoài hoặc lưu ở frontend
  • secretKey chỉ được sử dụng ở Order Backend (server-side)
Format dữ liệu
  • Thứ tự các trường trong raw data string phải chính xác: orderId|referenceId|amount|currency|createdAt
  • amount phải là số nguyên (VD: 1699000000 VND, không phải 1699000000.00)
  • createdAt phả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
Kiểm tra
  • Test kỹ việc generate secureHash vớ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