Tổng quan
Payment Gateway là cầu nối giữa ứng dụng của bạn và hệ thống ngân hàng / ví điện tử. Khi khách hàng thanh toán, dữ liệu đi qua gateway để xác thực, định tuyến và xác nhận giao dịch.
TConnect Payment Gateway hỗ trợ:
- Thẻ nội địa / quốc tế (Napas, Visa, Mastercard, JCB)
- Ví điện tử (MoMo, ZaloPay, VNPay)
- QR VietQR (chuyển khoản ngân hàng)
- Trả góp qua các ngân hàng đối tác
Một giải pháp API, tất cả phương thức — không cần tích hợp từng cổng riêng lẻ.
Kiến trúc tổng thể
[Frontend] → POST /create-order → [Backend của bạn]
│
POST /openapi/v1/payment/create
│
[TConnect API]
│
┌─────────────┴──────────────┐
[Ngân hàng] [Ví điện tử]
│
redirect_url / deeplink trả về
│
[Khách thanh toán]
│
IPN Callback → [Backend của bạn]
│
Cập nhật trạng thái đơn hàngBước 1: Xác thực và lấy Access Token
TConnect dùng OAuth2 với cặp client_id / client_secret. Token có hiệu lực 36.000 giây (10 giờ).
function login(base_url, username, password, client_id, client_secret):
payload = {
username: username,
password: password,
client_id: client_id,
client_secret: client_secret,
grant_type: "password"
}
POST {base_url}/openapi/v1/auth/login
body: payload
return response.access_token, response.refresh_tokenLưu ý: Cache
access_token— đừng gọi login trước mỗi request. Dùngrefresh_tokenđể gia hạn khi hết hạn.
Bước 2: Mã hóa payload với AES-256-CBC
Tất cả request body gửi lên TConnect đều phải mã hóa AES-256-CBC. IV (16 bytes) được prepend vào ciphertext, toàn bộ hex-encode.
function encrypt_payload(data, aes_key):
iv = generate random 16 bytes
plaintext = json_encode(data) as UTF-8 bytes
ciphertext = AES_256_CBC_encrypt(plaintext, key=aes_key, iv=iv)
return hex_encode(iv + ciphertext)
function decrypt_response(hex_data, aes_key):
raw = hex_decode(hex_data)
iv = first 16 bytes of raw
ciphertext = remaining bytes of raw
plaintext = AES_256_CBC_decrypt(ciphertext, key=aes_key, iv=iv)
return json_decode(plaintext)Bước 3: Tạo giao dịch thanh toán
function create_payment(base_url, partner_code, access_token, aes_key, order_data):
payload = {
order_id: order_data.id,
amount: order_data.amount, // VND, không có số thập phân
currency: "VND",
description: order_data.description,
return_url: "https://yoursite.com/payment/return",
cancel_url: "https://yoursite.com/payment/cancel",
ipn_url: "https://yoursite.com/webhook/ipn",
payment_method: "all", // hoặc "card", "wallet", "qr"
buyer_info: {
name: order_data.customer_name,
email: order_data.customer_email,
phone: order_data.customer_phone
}
}
encrypted = encrypt_payload(payload, aes_key)
POST {base_url}/openapi/v1/payment/create
body: { data: encrypted }
headers:
Authorization: "Bearer {access_token}"
Partner-Code: partner_code
Content-Type: "application/json"
result = decrypt_response(response.data, aes_key)
return result.payment_url // redirect khách đến đâySau khi có payment_url, redirect frontend của bạn đến URL này. Khách hàng sẽ chọn phương thức thanh toán và hoàn tất.
Bước 4: Nhận IPN Callback
Khi giao dịch hoàn tất (thành công hoặc thất bại), TConnect POST đến ipn_url bạn cung cấp.
// IPN endpoint: POST /webhook/ipn
function ipn_handler(request):
body = parse_json(request)
// 1. Decrypt
txn = decrypt_response(body.data, AES_KEY)
// 2. Idempotency — tránh xử lý 2 lần
if cache.get("ipn:{txn.request_id}") exists:
return { status: "ok" }
cache.set("ipn:{txn.request_id}", ttl=86400)
// 3. Xử lý theo trạng thái
if txn.status == "SUCCESS":
mark_order_paid(txn.order_id, txn.amount)
else if txn.status == "FAILED":
mark_order_failed(txn.order_id)
// 4. Bắt buộc trả 200 — TConnect retry nếu không nhận được
return { status: "ok" }Quan trọng: Luôn trả HTTP 200 kể cả khi bạn đã xử lý rồi. Nếu trả lỗi, TConnect sẽ retry — dẫn đến xử lý trùng lặp.
Bước 5: Kiểm tra trạng thái giao dịch (polling)
Ngoài IPN, bạn có thể chủ động query trạng thái:
function check_payment_status(base_url, partner_code, access_token, aes_key, order_id):
payload = { order_id: order_id }
encrypted = encrypt_payload(payload, aes_key)
POST {base_url}/openapi/v1/payment/query
body: { data: encrypted }
headers:
Authorization: "Bearer {access_token}"
Partner-Code: partner_code
return decrypt_response(response.data, aes_key)Dùng polling như fallback khi IPN bị delay — ví dụ: poll sau 5 phút nếu chưa nhận IPN.
Xử lý các edge cases thường gặp
| Tình huống | Cách xử lý |
|---|---|
| Khách đóng trình duyệt sau khi thanh toán | IPN vẫn gửi — xử lý bình thường |
| Mạng lỗi, IPN không đến | TConnect retry tự động · Thêm cronjob poll trạng thái sau 15 phút |
| Khách thanh toán 2 lần cùng order | Idempotency check qua request_id |
return_url redirect nhưng chưa có IPN | Không mark paid tại return_url — chờ IPN |
| Token hết hạn giữa chừng | Catch 401 → refresh token → retry request |
Checklist trước khi go-live
- Test đầy đủ flow thanh toán trên Sandbox
- Implement idempotency cho IPN handler
- Log toàn bộ IPN payload (để debug sau này)
- Cấu hình retry/fallback khi IPN delay
- Không dùng
return_urlđể mark paid — luôn dùng IPN - Rate limiting cho endpoint IPN (tránh DDoS)
- Rotate
aes_keyvàclient_secretđịnh kỳ
Kết luận
Tích hợp Payment Gateway không phức tạp nếu bạn hiểu đúng luồng: tạo giao dịch → redirect → IPN callback. Phần quan trọng nhất là xử lý IPN an toàn với idempotency và không bao giờ tin vào return_url để xác nhận thanh toán thành công.
Sandbox TConnect miễn phí và không yêu cầu KYB — bắt đầu ngay hôm nay.