Snapshot trạng thái thanh toán
API snapshot cho phép các bên external (không phải Payment Hub) báo cáo trạng thái thanh toán hoặc refund (thành công hoặc thất bại) để Payment Hub ghi nhận (snapshot) lại trạng thái trên hệ thống của mình (không gọi sang payment provider). Đây là tính năng tùy chọn, không phải tất cả các bên đều cần sử dụng.
Tổng quan
Payment Hub hỗ trợ 2 loại snapshot:
- Snapshot Transaction: Báo cáo trạng thái thanh toán (COMPLETED/FAILED)
- Snapshot Refund: Báo cáo trạng thái refund (COMPLETED/FAILED)
Cả 2 API đều:
- Ghi nhận (snapshot) trạng thái payment/refund trực tiếp trên transaction/refund tại Payment Hub (theo dữ liệu external gửi lên)
- Không thực hiện bất kỳ nghiệp vụ nào với payment provider (không capture/refund/... trên provider; chỉ xử lý nội bộ trong Payment Hub)
- Verify
secureHashvà validate tính đúng đắn của dữ liệu (merchant, user, payment method, provider, transaction, v.v.) theo đúng rule của từng API - Routing qua MuleSoft (giống các API khác)
1. Snapshot Transaction Status
Snapshot trạng thái thanh toán từ external system vào Payment Hub.
Endpoint: POST /api/payments/v1/transactions/snapshot
Headers:
| Header | Required | Mô tả |
|---|---|---|
X-Payment-API-Key | ✅ | API key của merchant/terminal |
X-Request-ID | ✅ | Unique request ID (UUID) để đảm bảo idempotency và request tracing |
X-Timestamp | ✅ | Unix timestamp in seconds |
X-MiniApp-User-ID | ✅ | ID mà VSF cung cấp cho user MiniApp để SuperApp hiển thị & quản trị. PnL tạo transaction cho user nào thì truyền đúng miniAppUserId của user đó lên. Payment sẽ enrich chính xác thông tin v-app-user-id từ IAM. Nếu không enrich được sẽ trả về lỗi |
X-Auth-Audience | ✅ | Audience identifier (thường là v-app) |
client_id | ✅ | Client ID để authenticate với MuleSoft |
client_secret | ✅ | Client secret để authenticate với MuleSoft |
Content-Type | ✅ | application/json |
- Bắt buộc gửi header
X-Request-ID(UUID) cho mỗi request để đảm bảo idempotency và request tracing - Bắt buộc gửi header
X-Timestamp(Unix timestamp in seconds) cho validation và security - API có cơ chế kiểm tra duplicate request dựa trên
X-Request-ID
Request Body:
Request body bao gồm các thông tin cơ bản tương tự như initPayment (trừ paymentType và skipHolding):
{
"orderId": "ORDER_001",
"referenceId": "REF_123456",
"amount": 300000.0,
"currency": "VND",
"description": "Payment for order: OrderId_1761297780725",
"providerId": "067d848c-2fc8-4565-985e-f18b78fb9c7e",
"paymentMethodCode": "INTERNATIONAL_CARD",
"status": "COMPLETED",
"processedAt": 1705320600000,
"providerTransactionId": "provider_txn_001",
"businessUnitId": "BU_VINFAST_001",
"branchId": "BR_HN_001",
"orderInfo": {
"customerName": "TestCustomer",
"customerEmail": "[email protected]",
"customerPhone": "0123456789",
"orderCreatedAt": 1761297780725,
"items": [
{
"name": "Test Item",
"sku": "SKU_001",
"quantity": 1,
"unitPrice": 100000.0,
"description": "Description for Test Item",
"categoryCode": "CAT_ELECTRONICS",
"categoryName": "Electronics"
}
]
},
"secureHash": "a1b2c3d4e5f6..."
}
| Field | Type | Required | Mô tả |
|---|---|---|---|
orderId | string | ✅ | ID đơn hàng |
referenceId | string | ✅ | Mã tham chiếu |
amount | number | ✅ | Số tiền thanh toán (integer hoặc float, minor unit) |
currency | string | ✅ | Đơn vị tiền tệ (VD: VND, USD) |
description | string | ✅ | Mô tả đơn hàng |
providerId | string | ✅ | ID payment provider |
paymentMethodCode | string | ✅ | Mã phương thức thanh toán |
status | string | ✅ | Trạng thái thanh toán: COMPLETED hoặc FAILED |
processedAt | number | ✅ | Unix timestamp in milliseconds (UTC) - thời điểm xử lý thanh toán |
providerTransactionId | string | ❌ | ID giao dịch từ payment provider (nếu có) |
businessUnitId | string | ❌ | Mã cơ sở kinh doanh (Business Unit ID) |
branchId | string | ❌ | Mã chi nhánh |
orderInfo | object | ✅ | Thông tin đơn hàng chi tiết (CustomerOrderInfo) |
orderInfo.customerName | string | ❌ | Tên khách hàng |
orderInfo.customerEmail | string | ❌ | Email khách hàng |
orderInfo.customerPhone | string | ❌ | Số điện thoại khách hàng |
orderInfo.orderCreatedAt | number | ✅ | Unix timestamp (milliseconds) khi đơn hàng được tạo |
orderInfo.items | array | ❌ | Danh sách sản phẩm/dịch vụ trong đơn hàng |
orderInfo.items[].name | string | ❌ | Tên sản phẩm |
orderInfo.items[].sku | string | ❌ | Mã SKU sản phẩm |
orderInfo.items[].quantity | number | ❌ | Số lượng |
orderInfo.items[].unitPrice | number | ❌ | Giá đơn vị |
orderInfo.items[].description | string | ❌ | Mô tả sản phẩm |
orderInfo.items[].categoryCode | string | ❌ | Mã danh mục/ngành hàng (optional) |
orderInfo.items[].categoryName | string | ❌ | Tên danh mục/ngành hàng (optional) |
secureHash | string | ✅ | Chữ ký SHA-256 để verify tính toàn vẹn dữ liệu |
Lưu ý:
- Bắt buộc phải truyền cả
orderIdvàreferenceId(cặp định danh transaction ở Payment Hub) - Payment Hub sẽ kiểm tra transaction theo cặp
orderId+referenceId:- Nếu đã tồn tại: không snapshot và trả lỗi
409(4091 Duplicate referenceId) - Nếu chưa tồn tại: tạo transaction và ghi nhận (snapshot) trạng thái theo dữ liệu request
- Nếu đã tồn tại: không snapshot và trả lỗi
statuschỉ chấp nhận giá trịCOMPLETEDhoặcFAILED- Nếu
status = FAILED, bắt buộc phải truyềnerrorCodevàerrorMessagetrong request body
SecureHash Formula:
Công thức tương tự như initPayment nhưng không có paymentType và skipHolding, và có thêm status và processedAt:
HMAC-SHA-256(secretKey, orderId + '|' + referenceId + '|' + amount + '|' + currency + '|' + orderInfo.orderCreatedAt + '|' + branchId + '|' + businessUnitId + '|' + status + '|' + processedAt + '|' + timestamp)
Lưu ý:
timestamptrong secureHash là giá trịX-Timestampheader (Unix timestamp in seconds), không phảiprocessedAt- Lưu ý Amount Hash:
amountđược lấy phần nguyên (integer part string) để hash. - Thứ tự append:
orderId|referenceId|amount|currency|orderCreatedAt→branchId(nếu có) →businessUnitId(nếu có) →status→processedAt→timestamp - KHÔNG có
paymentTypevàskipHoldingtrong secureHash
Success Response (200):
{
"code": 0,
"message": "Thành công"
}
Error Responses:
| HTTP Status | Code | Message | Mô tả |
|---|---|---|---|
| 400 | 4001 | Invalid request | Request không hợp lệ |
| 400 | 4016 | Invalid status | Status không hợp lệ (chỉ chấp nhận COMPLETED hoặc FAILED) |
| 400 | 4017 | Missing error information | Thiếu errorCode/errorMessage khi status = FAILED |
| 409 | 4091 | Duplicate referenceId | Transaction đã tồn tại theo cặp orderId + referenceId |
| 401 | 4100 | Invalid API key | API Key không hợp lệ |
| 403 | 4200 | Resource does not belong to this user | Giao dịch không thuộc merchant/terminal |
| 404 | 4302 | User not found | Không thể enrich thông tin từ X-MiniApp-User-ID (không gọi được IAM hoặc user không tồn tại) |
| 500 | 5000 | Internal server error | Lỗi hệ thống |
Example (status COMPLETED):
curl -X 'POST' \
'https://api.example.com/api/payments/v1/transactions/snapshot' \
-H 'X-Payment-API-Key: <your_api_key>' \
-H 'X-Request-ID: 550e8400-e29b-41d4-a716-446655440000' \
-H 'X-Timestamp: 1760677974' \
-H 'X-MiniApp-User-ID: 109306626' \
-H 'X-Auth-Audience: v-app' \
-H 'client_id: YOUR_CLIENT_ID' \
-H 'client_secret: YOUR_CLIENT_SECRET' \
-H 'Content-Type: application/json' \
-d '{
"orderId": "ORDER_001",
"referenceId": "REF_123456",
"amount": 300000.0,
"currency": "VND",
"description": "Payment for order: OrderId_1761297780725",
"providerId": "067d848c-2fc8-4565-985e-f18b78fb9c7e",
"paymentMethodCode": "INTERNATIONAL_CARD",
"status": "COMPLETED",
"processedAt": 1705320600000,
"providerTransactionId": "provider_txn_001",
"businessUnitId": "BU_VINFAST_001",
"branchId": "BR_HN_001",
"orderInfo": {
"customerName": "TestCustomer",
"customerEmail": "[email protected]",
"customerPhone": "0123456789",
"orderCreatedAt": 1761297780725,
"items": [
{
"name": "Test Item",
"sku": "SKU_001",
"quantity": 1,
"unitPrice": 100000.0,
"description": "Description for Test Item",
"categoryCode": "CAT_ELECTRONICS",
"categoryName": "Electronics"
}
]
},
"secureHash": "a1b2c3d4e5f6..."
}'
Example (status FAILED):
curl -X 'POST' \
'https://api.example.com/api/payments/v1/transactions/snapshot' \
-H 'X-Payment-API-Key: <your_api_key>' \
-H 'X-Request-ID: 550e8400-e29b-41d4-a716-446655440001' \
-H 'X-Timestamp: 1760677974' \
-H 'X-MiniApp-User-ID: 109306626' \
-H 'X-Auth-Audience: v-app' \
-H 'client_id: YOUR_CLIENT_ID' \
-H 'client_secret: YOUR_CLIENT_SECRET' \
-H 'Content-Type: application/json' \
-d '{
"orderId": "ORDER_001",
"referenceId": "REF_123456",
"amount": 300000.0,
"currency": "VND",
"description": "Payment for order: OrderId_1761297780725",
"providerId": "067d848c-2fc8-4565-985e-f18b78fb9c7e",
"paymentMethodCode": "INTERNATIONAL_CARD",
"status": "FAILED",
"processedAt": 1705320600000,
"providerTransactionId": "provider_txn_001",
"businessUnitId": "BU_VINFAST_001",
"branchId": "BR_HN_001",
"orderInfo": {
"customerName": "TestCustomer",
"customerEmail": "[email protected]",
"customerPhone": "0123456789",
"orderCreatedAt": 1761297780725,
"items": [
{
"name": "Test Item",
"sku": "SKU_001",
"quantity": 1,
"unitPrice": 100000.0,
"description": "Description for Test Item",
"categoryCode": "CAT_ELECTRONICS",
"categoryName": "Electronics"
}
]
},
"errorCode": "PAYMENT_FAILED",
"errorMessage": "Insufficient funds",
"secureHash": "a1b2c3d4e5f6..."
}'
JavaScript Example:
// Example: Snapshot transaction status
const snapshotTransactionStatus = async (paymentData, secretKey, paymentApiKey, userId, clientId, clientSecret) => {
// Generate timestamp (Unix timestamp in seconds)
const timestamp = Math.floor(Date.now() / 1000)
// Generate secureHash (tương tự initPayment nhưng không có paymentType và skipHolding)
// Base: orderId|referenceId|amount|currency|orderCreatedAt
// Append branchId if exists, then businessUnitId if exists, then status, then processedAt, then timestamp
let rawDataString = `${paymentData.orderId}|${paymentData.referenceId}|${paymentData.amount}|${paymentData.currency}|${paymentData.orderInfo.orderCreatedAt}`
if (paymentData.branchId) {
rawDataString += `|${paymentData.branchId}`
}
if (paymentData.businessUnitId) {
rawDataString += `|${paymentData.businessUnitId}`
}
rawDataString += `|${paymentData.status}|${paymentData.processedAt}|${timestamp}`
const secureHash = await generateSecureHash(rawDataString, secretKey)
// Generate unique request ID for idempotency
const requestId = crypto.randomUUID()
const requestBody = {
...paymentData,
secureHash,
}
const response = await fetch('{MULE_INTERNAL_BASE_URL}/payment-hub/api/payments/v1/transactions/snapshot', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Payment-API-Key': paymentApiKey,
'X-Request-ID': requestId,
'X-Timestamp': timestamp.toString(),
'X-MiniApp-User-ID': userId,
'X-Auth-Audience': 'v-app',
'client_id': clientId,
'client_secret': clientSecret,
},
body: JSON.stringify(requestBody),
})
return response.json()
}
// Helper function to generate secureHash (HMAC-SHA-256)
async function generateSecureHash(data, hashKey) {
const encoder = new TextEncoder()
const keyData = encoder.encode(hashKey)
const cryptoKey = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign'],
)
const message = encoder.encode(data)
const signature = await crypto.subtle.sign('HMAC', cryptoKey, message)
const hashArray = Array.from(new Uint8Array(signature))
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
}
Lưu ý về logic xử lý:
- Payment Hub sẽ:
- Verify
secureHashtrước khi xử lý - Enrich thông tin v-app-user-id từ IAM dựa trên
X-MiniApp-User-IDheader - Kiểm tra transaction theo cặp
orderId+referenceId:- Nếu đã tồn tại: trả
409/4091và không snapshot - Nếu chưa tồn tại: tạo transaction và ghi nhận (snapshot) trạng thái theo dữ liệu request
- Nếu đã tồn tại: trả
- Update các field liên quan theo thiết kế nội bộ (status, processedAt, providerTransactionId, error info nếu có, v.v.)
- Không gọi sang payment provider, không thực hiện capture/refund/... trên provider
- Trả về kết quả đồng bộ cho client
- Verify
2. Snapshot Refund Status
Snapshot trạng thái refund từ external system vào Payment Hub.
Endpoint: POST /api/payments/v1/refunds/snapshot
Headers:
| Header | Required | Mô tả |
|---|---|---|
X-Payment-API-Key | ✅ | API key của merchant/terminal |
X-Request-ID | ✅ | Unique request ID (UUID) để đảm bảo idempotency và request tracing |
X-Timestamp | ✅ | Unix timestamp in seconds |
Content-Type | ✅ | application/json |
- Bắt buộc gửi header
X-Request-ID(UUID) cho mỗi request để đảm bảo idempotency và request tracing - Bắt buộc gửi header
X-Timestamp(Unix timestamp in seconds) cho validation và security - API có cơ chế kiểm tra duplicate request dựa trên
X-Request-ID
Request Body:
{
"transactionId": "txn_123456789",
"amount": 100000,
"currency": "VND",
"refundReferenceId": "refund_001",
"refundType": "full",
"status": "COMPLETED",
"processedAt": 1705320600000,
"secureHash": "a1b2c3d4e5f6..."
}
| Field | Type | Required | Mô tả |
|---|---|---|---|
transactionId | string | ✅ | ID của giao dịch cần refund |
amount | number | ✅ | Số tiền refund (integer minor unit) |
currency | string | ✅ | Đơn vị tiền tệ (VD: VND, USD) |
refundReferenceId | string | ✅ | Reference ID cho refund |
refundType | string | ✅ | Loại refund: full hoặc partial |
status | string | ✅ | Trạng thái refund: COMPLETED hoặc FAILED |
processedAt | number | ✅ | Unix timestamp in milliseconds (UTC) - thời điểm xử lý refund |
errorCode | string | ❌ | Mã lỗi (bắt buộc nếu status = FAILED) |
errorMessage | string | ❌ | Thông báo lỗi (bắt buộc nếu status = FAILED) |
secureHash | string | ✅ | Chữ ký SHA-256 để verify tính toàn vẹn dữ liệu |
Lưu ý:
- Bắt buộc phải truyền
transactionId(ID của transaction gốc cần refund) statuschỉ chấp nhận giá trịCOMPLETEDhoặcFAILED- Nếu
status = FAILED, bắt buộc phải truyềnerrorCodevàerrorMessage
SecureHash Formula:
HMAC-SHA-256(secretKey, transactionId + '|' + amount + '|' + currency + '|' + refundReferenceId + '|' + refundType + '|' + status + '|' + processedAt + '|' + timestamp)
Lưu ý:
timestamptrong secureHash là giá trịX-Timestampheader (Unix timestamp in seconds), không phảiprocessedAt- Lưu ý Amount Hash:
amountđược lấy phần nguyên (integer part string) để hash. - Thứ tự:
transactionId|amount|currency|refundReferenceId|refundType|status|processedAt|timestamp
Success Response (200):
{
"code": 0,
"message": "Thành công"
}
Error Responses:
| HTTP Status | Code | Message | Mô tả |
|---|---|---|---|
| 400 | 4001 | Invalid request | Request không hợp lệ |
| 400 | 4016 | Invalid status | Status không hợp lệ (chỉ chấp nhận COMPLETED hoặc FAILED) |
| 400 | 4017 | Missing error information | Thiếu errorCode/errorMessage khi status = FAILED |
| 409 | 4092 | Duplicate refundReferenceId | Refund đã tồn tại theo transactionId + refundReferenceId |
| 401 | 4100 | Invalid API key | API Key không hợp lệ |
| 403 | 4200 | Resource does not belong to this user | Refund không thuộc merchant/terminal |
| 404 | 4301 | Transaction not found | Không tìm thấy giao dịch |
| 500 | 5000 | Internal server error | Lỗi hệ thống |
Example (status COMPLETED):
curl -X 'POST' \
'https://api.example.com/api/payments/v1/refunds/snapshot' \
-H 'X-Payment-API-Key: <your_api_key>' \
-H 'X-Request-ID: 550e8400-e29b-41d4-a716-446655440000' \
-H 'X-Timestamp: 1760677974' \
-H 'Content-Type: application/json' \
-d '{
"transactionId": "txn_123456789",
"amount": 100000,
"currency": "VND",
"refundReferenceId": "refund_001",
"refundType": "full",
"status": "COMPLETED",
"processedAt": 1705320600000,
"secureHash": "a1b2c3d4e5f6..."
}'
Example (status FAILED):
curl -X 'POST' \
'https://api.example.com/api/payments/v1/refunds/snapshot' \
-H 'X-Payment-API-Key: <your_api_key>' \
-H 'X-Request-ID: 550e8400-e29b-41d4-a716-446655440001' \
-H 'X-Timestamp: 1760677974' \
-H 'Content-Type: application/json' \
-d '{
"transactionId": "txn_123456789",
"amount": 100000,
"currency": "VND",
"refundReferenceId": "refund_001",
"refundType": "full",
"status": "FAILED",
"processedAt": 1705320600000,
"errorCode": "REFUND_FAILED",
"errorMessage": "Insufficient balance",
"secureHash": "a1b2c3d4e5f6..."
}'
JavaScript Example:
// Example: Snapshot refund status
const snapshotRefundStatus = async (refundData, secretKey, paymentApiKey, userId) => {
// Generate timestamp (Unix timestamp in seconds)
const timestamp = Math.floor(Date.now() / 1000)
// Generate secureHash
// transactionId|amount|currency|refundReferenceId|refundType|status|processedAt|timestamp
const rawDataString = `${refundData.transactionId}|${refundData.amount}|${refundData.currency}|${refundData.refundReferenceId}|${refundData.refundType}|${refundData.status}|${refundData.processedAt}|${timestamp}`
const secureHash = await generateSecureHash(rawDataString, secretKey)
// Generate unique request ID for idempotency
const requestId = crypto.randomUUID()
const requestBody = {
...refundData,
secureHash,
}
const response = await fetch('/api/payments/v1/refunds/snapshot', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Payment-API-Key': paymentApiKey,
'X-Request-ID': requestId,
'X-Timestamp': timestamp.toString()
},
body: JSON.stringify(requestBody),
})
return response.json()
}
// Helper function to generate secureHash (HMAC-SHA-256)
async function generateSecureHash(data, hashKey) {
const encoder = new TextEncoder()
const keyData = encoder.encode(hashKey)
const cryptoKey = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign'],
)
const message = encoder.encode(data)
const signature = await crypto.subtle.sign('HMAC', cryptoKey, message)
const hashArray = Array.from(new Uint8Array(signature))
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
}
Lưu ý về logic xử lý:
- Payment Hub sẽ:
- Verify
secureHashtrước khi xử lý - Tìm transaction dựa trên
transactionId - Kiểm tra refund theo
transactionId+refundReferenceId:- Nếu đã tồn tại: trả
409/4092và không snapshot - Nếu chưa tồn tại: tạo refund và ghi nhận (snapshot) trạng thái theo dữ liệu request
- Nếu đã tồn tại: trả
- Không gọi sang payment provider, không thực hiện refund/void/... trên provider
- Trả về kết quả đồng bộ cho client
- Verify
Sinh SecureHash cho Snapshot APIs
Công thức và Format
Sử dụng HMAC-SHA-256 với format data tương ứng cho từng API:
| API | Raw Data String Format |
|---|---|
| Snapshot Transaction | orderId + '|' + referenceId + '|' + amount + '|' + currency + '|' + orderCreatedAt + '|' + branchId + '|' + businessUnitId + '|' + status + '|' + processedAt + '|' + timestampLưu ý: Không có paymentType và skipHolding trong secureHash |
| Snapshot Refund | transactionId + '|' + amount + '|' + currency + '|' + refundReferenceId + '|' + refundType + '|' + status + '|' + processedAt + '|' + timestamp |
Sample Code
async function getExpectedHash(data, hashKey) {
const encoder = new TextEncoder()
const keyData = encoder.encode(hashKey)
const cryptoKey = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign'],
)
const message = encoder.encode(data)
const signature = await crypto.subtle.sign('HMAC', cryptoKey, message)
const hashArray = Array.from(new Uint8Array(signature))
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
}
// Example for Snapshot Transaction
const snapshotTransactionData = {
orderId: 'ORDER_001',
referenceId: 'REF_123456',
amount: '300000',
currency: 'VND',
orderCreatedAt: '1761297780725',
branchId: 'BR_HN_001',
businessUnitId: 'BU_VINFAST_001',
status: 'COMPLETED',
processedAt: '1705320600000',
timestamp: '1760677974',
}
getExpectedHash(
`${snapshotTransactionData.orderId}|${snapshotTransactionData.referenceId}|${snapshotTransactionData.amount}|${snapshotTransactionData.currency}|${snapshotTransactionData.orderCreatedAt}|${snapshotTransactionData.branchId}|${snapshotTransactionData.businessUnitId}|${snapshotTransactionData.status}|${snapshotTransactionData.processedAt}|${snapshotTransactionData.timestamp}`,
'sk_dev_xx7ca9hvyneral068d06mr2l5tb3',
).then(h => console.log('Snapshot Transaction Hash =', h))
// Example for Snapshot Refund
const snapshotRefundData = {
transactionId: 'txn_123456789',
amount: '100000',
currency: 'VND',
refundReferenceId: 'refund_001',
refundType: 'full',
status: 'COMPLETED',
processedAt: '1705320600000',
timestamp: '1760677974',
}
getExpectedHash(
`${snapshotRefundData.transactionId}|${snapshotRefundData.amount}|${snapshotRefundData.currency}|${snapshotRefundData.refundReferenceId}|${snapshotRefundData.refundType}|${snapshotRefundData.status}|${snapshotRefundData.processedAt}|${snapshotRefundData.timestamp}`,
'sk_dev_xx7ca9hvyneral068d06mr2l5tb3',
).then(h => console.log('Snapshot Refund Hash =', h))
Lưu ý quan trọng
- Sử dụng HMAC-SHA-256 để tạo chữ ký số
- Format dữ liệu cần ký khác nhau cho mỗi loại snapshot:
- Snapshot Transaction:
orderId|referenceId|amount|currency|orderCreatedAt|branchId|businessUnitId|status|processedAt|timestamp- Lưu ý: Không có
paymentTypevàskipHoldingtrong secureHash
- Lưu ý: Không có
- Snapshot Refund:
transactionId|amount|currency|refundReferenceId|refundType|status|processedAt|timestamp
- Snapshot Transaction:
- Luôn giữ
secretKeyở backend, không được expose ra client - Kiểm tra kỹ format dữ liệu trước khi ký
- Lưu ý Amount Hash:
amountđược lấy phần nguyên (integer part string) để hash. timestamptrong secureHash là giá trịX-Timestampheader (Unix timestamp in seconds), không phảiprocessedAt
Routing qua Mule (Payment Hub)
Quan trọng: Các API snapshot được gọi trực tiếp từ Order Backend của MiniApp tới Payment Hub thông qua MuleSoft, không được gọi trực tiếp tới Payment Hub.
Internal (PnL)
Kết nối Internal dành cho các PnL trong Vingroup.
Base URL trên Mule (endpoint tới Payment Hub):
- DEV:
{MULE_INTERNAL_BASE_URL}/payment-hub/ - SIT:
{MULE_INTERNAL_BASE_URL}/sit/payment-hub/ - UAT:
{MULE_INTERNAL_BASE_URL}/uat/payment-hub/ - Production:
{MULE_INTERNAL_PROD_URL}/payment-hub/
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_id và client_secret). Với môi trường DEV/SIT/UAT dùng chung key.
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: Production và UAT.
Base URL trên Mule (endpoint tới Payment Hub):
- UAT:
https://test-api.vingroup.net:7445/payment-hub/ - Production:
https://api-cloud.vingroup.net/payment-hub/
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_idvàclient_secretriêng cho từng đối tác - Có whitelist và limit access theo endpoint
Quy tắc chung:
- Yêu cầu các header cơ bản:
X-Payment-API-Key,X-Request-ID,X-Timestamp(hoặc theo từng API doc). Thêm header để pass MuleSoft:client_id,client_secret, v.v. (với snapshot transaction). - Các request được forward qua Mule tới Payment Hub — đảm bảo token/client/config phù hợp với Mule khi gọi.
Liên kết liên quan
- Tích hợp thanh toán - Luồng thanh toán tổng quan từ V-App
- Quản lý giao dịch thanh toán - Các API cancel, refund, confirm
- Khởi tạo thanh toán từ Backend - API initPayment được gọi từ Backend qua Mule