Đối soát giao dịch

Hướng dẫn đối soát giao dịch giữa hệ thống của bạn và SePay để đảm bảo không bỏ sót giao dịch nào.


Tại sao cần đối soát giao dịch?

Khi tích hợp SePay Webhooks, hệ thống của bạn sẽ nhận giao dịch theo thời gian thực. Tuy nhiên, trong một số trường hợp webhook có thể không đến được hệ thống của bạn:

  • Server của bạn tạm thời không hoạt động (downtime, deploy, restart)
  • Lỗi mạng giữa SePay và server của bạn
  • Webhook bị timeout (server xử lý quá lâu)
  • Đã vượt quá số lần retry tối đa (7 lần)

Để đảm bảo không bỏ sót giao dịch nào, bạn cần thực hiện đối soát định kỳ bằng cách gọi API Giao dịch và so khớp với dữ liệu trong database của bạn.


Quy trình đối soát

1. Lấy danh sách giao dịch từ SePay

Gọi API lấy danh sách giao dịch trong khoảng thời gian cần đối soát:

GET
https://my.sepay.vn/userapi/transactions/list
transaction_date_minstring
Thời gian bắt đầu, định dạng YYYY-MM-DD HH:mm:ss
transaction_date_maxstring
Thời gian kết thúc, định dạng YYYY-MM-DD HH:mm:ss
account_numberstring
Lọc theo số tài khoản ngân hàng (không bắt buộc)
limitinteger
Số lượng giao dịch tối đa trả về (mặc định: 5000)
  • Ví dụ: Lấy tất cả giao dịch trong ngày 01/03/2026
cURL
1
2
3
curl -X GET "https://my.sepay.vn/userapi/transactions/list?transaction_date_min=2026-03-01%2000:00:00&transaction_date_max=2026-03-01%2023:59:59" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN"

2. So khớp với database của bạn

So sánh danh sách giao dịch từ SePay với các giao dịch đã lưu trong database. Sử dụng trường id (ID giao dịch trên SePay) để xác định giao dịch nào bị thiếu.

3. Bổ sung giao dịch thiếu

Với những giao dịch có trên SePay nhưng không có trong database, tiến hành lưu bổ sung và xử lý logic nghiệp vụ tương ứng (ví dụ: cập nhật trạng thái đơn hàng).


Code mẫu đối soát

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?php
$sepayToken = 'YOUR_API_TOKEN';
 
// Ket noi database
$conn = new mysqli('localhost', 'webhooks_receiver', 'EL2vKpfpDLsz', 'webhooks_receiver');
if ($conn->connect_error) {
die('MySQL connection failed: ' . $conn->connect_error);
}
 
// Lay giao dich tu SePay trong 24h qua
$dateMin = date('Y-m-d H:i:s', strtotime('-24 hours'));
$dateMax = date('Y-m-d H:i:s');
 
$url = 'https://my.sepay.vn/userapi/transactions/list?'
. http_build_query([
'transaction_date_min' => $dateMin,
'transaction_date_max' => $dateMax
]);
 
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $sepayToken
]);
$response = curl_exec($ch);
curl_close($ch);
 
$data = json_decode($response, true);
$sepayTransactions = $data['transactions'] ?? [];
echo count($sepayTransactions) . " giao dich tu SePay\n";
 
// Lay danh sach reference_number da luu
$result = $conn->query(
"SELECT reference_number FROM tb_transactions WHERE created_at >= '{$dateMin}'"
);
$existingRefs = [];
while ($row = $result->fetch_assoc()) {
$existingRefs[$row['reference_number']] = true;
}
 
// Tim va bo sung giao dich thieu
$missingCount = 0;
foreach ($sepayTransactions as $tx) {
if (!isset($existingRefs[$tx['reference_number']])) {
echo "Thieu giao dich: ID={$tx['id']}, so tien={$tx['amount_in']}, ref={$tx['reference_number']}\n";
 
$stmt = $conn->prepare(
'INSERT INTO tb_transactions
(gateway, transaction_date, account_number, sub_account,
amount_in, amount_out, accumulated, code,
transaction_content, reference_number, body)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
);
$stmt->bind_param('ssssdddssss',
$tx['bank_brand_name'],
$tx['transaction_date'],
$tx['bank_account_id'],
$tx['bank_sub_acc_id'],
$tx['amount_in'],
$tx['amount_out'],
$tx['accumulated'],
$tx['code'],
$tx['transaction_content'],
$tx['reference_number'],
$tx['description']
);
$stmt->execute();
$missingCount++;
}
}
 
echo "Doi soat xong. Bo sung {$missingCount} giao dich.\n";
$conn->close();
?>
Mẹo

Thiết lập cron job hoặc scheduled task để chạy đối soát tự động, ví dụ mỗi giờ hoặc mỗi ngày.

Bash
1
2
# Chay doi soat moi gio (crontab)
0 * * * * node /path/to/reconcile.js >> /var/log/reconcile.log 2>&1

Chiến lược đối soát

Đối soát theo khoảng thời gian

Phù hợp cho đối soát định kỳ (mỗi giờ, mỗi ngày). Sử dụng transaction_date_mintransaction_date_max để lấy giao dịch trong khoảng thời gian cố định.

Đối soát theo since_id

Phù hợp cho đối soát liên tục. Lưu lại ID giao dịch cuối cùng đã xử lý, sau đó dùng tham số since_id để chỉ lấy các giao dịch mới hơn.

cURL
1
2
3
curl -X GET "https://my.sepay.vn/userapi/transactions/list?since_id=92704" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN"
Lưu ý
  • Giới hạn tốc độ: API Giao dịch giới hạn 3 request/giây. Nếu vượt quá, bạn sẽ nhận HTTP 429.
  • Chống trùng lặp: Luôn kiểm tra id hoặc reference_number trước khi lưu giao dịch bổ sung để tránh trùng lặp.
  • Giới hạn kết quả: Mặc định API trả về tối đa 5000 giao dịch. Nếu có nhiều hơn, hãy chia nhỏ khoảng thời gian đối soát.