VietQR Payment Automation for AI Agents: Stripe Alternative

2026-07-13 · AgentPay VN

vietqrpythonai-agentspayment-automationvietnam

The Problem: AI Agents Need Native Vietnam Payment Support

You've built an impressive AI agent—maybe it books restaurant reservations, sells online courses, or handles customer support for a Vietnamese e-commerce platform. Everything works smoothly until payment collection arrives. Stripe doesn't support Vietnam's domestic bank transfers. PayPal has friction. International payment gateways add latency, fees, and complexity that kill the agent's real-time responsiveness.

Meanwhile, your customers expect to pay directly from their Vietnamese bank accounts using VietQR—the ubiquitous QR code standard that works across all major banks. The disconnect is frustrating: your agent can understand context, negotiate with users, and close deals, but can't actually collect money without awkward redirects or manual interventions.

AgentPay VN solves this. It's an open-source Python SDK + MCP server that lets AI agents generate VietQR payment requests, send checkout URLs, and confirm settlements—all within the agent's decision loop. No money ever touches AgentPay servers. The QR code points directly at your merchant bank account. A bank feed confirms when the customer pays.

This tutorial shows you exactly how to build payment-collecting AI agents for the Vietnamese market, with working code and real-world examples.


Why VietQR + AI Agents Matter for Vietnam

Vietnam has 99 million people and 95%+ smartphone penetration. VietQR, launched in 2021 by the State Bank, unified QR-based transfers across all major banks—VCB, Agribank, Techcombank, MB, ACB, and dozens more. It's the de facto payment standard for peer-to-peer and merchant transfers.

For AI agents, this is transformative:

The result: Vietnamese AI agents that feel native, fast, and trustworthy.


What Is AgentPay VN? Architecture Overview

AgentPay VN is two components:

  1. Python SDK (pip install agentpay-vn): Functions to create payment requests, generate checkout URLs, and poll settlement status.
  2. MCP Server (agentpay-mcp): A Claude-compatible Model Context Protocol server that wraps the SDK, letting Claude and other AI frameworks call payment functions as tools.

The typical flow is three steps:

  1. create_payment_request: Your agent specifies an amount, description, and metadata (order ID, customer name, etc.).
  2. send checkout_url: The agent presents a QR code or clickable link to the customer.
  3. await_settlement: The agent polls the bank feed until the payment arrives, then proceeds (e.g., ships goods, unlocks content).

AgentPay VN is MIT-licensed (open source), so you can audit the code, self-host the MCP server, and contribute improvements. The source is at https://github.com/phuocdu/agentpay-vn; full API docs are at https://agentpay.servicesai.vn/v1/docs.


Installation and Initial Setup

Step 1: Install the Python SDK

pip install agentpay-vn

This installs the SDK and CLI. Verify:

python -c "import agentpay_vn; print(agentpay_vn.__version__)"

Step 2: Configure Your Merchant Account

You'll need:

Set these as environment variables or in a config file:

export AGENTPAY_MERCHANT_ID="your_merchant_id"
export AGENTPAY_BANK_ACCOUNT="0123456789"
export AGENTPAY_BANK_CODE="970012"  # VCB code

Step 3: Enable MCP Server for Claude

If using Claude (via Claude.ai, Claude API, or your own Claude client), configure the MCP server in your client config:

{
  "mcpServers": {
    "agentpay": {
      "command": "agentpay-mcp",
      "args": []
    }
  }
}

Save this to your MCP config file (e.g., ~/.config/claude-mcp.json). Restart Claude, and it will load the AgentPay tools.


Your First Payment Request: Code Walkthrough

Here's a minimal example. Suppose you're building an online course bot that sells a Python course for 299,000 VND.

from agentpay_vn import PaymentClient, PaymentRequest
import time
import json

# Initialize the client with your merchant details
client = PaymentClient(
    merchant_id="MYSHOP_001",
    bank_account="0123456789",
    bank_code="970012"  # VietComBank
)

# Step 1: Create a payment request
# This generates a unique QR code and checkout URL
payment_req = PaymentRequest(
    amount=299000,                      # 299,000 VND
    description="Python Masterclass Course",
    order_id="ORDER_2025_001",          # Unique order ID
    customer_id="CUST_12345",           # Track customer
    metadata={
        "course_id": "python_2025",
        "customer_name": "Nguyễn Văn A",
        "customer_email": "customer@example.com"
    }
)

# Submit the request and get back a checkout session
checkout = client.create_payment_request(payment_req)

print(f"Checkout URL: {checkout.checkout_url}")
print(f"QR Code (ASCII): \n{checkout.qr_ascii}")
print(f"Session ID: {checkout.session_id}")

