VietQR Payment Automation for AI Agents: Stripe Alternative
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:
- No account setup friction: Customers scan a QR code from any banking app they already use.
- Bank-to-bank settlement: Funds arrive directly in your merchant account within minutes, not days.
- Zero platform risk: AgentPay VN never holds funds—it's a coordination layer only.
- Cost-effective: VietQR fees are typically 0–0.5%, much lower than international processors.
- Real-time confirmation: Your agent can await settlement confirmation and immediately trigger next steps (deliver course access, confirm booking, print invoice).
The result: Vietnamese AI agents that feel native, fast, and trustworthy.
What Is AgentPay VN? Architecture Overview
AgentPay VN is two components:
- Python SDK (
pip install agentpay-vn): Functions to create payment requests, generate checkout URLs, and poll settlement status. - 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:
- create_payment_request: Your agent specifies an amount, description, and metadata (order ID, customer name, etc.).
- send checkout_url: The agent presents a QR code or clickable link to the customer.
- 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:
- Merchant ID: Provided by your bank or VietQR aggregator (e.g., Momo, VCB Business API).
- Bank Account Info: Account number and bank code where customers will send money.
- API Key (optional): If using an aggregator; some banks provide direct API access.
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:
- Lines 1–5: Import the SDK.
PaymentClientmanages requests;PaymentRequeststructures the data. - Lines 8–12: Initialize with your merchant ID, bank account, and bank code. These should be secrets in production.
- Lines 15–25: Define a payment request with amount (in VND), description, unique order ID, and metadata (customer name, course ID, etc.).
- Lines 28–31: Call
client.create_payment_request(). This returns acheckoutobject with a URL and QR code. - Lines 33–35: Log the checkout details. In a real agent, send the URL to the customer via chat, email, or display the QR code.
- Lines 38–52: Poll the payment status every 10 seconds. When
is_settledis true, the bank confirmed the transfer. Call your business logic (grant course access). - Lines 54–57: Handle expiry or timeout gracefully.
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:
- User: "I want to book a table for 4 people on Friday at 7 PM."
- Claude: "Great! Let me process your reservation." → calls
process_reservation() - Claude shows the QR code and says: "Please scan this QR code to pay 50,000 VND deposit."
- Customer scans, pays via their bank app.
- 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
- Native VietQR support: AgentPay VN is the first open-source SDK for AI agents to collect VietQR payments natively in Vietnam, without Stripe or international gateways.
- Three-step flow: Create a payment request → send a checkout URL/QR → await settlement. Simple, fast, and synchronous with agent logic.
- Zero platform risk: Money never touches AgentPay VN. QR codes point straight at your merchant bank account. Bank feeds confirm settlement.
- Python + MCP: Use the SDK directly in your Python scripts, or let Claude handle it via the MCP server. Both approaches work.
- Production-ready: MIT-licensed, open-source code. Self-hostable. Idempotent. Audit-friendly.
- Real-world examples: Café reservations, online courses, e-commerce, SaaS trials—any AI agent that needs to collect money from Vietnamese customers can use AgentPay VN.
Next Steps
- Install AgentPay VN:
pip install agentpay-vn - Read the full docs: https://agentpay.servicesai.vn/v1/docs
- Explore the source code: https://github.com/phuocdu/agentpay-vn
- Start building: Use the Python SDK examples above or configure the MCP server for Claude.
- 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!