Blog
pospayment-apiintegrationretailvietqr

Tích hợp POS với TConnect API: Từ phần mềm quản lý bán hàng đến thanh toán số

Hướng dẫn tích hợp POS (phần mềm và phần cứng) với TConnect API để chấp nhận thanh toán VietQR, thẻ và ví điện tử trực tiếp tại quầy bán hàng.

TConnect Team 6 tháng 3, 2026 6 min read

POS Integration: Vì sao cần thiết?

Phần mềm POS truyền thống và thanh toán số vẫn còn nhiều khoảng cách:

  • Thu ngân nhập số tiền thủ công vào máy POS → dễ nhầm lẫn
  • Khách thanh toán QR nhưng nhân viên phải kiểm tra app ngân hàng để xác nhận
  • Không đối soát được giao dịch ngân hàng với đơn hàng POS

Tích hợp TConnect API vào POS giải quyết tất cả: số tiền từ đơn hàng tự động điền vào QR, xác nhận thanh toán tự động, đơn hàng đóng ngay sau khi nhận tiền.


Kiến trúc tích hợp POS

[POS Software]

     │  1. Tạo đơn hàng

POST /openapi/v1/qr/create

     │  2. Nhận QR code + order binding

[Hiển thị QR trên màn hình khách / in QR]

     │  3. Khách quét & thanh toán

[TConnect IPN] → [POS Backend] → Đóng đơn tự động

Hai mô hình phổ biến

Mô hình 1: POS Software — tích hợp qua API, hiển thị QR trên màn hình.

Mô hình 2: POS Hardware — máy POS vật lý (terminal) kết nối qua TConnect SDK.


Implement: POS Software Integration

1. Tạo Virtual Account cho mỗi quầy

// Chạy một lần khi setup quầy — không cần tạo lại mỗi ngày
function setup_cashier_va(cashier_id, cashier_name):
    va = tconnect.create_va(
        bank_code:    "970454",   // BIDV — thay theo ngân hàng merchant
        account_name: "QUAY {cashier_id} - {uppercase(cashier_name)}"
    )
    // Lưu va_number vào database của quầy
    db.update_cashier(cashier_id, va_number=va.va_number)
    return va.va_number
 
// Kết quả: QUAY 01 - THU NGAN A → VA300125

2. Tạo QR khi checkout

// Tạo QR động cho từng đơn hàng.
// order_id encode cả cashier + invoice để dễ đối soát.
function create_checkout_qr(cashier, order):
    order_id = "{cashier.store_code}-{cashier.id}-{order.invoice_id}"
 
    qr = tconnect.create_qr(
        order_id:     order_id,
        va:           cashier.va_number,
        bincode:      "970454",
        service_code: "bidv-qr",
        amount:       order.total_amount
    )
 
    return {
        qr_image:   qr.image_png_base64,
        qr_content: qr.qr_content,
        order_id:   order_id,
        amount:     order.total_amount,
        expires_at: current_time() + 15 minutes
    }

3. Hiển thị QR trên màn hình

// Component màn hình quầy: hiển thị QR và đếm ngược thời gian
function CheckoutQR(qrData, onPaymentConfirmed):
    state timeLeft = 900   // 15 phút tính bằng giây
    state paid     = false
 
    // Countdown timer: giảm timeLeft mỗi giây
    on mount:
        start timer every 1 second:
            if timeLeft > 0: decrease timeLeft by 1
 
    // Lắng nghe IPN qua WebSocket
    on mount:
        connect to WebSocket at "wss://api.yourpos.com/ws/payment"
        on message received:
            data = parse_json(message)
            if data.type == "payment_confirmed":
                set paid = true
                wait 2 seconds then call onPaymentConfirmed(data.order_id)
        on unmount: close WebSocket
 
    render:
        if paid:
            display "Thanh toán thành công!"
        else:
            display QR image from "data:image/png;base64," + qrData.qr_image
            display formatCurrency(qrData.amount)
            if timeLeft > 0:
                display "Hết hạn sau {minutes}:{seconds}"
            else:
                display "QR đã hết hạn — tạo QR mới"

4. Xử lý IPN và đóng đơn

