Blog
sandboxtestingdeveloperpayment-api

Sandbox TConnect: Hướng dẫn test tích hợp thanh toán không cần KYB

Hướng dẫn sử dụng Sandbox TConnect để test đầy đủ luồng thanh toán — tạo QR, simulate IPN, test VA billing và hóa đơn điện tử — trước khi go-live lên production.

TConnect Team 28 tháng 8, 2025 6 min read

Tại sao cần test kỹ trước khi go-live?

Thanh toán là nghiệp vụ nhạy cảm nhất trong bất kỳ ứng dụng nào. Một lỗi nhỏ — IPN handler bị crash, số tiền tính sai, đơn hàng không đóng — trực tiếp ảnh hưởng đến doanh thu và uy tín.

TConnect Sandbox cho phép test toàn bộ luồng thực (bao gồm IPN callback, mã hóa AES, tạo VA) mà không cần:

  • KYB (Know Your Business) doanh nghiệp
  • Tài khoản ngân hàng thật
  • Tiền thật

Thiết lập môi trường

Endpoint

Môi trườngBase URL
Sandboxhttps://sme-open-api-sandbox.tconnect.vn
Productionhttps://sme-open-api.tconnect.vn

Chỉ thay đổi base_url khi chuyển từ sandbox lên production — không cần sửa code logic.

Credential Sandbox

Đăng ký tại tconnect.vn/developers để nhận:

  • username / password
  • client_id / client_secret
  • partner_code
  • aes_key (hex string)

Cấu hình bằng Environment Variables

# .env.sandbox
TCONNECT_BASE_URL=https://sme-open-api-sandbox.tconnect.vn
TCONNECT_PARTNER_CODE=SANDBOX_PARTNER_001
TCONNECT_USERNAME=[email protected]
TCONNECT_PASSWORD=your_sandbox_password
TCONNECT_CLIENT_ID=sandbox_client_id
TCONNECT_CLIENT_SECRET=sandbox_client_secret
TCONNECT_AES_KEY=dummyhexstring1234567890abcdef
 
# .env.production
TCONNECT_BASE_URL=https://sme-open-api.tconnect.vn
# ... production credentials
// Load đúng env file theo môi trường
env = read_env_var("APP_ENV", default="sandbox")
load_env_file(".env.{env}")
 
TCONNECT_BASE_URL = read_env_var("TCONNECT_BASE_URL")
TCONNECT_AES_KEY  = hex_decode(read_env_var("TCONNECT_AES_KEY"))
TCONNECT_PARTNER  = read_env_var("TCONNECT_PARTNER_CODE")

Test Checklist: Luồng QR thanh toán

1. Login & lấy token

function test_login():
    token, refresh = tconnect.login(USERNAME, PASSWORD, CLIENT_ID, CLIENT_SECRET)
    assert token is not null
    assert length(token) > 10
    print "Login OK — token length: {length(token)}"

2. Tạo Virtual Account

function test_create_va():
    va = tconnect.create_va(
        bank_code:    "970454",
        account_name: "TEST MERCHANT CO LTD"
    )
    assert va.va_number starts with "VA"
    assert va.status == "active"
    print "VA created: {va.va_number}"
    return va.va_number

3. Tạo QR động

function test_create_qr(va_number):
    order_id = "TEST-ORDER-" + current_unix_timestamp()
 
    qr = tconnect.create_qr(
        order_id:     order_id,
        va:           va_number,
        bincode:      "970454",
        service_code: "bidv-qr",
        amount:       50000   // 50,000 VND test
    )
 
    assert "image_png_base64" in qr
    assert "qr_content" in qr
    assert length(qr.image_png_base64) > 100
 
    print "QR created for order: {order_id}"
    print "QR content preview: {first 50 chars of qr.qr_content}..."
    return order_id, qr

4. Simulate IPN (Sandbox feature)

Sandbox cho phép trigger IPN thủ công:

// Trigger IPN giả lập để test handler của bạn
function test_simulate_ipn(order_id):
    payload = {
        order_id: order_id,
        status:   "SUCCESS",
        amount:   50000
    }
    POST {SANDBOX_BASE_URL}/openapi/v1/test/simulate-ipn
        body:    { data: encrypt_payload(payload, AES_KEY) }
        headers: AUTH_HEADERS
 
    result = decrypt_response(response.data, AES_KEY)
    print "IPN simulated: {result}"

