Tổng quan API hoàn tiền

Hướng dẫn tạo lệnh hoàn tiền và theo dõi trạng thái bằng SePay API, kèm quy tắc chống trùng và hạn mức hoàn của giao dịch gốc.

||

Gửi một lệnh hoàn tiền, SePay chuyển sang ngân hàng và trả về refund_id để bạn theo dõi trạng thái tới khi có kết quả cuối cùng.

Hiện chỉ hỗ trợ VietinBank doanh nghiệp

Hoàn tiền hiện chỉ áp dụng cho tài khoản VietinBank doanh nghiệp. Tài khoản thuộc ngân hàng khác nhận 422 unsupported_bank, tài khoản chưa được bật nhận 422 not_available. Sandbox không áp dụng khóa này, nên lệnh hoàn chạy được trên Sandbox vẫn có thể trả 422 not_available khi lên thật.

Bắt buộc gửi X-Idempotency-Key

Mỗi lệnh hoàn phải kèm header X-Idempotency-Key do bạn tự sinh. Thiếu header này SePay trả 422 idempotency_key_required thay vì thực hiện lệnh.

Lý do: nếu request timeout và bạn gửi lại, khóa chống trùng cho phép SePay trả về đúng bản ghi cũ. Không có khóa, lần gửi lại là một lệnh chi tiền thứ hai, và hạn mức hoàn chỉ chặn trường hợp hoàn toàn bộ, không chặn hoàn một phần.

Dùng lại khóa cũ nhưng đổi nội dung request thì SePay trả 409 idempotency_key_reused. Mỗi lệnh hoàn khác nhau cần một khóa khác nhau.

Quy tắc số tiền

Bạn gửiSePay hoàn
Bỏ trống refund_amountToàn bộ phần còn lại của giao dịch gốc
refund_amount nhỏ hơn phần còn lạiĐúng số tiền đó, phần còn lại vẫn hoàn tiếp được
refund_amount lớn hơn phần còn lạiTừ chối, 422 kèm lỗi trên trường refund_amount
Giao dịch gốc đã hoàn hếtTừ chối, 422 already_refunded

Hạn mức tính trên giao dịch thu hộ gốc

Hạn mức hoàn không tính trên tổng tiền của đơn hàng, mà trên từng giao dịch thu hộ đã vào tài khoản. Tổng các lệnh hoàn của một giao dịch không bao giờ vượt quá số tiền chính giao dịch đó thu được. Lệnh đang pending cũng chiếm hạn mức, nên một lệnh chưa có kết quả không mở đường cho lệnh thứ hai vượt số tiền đã thu.

Điều này quyết định cách endpoint theo đơn hàng hoạt động:

Đơn hàng cóBỏ trống transaction_idTruyền transaction_id
0 giao dịch thu hộ422 no_original_transaction422 no_original_transaction
Đúng 1 giao dịchSePay tự chọn giao dịch đó, hạn mức bằng số tiền của nóChọn đúng giao dịch được chỉ định
2 giao dịch trở lên422 transaction_requiredChọn đúng giao dịch được chỉ định, hạn mức bằng số tiền của riêng nó

Ví dụ một đơn hàng 500.000đ được khách trả làm hai đợt 300.000đ và 200.000đ: đơn có hai giao dịch thu hộ, nên gọi hoàn theo đơn hàng mà không truyền transaction_id sẽ nhận 422 transaction_required. Chỉ định đợt 300.000đ thì hoàn được tối đa 300.000đ cho đợt đó, muốn hoàn nốt phần còn lại phải gửi lệnh thứ hai chỉ định đợt 200.000đ.

Không có cách hoàn cả đơn hàng bằng một lệnh

Với đơn hàng nhiều đợt thu, mỗi đợt là một hạn mức riêng và cần một lệnh hoàn riêng, kèm khóa chống trùng riêng. Đừng gửi refund_amount bằng tổng paid_amount của đơn hàng, lệnh đó sẽ bị từ chối vì vượt hạn mức của giao dịch được chọn.

Hoàn theo đơn hàng hay theo giao dịch

Hai endpoint chạy qua cùng một lớp xử lý và cùng một hạn mức, chỉ khác ở cách bạn chỉ ra khoản thu cần hoàn:

Hoàn theo đơn hàngHoàn theo giao dịch
Định danh bằngUUID đơn hàngID giao dịch thu hộ
Chọn giao dịchTự động khi đơn có đúng 1 giao dịch, ngoài ra phải truyền transaction_idĐã nằm sẵn trong đường dẫn
Dùng được khi khoản thu không có đơn hàngKhông
order_type trong responseva_orderva_order nếu khoản thu thuộc một đơn hàng VA, null nếu không thuộc đơn hàng nào
Cập nhật refund_status của đơn hàngCó, khi khoản thu thuộc một đơn hàng
Hạn mứcSố tiền của giao dịch được chọnSố tiền của chính giao dịch đó

Vì hạn mức gắn với giao dịch gốc chứ không gắn với đường gọi, hoàn một khoản thu qua endpoint giao dịch rồi gọi tiếp endpoint đơn hàng cho cùng khoản đó sẽ thấy phần còn lại đã bị trừ. Không có cách nào chi tiền hai lần bằng cách đổi endpoint.

Hoàn tiền không đổi trạng thái đơn hàng

Đơn hàng giữ nguyên statuspaid_amount sau khi hoàn. Hoàn tiền là bản ghi riêng, tra cứu qua Danh sách hoàn tiền.

Trạng thái hoàn tiền

Trạng thái lệnh hoàn tiền
Rendering diagram...
Trạng tháiNghĩa
pendingĐang xử lý, chưa có kết quả từ ngân hàng
succeededNgân hàng xác nhận tiền đã chuyển đi
failedNgân hàng xác nhận tiền không chuyển đi
rejectedNgân hàng từ chối lệnh ngay từ đầu

Ba trạng thái cuối là kết thúc và không đổi nữa. Một lệnh đã succeeded không bao giờ quay lại pending, nên bạn có thể ghi nhận kết quả ngay khi đọc được.

Không cần tự hỏi lại ngân hàng

SePay tự cập nhật trạng thái các lệnh đang pending. Bạn chỉ cần đọc Chi tiết hoàn tiền hoặc lọc danh sách theo status.

Các endpoint

POST
https://userapi.sepay.vn/v2/bank-accounts/{ba_xid}/orders/{order_xid}/refund
POST
https://userapi.sepay.vn/v2/transactions/{transaction_id}/refund
GET
https://userapi.sepay.vn/v2/bank-accounts/{ba_xid}/refunds
GET
https://userapi.sepay.vn/v2/bank-accounts/{ba_xid}/refunds/{refund_id}

Các bước tích hợp

Chuẩn bị

  • Tạo API Token
  • Tài khoản ngân hàng đã liên kết thuộc ngân hàng có hỗ trợ hoàn tiền, và đã được SePay bật hoàn tiền cho chính tài khoản đó. Chưa bật thì lệnh tạo trả 422 not_available
  • Lấy UUID tài khoản từ API Tài khoản ngân hàng

Sinh khóa chống trùng

Khóa là chuỗi của riêng bạn, duy nhất cho mỗi lệnh hoàn. Ghép từ mã đơn hàng và số thứ tự lần hoàn là đủ, miễn là lần gửi lại của cùng một lệnh dùng lại đúng chuỗi đó.

Gửi lệnh hoàn tiền

cURL
1
2
3
4
5
curl -X POST "https://userapi.sepay.vn/v2/bank-accounts/{ba_uuid}/orders/{order_uuid}/refund" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "X-Idempotency-Key: refund-DH20250001-01" \
-d '{"refund_amount": 50000}'
Response 200
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"status": "success",
"data": {
"refund_id": "RF-7QK3-M8PZ-4VNW",
"order_type": "va_order",
"order_id": "b2c3d4e5-f6a7-8901-bcde-f12345678902",
"order_code": "DH20250001",
"xid": "e5f6a7b8-c9d0-1234-ef56-789012345abc",
"transaction_id": "6398452",
"original_reference": "164T24200GKAJ7BY",
"amount": 50000,
"currency": "VND",
"scope": "partial",
"status": "pending",
"created_at": "2026-08-28 14:05:00"
}
}

Lưu refund_id lại. Đây là mã tra cứu lệnh hoàn, và cũng là mã bạn đưa cho SePay khi cần hỗ trợ.

Theo dõi kết quả

cURL
1
2
curl -X GET "https://userapi.sepay.vn/v2/bank-accounts/{ba_uuid}/refunds/RF-7QK3-M8PZ-4VNW" \
-H "Authorization: Bearer YOUR_API_TOKEN"

Đọc status. Còn pending thì kiểm tra lại sau; succeeded, failed hoặc rejected là kết quả cuối.


Xem thêm