Let Your AI Agent Accept VietQR Payments in Python
The Problem: AI Agents That Can't Take Money
You've built an AI chatbot that books consultations, sells digital products, or reserves café tables. It handles conversations flawlessly. But when a customer says "I'm ready to pay," your agent hits a wall. There's no way to collect payment without breaking character, handing off to Stripe, or setting up a complex webhook maze. You're leaving revenue on the table—literally—because your agent can't complete the transaction itself.
In Vietnam, this problem is even sharper. VietQR is ubiquitous and trusted, but integrating it into an AI workflow has meant choosing between heavy payment platforms (that take cuts) or building from scratch. AgentPay VN changes this. It's an open-source Python SDK that lets your AI agent generate a VietQR code, send it to the customer, and wait for bank settlement—all without touching the money. The QR points straight at the merchant's real bank account.
What Is AgentPay VN?
AgentPay VN is a lightweight, MIT-licensed Python SDK paired with an MCP (Model Context Protocol) server. Think of it as a bridge between your AI agent and Vietnam's banking system.
Key facts: - No fund custody. AgentPay VN never holds or transfers money. The QR code directs payment straight to your merchant bank account. - Bank-confirmed settlement. A real bank feed verifies when money arrives. - 3-line flow. Create a payment request → send the checkout URL → await settlement. That's it. - Open-source (MIT). Full transparency. Run it on your own infrastructure. - Works with Claude, o1, any LLM via MCP. Your agent gets payment tools as native functions.
Use cases: - Course-selling bots ("Pay now to access the module") - AI barista taking café orders ("Your total is 50,000 VND—scan this code") - Consultation booking ("Reserve 30 mins for 200,000 VND") - Freelance agent marketplaces - Donation campaigns with AI verification
Installation and Setup
Step 1: Install the SDK
pip install agentpay-vn
Verify the installation:
python -c "import agentpay_vn; print(agentpay_vn.__version__)"
Step 2: Set Environment Variables
AgentPay VN requires minimal config. You'll need:
- MERCHANT_ID: Your VietQR merchant identifier (provided by your bank or VietQR partner)
- BANK_CODE: Your bank code (e.g., MB for MBBank, ACB for ACB)
- ACCOUNT_NUMBER: Your receiving account number
Create a .env file:
MERCHANT_ID=your_merchant_id
BANK_CODE=MB
ACCOUNT_NUMBER=1234567890
Load it in your Python app:
import os
from dotenv import load_dotenv
load_dotenv()
MERCHANT_ID = os.getenv("MERCHANT_ID")
BANK_CODE = os.getenv("BANK_CODE")
ACCOUNT_NUMBER = os.getenv("ACCOUNT_NUMBER")
Step 3: Install the MCP Server (Optional but Recommended)
If you're using Claude or another LLM, run the MCP server:
pip install agentpay-mcp
agentpay-mcp
This exposes AgentPay as a set of callable tools for your AI agent.
Building Your First Payment-Enabled Agent
The 3-Step Flow
Every AgentPay transaction follows the same pattern:
- create_payment_request() – Generate a unique payment session
- send checkout_url() – Give the customer the VietQR code
- await_settlement() – Wait for bank confirmation
Code Example: A Course-Selling Bot
Let's build a simple AI agent that sells Python courses. The agent chats with the customer, and when they decide to buy, it handles payment end-to-end.
from agentpay_vn import AgentPayClient, PaymentRequest
import os
import time
# Initialize client (reads env vars)
client = AgentPayClient(
merchant_id=os.getenv("MERCHANT_ID"),
bank_code=os.getenv("BANK_CODE"),
account_number=os.getenv("ACCOUNT_NUMBER")
)
def sell_course(customer_name: str, course_title: str, price_vnd: int) -> dict:
"""
Orchestrate a course sale from request to settlement.
Args:
customer_name: Name of the buyer
course_title: Course being sold
price_vnd: Price in Vietnamese Dong
Returns:
Settlement confirmation dict
"""
# Step 1: Create a payment request
# This generates a unique session and prepares the QR data.
payment_request = PaymentRequest(
amount=price_vnd,
description=f"Course: {course_title}",
reference_id=f"COURSE_{customer_name}_{int(time.time())}",
timeout_seconds=600 # 10 minute window to pay
)
# Step 2: Send to customer
# In a real bot, you'd display the QR or send a link.
checkout_url = client.create_payment_request(payment_request)
print(f"\n📱 Pay here: {checkout_url}")
print(f"💰 Amount: {price_vnd:,} VND")
# Step 3: Wait for settlement
# This polls the bank feed until payment is confirmed.
print("\n⏳ Waiting for payment confirmation...")
settlement = client.await_settlement(
reference_id=payment_request.reference_id,
max_wait_seconds=600
)
if settlement['status'] == 'confirmed':
print(f"\n✅ Payment received!")
print(f" Settlement time: {settlement['settled_at']}")
print(f" Bank ref: {settlement['bank_reference']}")
# Send course access
send_course_access(customer_name, course_title)
return {"success": True, "course": course_title}
else:
print(f"\n❌ Payment failed or timed out.")
return {"success": False, "reason": settlement.get('error')}
def send_course_access(customer_name: str, course_title: str):
"""Mock function to grant course access."""
print(f"\n🎓 Sending course link to {customer_name}...")
print(f" Download: https://courses.example.com/{course_title.replace(' ', '-')}")
# Run it
if __name__ == "__main__":
result = sell_course(
customer_name="Nguyễn Văn A",
course_title="Advanced Python for AI",
price_vnd=299000
)
print(f"\nResult: {result}")
Line-by-line breakdown:
- Line 10–13: Initialize the AgentPayClient with your merchant credentials from env vars.
- Line 19–30: Define the payment request with amount, description, and a unique reference ID (prevents duplicate payments).
- Line 33: create_payment_request() returns a checkout URL containing the VietQR code data.
- Line 39–41: await_settlement() blocks until the customer pays or the timeout expires. It polls your bank feed every few seconds.
- Line 43–51: Check the settlement status and act accordingly (grant access, send confirmation, etc.).
Integrating with Claude via MCP
If you want Claude (or another LLM) to call these functions directly, use the MCP server. First, start the MCP server:
agentpay-mcp --port 3000
Then configure your LLM's Claude integration with this JSON config:
{
"mcpServers": {
"agentpay": {
"command": "agentpay-mcp",
"args": [],
"env": {
"MERCHANT_ID": "your_merchant_id",
"BANK_CODE": "MB",
"ACCOUNT_NUMBER": "1234567890"
}
}
}
}
Now Claude can call create_payment_request, await_settlement, etc. as first-class functions in its tool set.
Real-World Walkthrough: AI Café Ordering Bot
Imagine a Telegram bot for a Saigon café. Customers chat to order, and the bot processes payment.
from agentpay_vn import AgentPayClient, PaymentRequest
import asyncio
client = AgentPayClient(
merchant_id="cafe_123",
bank_code="ACB",
account_number="1234567890"
)
async def process_cafe_order(customer_id: str, items: list, total_vnd: int):
"""
Customer has finished ordering. Take payment.
Items: [{"name": "Cà phê đen", "qty": 2, "price": 20000}]
"""
# Build order summary
order_desc = "; ".join([f"{item['qty']}x {item['name']}" for item in items])
# Create payment request
payment = PaymentRequest(
amount=total_vnd,
description=f"Café order: {order_desc}",
reference_id=f"CAFE_{customer_id}_{int(time.time())}"
)
qr_url = client.create_payment_request(payment)
# Send QR to customer via Telegram/chat
message = f"""☕ Your order:
{order_desc}
💰 Total: {total_vnd:,} VND
📱 Scan to pay: {qr_url}
Order ready in 10 minutes!
"""
# Wait for payment (with timeout)
settlement = client.await_settlement(
reference_id=payment.reference_id,
max_wait_seconds=300 # 5 minute window
)
if settlement['status'] == 'confirmed':
# Mark order as paid, start preparing
print(f"✅ Order {customer_id} paid. Start prep!")
return {"paid": True, "ready_in_minutes": 10}
else:
print(f"❌ Order {customer_id} payment timeout.")
return {"paid": False}
# Simulate
async def main():
result = await process_cafe_order(
customer_id="tran_123",
items=[
{"name": "Cà phê đen", "qty": 2, "price": 20000},
{"name": "Bánh mì", "qty": 1, "price": 25000}
],
total_vnd=65000
)
print(result)
asyncio.run(main())
The café bot now seamlessly collects payment without leaving Telegram. Customers scan the QR, the bot confirms, and the café's account receives the money directly.
Best Practices & Common Pitfalls
| ✅ Do | ❌ Don't |
|---|---|
Use unique reference_id for each payment to prevent duplicates |
Reuse the same reference ID across multiple payments |
Set an appropriate timeout_seconds (5–15 minutes for most use cases) |
Leave timeout too short (customers need time to open banking app) |
Store settlement['bank_reference'] for accounting records |
Ignore settlement details—you'll need them for reconciliation |
| Test with small amounts first (e.g., 1,000 VND) | Go live with production without testing |
Handle await_settlement() timeouts gracefully (offer retry or refund) |
Crash if payment doesn't arrive within timeout |
| Run the MCP server on a separate process/container for reliability | Mix MCP server logic with your main agent code |
Advanced Tips
1. Retry Logic for Timeouts
If the customer takes longer than expected:
max_retries = 3
for attempt in range(max_retries):
settlement = client.await_settlement(
reference_id=payment.reference_id,
max_wait_seconds=600
)
if settlement['status'] == 'confirmed':
break
if attempt < max_retries - 1:
print(f"Retry {attempt + 1}...")
time.sleep(30)
2. Idempotency & Duplicate Prevention
Always include a unique reference ID tied to the customer and timestamp:
import hashlib
def generate_reference_id(customer_id: str, order_id: str) -> str:
data = f"{customer_id}_{order_id}_{int(time.time())}"
return hashlib.md5(data.encode()).hexdigest()[:16].upper()
3. Logging Settlements for Accounting
Store settlement data for reconciliation:
import json
from datetime import datetime
def log_settlement(settlement: dict):
record = {
"timestamp": datetime.utcnow().isoformat(),
"reference_id": settlement['reference_id'],
"amount": settlement['amount'],
"status": settlement['status'],
"bank_reference": settlement.get('bank_reference')
}
with open("settlements.jsonl", "a") as f:
f.write(json.dumps(record) + "\n")
FAQ
Q: Does AgentPay VN hold my money? No. The QR code points directly to your merchant bank account. AgentPay VN is just a middleware layer—no money ever passes through it. Settlement is confirmed by your actual bank.
Q: What banks are supported? AgentPay VN supports any bank integrated with VietQR (which includes all major Vietnamese banks: MB, ACB, VCB, BID, etc.). Check your bank's VietQR provider.
Q: Can I run this locally for testing? Yes. Use a test/sandbox merchant ID from your bank. AgentPay VN works with both production and sandbox VietQR endpoints (configure via environment variables).
Q: What happens if the customer doesn't pay?
The await_settlement() call times out. You can retry, ask for an alternative payment method, or cancel the transaction. Your agent remains in control.
Q: Is it really open-source? Yes, MIT license. Full source at GitHub. Review, fork, contribute.
Key Takeaways
- AgentPay VN is lightweight. Three lines of Python (
create_payment_request → send checkout_url → await_settlement) get you from conversation to confirmed payment. - No fund custody risk. Money flows directly to your bank account. AgentPay VN is transparent middleware, not a payment processor.
- Made for AI agents. Whether it's Claude via MCP or a custom Python bot, your agent can orchestrate payments natively.
- Ideal for Vietnamese businesses. VietQR is ubiquitous, trusted, and instant. No Stripe fees or forex headaches.
- Open-source and affordable. MIT license, minimal infrastructure, run it yourself.
- Real-world ready. Course sales, café orders, consultations, marketplaces—AgentPay VN handles them all.
Next Steps
Install AgentPay VN now:
pip install agentpay-vn
Read the full documentation: https://agentpay.servicesai.vn/v1/docs
Explore the source: GitHub - AgentPay VN
Your AI agent is ready to sell. Let it collect VietQR payments with confidence.