5. Test IPN Handler của bạn

function test_ipn_handler():
    // Simulate IPN payload từ TConnect
    ipn_payload = {
        data: hex_encode(encrypt_payload({
            request_id:       "req_test_001",
            order_id:         "TEST-ORDER-12345",
            status:           "SUCCESS",
            amount:           50000,
            retrieval_ref_no: "TXN001"
        }, AES_KEY))
    }
 
    response = POST /webhook/tconnect/ipn with body ipn_payload
 
    assert response.status_code == 200
    assert response.body.status == "ok"
 
    // Verify đơn hàng đã được mark paid
    order = db.get_order("TEST-ORDER-12345")
    assert order.status == "paid"
 
function test_ipn_idempotency():
    // Test rằng IPN trùng lặp không được xử lý 2 lần
    ipn_payload = { data: "..." }   // Same request_id
 
    response1 = POST /webhook/tconnect/ipn with body ipn_payload
    response2 = POST /webhook/tconnect/ipn with body ipn_payload
 
    assert response1.status_code == 200
    assert response2.status_code == 200
 
    // Verify chỉ xử lý 1 lần
    payments = db.get_payments_for_order("TEST-ORDER-12345")
    assert length(payments) == 1

Test các edge cases quan trọng

Test caseMô tảKết quả mong đợi
IPN SUCCESSThanh toán thành côngOrder marked paid
IPN FAILEDThanh toán thất bạiOrder marked failed
IPN duplicateCùng request_id gửi 2 lầnChỉ xử lý 1 lần
Amount mismatchIPN amount ≠ order amountFlag for review
Invalid decryptPayload bị corruptedReturn 200, log error
Unknown order_idOrder không tồn tạiReturn 200, log warning
Token expired401 khi gọi APIAuto-refresh và retry

Expose local server cho IPN (ngrok)

Sandbox TConnect cần gọi được vào ipn_url của bạn. Trong môi trường local, dùng ngrok:

# Cài ngrok
brew install ngrok  # macOS
# hoặc: https://ngrok.com/download
 
# Expose port 8000
ngrok http 8000
 
# Output:
# Forwarding  https://abc123.ngrok.io → http://localhost:8000

Dùng https://abc123.ngrok.io/webhook/tconnect/ipn làm ipn_url khi test.

// Đọc ngrok URL tự động từ ngrok local API
function get_ngrok_url():
    GET http://localhost:4040/api/tunnels
    tunnels = response.tunnels
    https_tunnel = find first tunnel where tunnel.proto == "https"
    return https_tunnel.public_url
 
IPN_URL = get_ngrok_url() + "/webhook/tconnect/ipn"

Production readiness checklist

Trước khi chuyển sang production, đảm bảo:

Authentication
  ✓ Token caching (không login mỗi request)
  ✓ Token refresh khi gần hết hạn
  ✓ Retry sau 401 với token mới

Encryption
  ✓ AES key lưu trong secret manager (không hardcode)
  ✓ Test decrypt mọi response

IPN Handler
  ✓ Idempotency với request_id
  ✓ Luôn return 200
  ✓ Log đầy đủ: request_id, order_id, status, amount
  ✓ Reconciliation cronjob

Error handling
  ✓ Retry với exponential backoff cho transient errors
  ✓ Alert khi error rate tăng đột biến
  ✓ Fallback nếu IPN không đến sau 30 phút

Security
  ✓ IPN endpoint không expose trong docs/swagger
  ✓ Rate limiting
  ✓ Validate source IP (nếu có whitelist từ TConnect)

Kết luận

Sandbox là môi trường an toàn để thử nghiệm mọi scenario trước khi money thật vào cuộc. Đừng bỏ qua test các edge cases — đặc biệt là idempotency và amount mismatch. Một hệ thống thanh toán tốt là hệ thống xử lý được cả các tình huống bất thường mà không cần can thiệp thủ công.

Bắt đầu tích hợp ngay

Sandbox miễn phí · Tài liệu API đầy đủ · Hỗ trợ kỹ thuật