// IPN endpoint: POST /webhook/tconnect/ipn
function ipn_handler(request):
    body = parse_json(request)
    txn  = decrypt_aes(body.data, AES_KEY)
 
    // Parse order_id format: STORE01-CASH01-INV12345
    parts      = split(txn.order_id, separator="-")
    store_code = parts[0]
    cashier_id = parts[1]
    invoice_id = parts[2]
 
    if txn.status == "SUCCESS":
        // Đóng đơn trong POS
        pos_db.close_invoice(
            invoice_id:     invoice_id,
            payment_method: "BANK_TRANSFER",
            payment_ref:    txn.retrieval_ref_no,
            paid_amount:    txn.amount
        )
 
        // Notify màn hình quầy qua WebSocket
        ws_manager.notify_cashier(
            cashier_id: cashier_id,
            event: {
                type:     "payment_confirmed",
                order_id: txn.order_id,
                amount:   txn.amount
            }
        )
 
    return { status: "ok" }

Đối soát cuối ca (End-of-Day Reconciliation)

// Đối soát tất cả giao dịch trong ca làm việc
function reconcile_shift(store_code, cashier_id, shift_start, shift_end):
    // Lấy tất cả IPN đã nhận trong ca
    ipn_transactions = db.get_ipn_transactions(
        cashier_id: cashier_id,
        from_time:  shift_start,
        to_time:    shift_end
    )
 
    // Lấy tất cả đơn hàng trong ca từ POS
    pos_invoices = pos_db.get_invoices(
        cashier_id:     cashier_id,
        from_time:      shift_start,
        to_time:        shift_end,
        payment_method: "BANK_TRANSFER"
    )
 
    // Đối chiếu — xây map theo order_id
    ipn_by_order   = map each txn by txn.order_id
    pos_by_invoice = map each invoice by "STORE01-{cashier_id}-{invoice.id}"
 
    matched       = []
    unmatched_ipn = []
    unmatched_pos = []
 
    for each (order_id, ipn) in ipn_by_order:
        if order_id exists in pos_by_invoice:
            pos = pos_by_invoice[order_id]
            if ipn.amount == pos.total:
                append { order_id, amount: ipn.amount } to matched
            else:
                // Số tiền không khớp — cần review
                append { order_id, ipn_amount: ipn.amount,
                         pos_amount: pos.total, discrepancy: true } to matched
        else:
            append ipn to unmatched_ipn
 
    return {
        total_matched:        count(matched),
        total_matched_amount: sum of matched.amount where discrepancy is not set,
        discrepancies:        filter matched where discrepancy == true,
        unmatched_ipn:        unmatched_ipn,
        unmatched_pos:        unmatched_pos
    }

Tích hợp POS Hardware (Terminal vật lý)

Với máy POS vật lý (Android terminal), tích hợp qua TConnect POS SDK:

// Android (Java/Kotlin)
TConnectPOS pos = new TConnectPOS.Builder()
    .setPartnerCode("YOUR_PARTNER_CODE")
    .setAesKey(AES_KEY_BYTES)
    .setEnvironment(Environment.PRODUCTION)
    .build();
 
// Khi khách checkout
PaymentRequest request = new PaymentRequest.Builder()
    .setOrderId("ORDER_001")
    .setAmount(500000L)
    .setCurrency("VND")
    .setVaNumber("VA300125")
    .build();
 
pos.requestPayment(request, new PaymentCallback() {
    @Override
    public void onSuccess(PaymentResult result) {
        // result.getTransactionId()
        // result.getAmount()
        closeInvoice(result);
    }
 
    @Override
    public void onFailed(PaymentError error) {
        showError(error.getMessage());
    }
});

Checklist trước khi go-live

  • Test QR tạo đúng với số tiền từ POS
  • Test IPN nhận và đóng đơn tự động
  • Test WebSocket notify màn hình quầy
  • Test timeout QR và tạo QR mới
  • Implement đối soát cuối ca
  • Backup plan khi internet down (fallback sang tiền mặt)
  • Training nhân viên quy trình xử lý khi QR lỗi

Kết luận

Tích hợp POS với TConnect API không yêu cầu thay đổi toàn bộ hệ thống hiện tại — chỉ cần thêm một layer API call khi tạo đơn và một IPN handler để đóng đơn. Kết quả: thu ngân nhanh hơn, ít sai sót hơn, và báo cáo đối soát tự động cuối ca.

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

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