# Step 2: Send the checkout URL to the customer
# (In a real agent, this would be displayed or messaged to the user)

# Step 3: Poll for settlement confirmation
# This checks the bank feed repeatedly until payment arrives
max_wait = 300  # Wait up to 5 minutes
start = time.time()

while time.time() - start < max_wait:
    status = client.get_payment_status(checkout.session_id)

    if status.is_settled:
        print(f"✓ Payment confirmed! {status.amount} VND received.")
        print(f"  Settlement ID: {status.settlement_id}")
        print(f"  Timestamp: {status.settled_at}")

        # Payment succeeded—now unlock the course
        grant_course_access(customer_id="CUST_12345", course_id="python_2025")
        break
    elif status.expired:
        print("✗ Payment request expired. Please create a new one.")
        break
    else:
        print(f"  Waiting for payment... ({status.amount_remaining} VND remaining)")
        time.sleep(10)  # Check every 10 seconds
else:
    print("✗ Timeout: Payment not received within 5 minutes.")

def grant_course_access(customer_id, course_id):
    """Dummy function to illustrate next steps."""
    print(f"Granting access to {course_id} for {customer_id}...")
    # In reality, update your database, send email, etc.

Line-by-line breakdown:


Real-World Example: AI Café Reservation Bot

Let's build a more realistic scenario: an AI agent that helps customers reserve tables at a café and collects a 50,000 VND deposit.

from agentpay_vn import PaymentClient, PaymentRequest
from datetime import datetime, timedelta
import uuid

client = PaymentClient(
    merchant_id="CAFE_HANOI_01",
    bank_account="1234567890",
    bank_code="970012"
)

def process_reservation(customer_name, customer_phone, reservation_date, table_size):
    """
    Main function: customer books a table, pays deposit, gets confirmation.
    """

    # Step 1: Validate inputs
    if not (1 <= table_size <= 10):
        return {"status": "error", "message": "Table size must be 1–10."}

    # Step 2: Create a unique reservation ID
    reservation_id = f"RES_{uuid.uuid4().hex[:8].upper()}"

    # Step 3: Create payment request for 50,000 VND deposit
    payment_req = PaymentRequest(
        amount=50000,
        description=f"Café deposit for {table_size} people on {reservation_date}",
        order_id=reservation_id,
        metadata={
            "customer_name": customer_name,
            "customer_phone": customer_phone,
            "reservation_date": reservation_date,
            "table_size": table_size,
            "reservation_type": "cafe_deposit"
        }
    )

    checkout = client.create_payment_request(payment_req)

    # Step 4: Return checkout details to the agent (to show customer)
    return {
        "status": "pending_payment",
        "reservation_id": reservation_id,
        "amount_due": 50000,
        "checkout_url": checkout.checkout_url,
        "qr_code": checkout.qr_ascii,
        "message": f"Please scan the QR or click the link to pay 50,000 VND deposit for {table_size} people on {reservation_date}."
    }

def confirm_reservation(reservation_id, timeout_minutes=5):
    """
    Poll for settlement and finalize the reservation.
    """
    import time

    deadline = time.time() + (timeout_minutes * 60)

    while time.time() < deadline:
        # Fetch payment status from database/API
        status = client.get_payment_status(reservation_id)

        if status.is_settled:
            # Payment received—update reservation status
            send_confirmation_sms(
                phone=status.metadata["customer_phone"],
                message=f"Your café reservation is confirmed! Reservation ID: {reservation_id}"
            )
            return {
                "status": "confirmed",
                "reservation_id": reservation_id,
                "message": "Reservation confirmed! You'll receive a confirmation SMS."
            }

        time.sleep(5)  # Check every 5 seconds

    # Timeout
    return {
        "status": "expired",
        "reservation_id": reservation_id,
        "message": "Payment not received. Please try again."
    }

def send_confirmation_sms(phone, message):
    """Send SMS confirmation. In production, use Twilio, AWS SNS, etc."""
    print(f"[SMS] {phone}: {message}")

In a real Claude agent, the flow would be:

  1. User: "I want to book a table for 4 people on Friday at 7 PM."
  2. Claude: "Great! Let me process your reservation." → calls process_reservation()
  3. Claude shows the QR code and says: "Please scan this QR code to pay 50,000 VND deposit."
  4. Customer scans, pays via their bank app.
  5. Claude: "Payment received! Your reservation is confirmed. Here's your ID: RES_ABC123."

Integrating with Claude via MCP

If you're running Claude (or another LLM with MCP support), you can skip Python code and let Claude handle everything. Configure your MCP like this:

{
  "mcpServers": {
    "agentpay": {
      "command": "agentpay-mcp",
      "env": {
        "AGENTPAY_MERCHANT_ID": "CAFE_HANOI_01",
        "AGENTPAY_BANK_ACCOUNT": "1234567890",
        "AGENTPAY_BANK_CODE": "970012"
      }
    }
  }
}

Now in Claude, you can use prompts like:

"User wants to book a table. Use the agentpay tools to create a payment request for 50,000 VND, then send the checkout URL."

Claude will automatically call create_payment_request, get back a URL and QR code, and present it to the user.


Do's and Don'ts: Best Practices

Do Don't
Store order_id and session_id in your database for audit trails. Hard-code merchant ID or bank account in your source code.
Validate amounts and metadata before calling create_payment_request. Assume payment is instant—always poll for settlement.
Use exponential backoff when polling: 5s, 10s, 20s. Reduces API load. Store customer bank details or payment secrets. AgentPay never handles them.
Set reasonable timeouts (5–10 minutes). VietQR transfers usually settle in 1–3 minutes. Retry expired payment requests. Create new ones instead.
Log all payment events (created, settled, expired) for compliance. Charge customer twice if settlement status is ambiguous. Check the settled_at timestamp.
Test with small amounts (10,000–50,000 VND) in a staging environment. Use real merchant credentials in development. Use test merchant IDs.

Advanced Tips: Production Readiness

Idempotency

If your agent retries due to a network glitch, use the same order_id twice. AgentPay VN returns the same session and QR code, preventing duplicate charges.

# First call (succeeds, but network dies)
checkout_1 = client.create_payment_request(payment_req)

# Retry with same order_id (safe)
checkout_2 = client.create_payment_request(payment_req)  # Same as checkout_1
assert checkout_1.session_id == checkout_2.session_id

Batch Settlement Confirmation

For high-volume agents, don't poll per-session. Instead, use a webhook or scheduled job to sync with your bank's transaction history:

# Pseudo-code for a daily batch reconciliation
for reservation in pending_reservations:
    bank_txns = fetch_bank_transactions(date=reservation.created_date)
    if any(t.amount == reservation.amount and t.metadata["order_id"] == reservation.id for t in bank_txns):
        mark_reservation_confirmed(reservation.id)

Error Handling

Always wrap payment calls in try–except:

try:
    checkout = client.create_payment_request(payment_req)
except ConnectionError:
    # Network issue; retry later
    queue_retry(payment_req)
except ValueError as e:
    # Invalid amount or metadata
    return {"status": "error", "message": str(e)}
except Exception as e:
    # Unexpected error; log and alert
    log_error(e)
    notify_admin(f"Payment creation failed: {e}")

FAQ

Q: Can AgentPay VN hold customer money?

No. AgentPay VN never touches funds. The QR code and checkout URL point directly at your merchant bank account. When the customer scans and pays, money goes straight to your bank. AgentPay VN only coordinates the QR generation and status checks.

Q: How long does settlement take?

VietQR transfers between major Vietnamese banks typically settle within 1–3 minutes during business hours. On weekends and holidays, settlement can take longer. Always set a reasonable polling timeout (5–10 minutes) and gracefully handle timeouts.

Q: Is AgentPay VN secure?

Yes. It's MIT-licensed open source, so you can audit the code yourself. It never handles customer bank credentials (those stay between customer and their bank app). Merchant IDs and bank accounts should be treated as secrets and stored in environment variables, not hardcoded. AgentPay VN uses industry-standard encryption for API communication.

Q: Can I use AgentPay VN outside Vietnam?

VietQR is Vietnam-specific, so AgentPay VN is designed for Vietnamese merchant accounts and Vietnamese customers. If you need multi-country support, consider Stripe or Wise for international transfers, and use AgentPay VN for domestic Vietnam transactions.

Q: What if a customer sends the wrong amount?

AgentPay VN monitors the exact amount specified in the payment request. If the customer underpays or overpays, the settlement status will reflect the actual amount received. Your agent should compare status.amount to the expected amount and either accept the overpayment, request additional funds, or refund the underpayment via a separate transfer.


Recap: Key Takeaways


Next Steps

  1. Install AgentPay VN: pip install agentpay-vn
  2. Read the full docs: https://agentpay.servicesai.vn/v1/docs
  3. Explore the source code: https://github.com/phuocdu/agentpay-vn
  4. Start building: Use the Python SDK examples above or configure the MCP server for Claude.
  5. Get support: Open an issue on GitHub or reach out via the AgentPay VN website.

Your Vietnamese AI agents are now ready to collect payments. Happy building!

Get started →

← All posts