AI Agent VietQR Payments in Python: Setup Guide
The Problem: AI Agents Can't Collect Money (Yet)
You've built an impressive AI agent—maybe it handles customer support, sells digital products, or books appointments. It can talk, understand context, and make decisions. But the moment a customer says "I want to buy this," your agent hits a wall.
Traditional payment systems require: - Complex PCI compliance setups - Merchant accounts that take weeks - Managing customer card data (risky) - Heavy integration overhead
For Vietnamese merchants especially, accepting payments through AI agents meant either routing payments manually outside the agent or building custom integrations with banking APIs. Neither solution was elegant.
AgentPay VN changes that. It lets your Python AI agent generate a VietQR code, send it to the customer, and know instantly when payment lands—all without ever touching the money. The funds go straight to your merchant account.
Why VietQR + AI Agents = Perfect Pairing
VietQR is Vietnam's QR standard for bank-to-bank transfers. It's ubiquitous: customers scan with any banking app, pay directly from their account, and it's settled in seconds. For AI agents, this is ideal because:
- No token storage: Your agent never handles sensitive card data
- Instant confirmation: Bank feeds confirm settlement in real-time
- User familiar: Vietnamese users already know VietQR from daily transactions
- Stateless: Perfect for serverless and ephemeral agent environments
AgentPay VN abstracts the complexity into three methods:
- create_payment_request() → generates a unique payment request
- send_checkout_url() → delivers the QR to the user
- await_settlement() → blocks until the bank confirms payment
Getting Started: Installation & Setup
Step 1: Install AgentPay VN SDK
pip install agentpay-vn
This gives you the core Python SDK (MIT licensed, open-source). Check out the GitHub repository to review the source.
Step 2: Set Environment Variables
You'll need merchant credentials from your Vietnamese bank (we support VietCombank, Techcombank, and others). Create a .env file:
BANK_CODE=970436 # VietCombank example
MERCHANT_ACCOUNT=0123456789 # Your bank account number
API_KEY=your_bank_api_key_here # From your bank's developer portal
SECRET_KEY=your_secret_key_here
Step 3: Initialize AgentPay in Your Agent
If you're using Claude or another LLM agent, you'll want the MCP (Model Context Protocol) server to expose AgentPay as a tool:
pip install agentpay-mcp
Create mcp_config.json for Claude:
{
"mcpServers": {
"agentpay": {
"command": "python",
"args": ["-m", "agentpay.mcp.server"],
"env": {
"BANK_CODE": "970436",
"MERCHANT_ACCOUNT": "0123456789",
"API_KEY": "your_api_key",
"SECRET_KEY": "your_secret_key"
}
}
}
}
Now Claude (or your agent) can call AgentPay methods like any other MCP tool.
The 3-Line Payment Flow Explained
Understanding the Flow
Every payment in AgentPay VN follows this pattern:
- Request → Agent creates a unique payment request (amount, description, customer info)
- Checkout → Agent gets a URL with embedded VietQR code, sends it to customer
- Settlement → Agent polls bank API, detects when payment lands, resumes execution
Let's code this:
from agentpay_vn import AgentPayClient, PaymentRequest
import asyncio
import os
# Initialize the client
client = AgentPayClient(
bank_code=os.getenv("BANK_CODE"),
merchant_account=os.getenv("MERCHANT_ACCOUNT"),
api_key=os.getenv("API_KEY"),
secret_key=os.getenv("SECRET_KEY")
)
async def process_payment(customer_id: str, amount: int, description: str):
"""
Process a VietQR payment for a customer.
Args:
customer_id: Unique customer identifier
amount: Payment amount in VND
description: Payment description (e.g., "Course Purchase")
"""
# Step 1: Create the payment request
# This generates a unique request ID and prepares the payment
payment_req = PaymentRequest(
amount=amount,
description=description,
customer_id=customer_id,
reference_code=f"ORDER_{customer_id}_{int(time.time())}"
)
payment = await client.create_payment_request(payment_req)
payment_id = payment["id"]
print(f"✓ Payment request created: {payment_id}")
print(f" Amount: {amount:,} VND")
# Step 2: Get the checkout URL with embedded VietQR code
# You'd send this link/QR to your customer (via message, email, chat)
checkout_url = await client.get_checkout_url(payment_id)
print(f"✓ Send this to customer: {checkout_url}")
# Step 3: Wait for settlement
# This polls the bank API every 2 seconds until payment is confirmed
# Timeout is 5 minutes by default
try:
settlement = await client.await_settlement(
payment_id=payment_id,
timeout_seconds=300
)
print(f"✓ Payment confirmed!")
print(f" Transaction ID: {settlement['transaction_id']}")
print(f" Settled at: {settlement['timestamp']}")
return True
except asyncio.TimeoutError:
print(f"✗ Payment not received within 5 minutes")
return False
# Example usage
if __name__ == "__main__":
success = asyncio.run(
process_payment(
customer_id="CUST_789",
amount=299000, # ~$13 USD
description="Python Course: Advanced Async Patterns"
)
)
if success:
print("\n→ Next step: Deliver course access to customer")
Line-by-line breakdown:
- PaymentRequest object: Encapsulates payment metadata. The reference_code is crucial—it's your link to orders in your own database.
- create_payment_request(): Contacts AgentPay backend, assigns a unique ID, validates credentials. Returns payment details.
- get_checkout_url(): Generates a one-time URL containing the VietQR code (as a rendered SVG or PNG). This is what you show the customer.
- await_settlement(): Enters a polling loop. Every 2 seconds, it queries your bank's API to check if the exact amount arrived. Once confirmed, returns transaction metadata.
Real-World Example: AI Course Selling Agent
Imagine you've built a chatbot that sells online courses. Here's how you'd wire AgentPay:
import json
from agentpay_vn import AgentPayClient
from datetime import datetime
class CourseSellingAgent:
def __init__(self):
self.client = AgentPayClient(
bank_code="970436",
merchant_account="1234567890",
api_key=os.getenv("AGENTPAY_API_KEY"),
secret_key=os.getenv("AGENTPAY_SECRET_KEY")
)
self.courses = {
"python-async": {"name": "Advanced Python Async", "price": 299000},
"langchain-pro": {"name": "LangChain + Claude", "price": 499000},
}
async def sell_course(self, user_id: str, course_key: str):
"""
Handle course purchase flow.
Called when user says 'I want to buy Advanced Python Async'
"""
if course_key not in self.courses:
return {"status": "error", "message": "Course not found"}
course = self.courses[course_key]
# Create payment request
payment_req = PaymentRequest(
amount=course["price"],
description=f"Course: {course['name']}",
customer_id=user_id,
reference_code=f"{course_key}_{user_id}_{datetime.now().timestamp()}"
)
payment = await self.client.create_payment_request(payment_req)
payment_id = payment["id"]
# Get QR code URL
checkout_url = await self.client.get_checkout_url(payment_id)
# In a real agent, you'd send this via Telegram/Discord/WhatsApp:
# - Display QR code image
# - Provide checkout_url as clickable link
# - Message: "Scan to complete purchase"
return {
"status": "awaiting_payment",
"course": course["name"],
"amount": course["price"],
"qr_url": checkout_url,
"payment_id": payment_id
}
async def confirm_and_deliver(self, payment_id: str, user_id: str):
"""
Called after user scans QR. Waits for payment, then delivers course.
"""
try:
# Wait up to 10 minutes for payment
settlement = await self.client.await_settlement(
payment_id=payment_id,
timeout_seconds=600
)
# Payment confirmed! Deliver course
course_link = self._generate_course_access(user_id, payment_id)
return {
"status": "success",
"message": "Payment received! Course access below:",
"course_link": course_link,
"transaction_id": settlement["transaction_id"]
}
except asyncio.TimeoutError:
return {
"status": "timeout",
"message": "Payment not received. Please try again."
}
def _generate_course_access(self, user_id: str, payment_id: str) -> str:
"""Generate unique course access token."""
# In production: create database record, generate JWT, etc.
return f"https://courses.example.com/access?user={user_id}&payment={payment_id}"
In action:
1. User: "Sell me the Python Async course"
2. Agent calls sell_course() → receives QR URL
3. Agent displays QR + text: "Scan to pay 299,000 VND"
4. User scans, transfers money
5. confirm_and_deliver() detects payment in 2-3 seconds
6. Agent: "✓ Payment confirmed! Here's your course link..."
The entire flow is automatic. Your agent never saw the money.
Advanced Tips & Patterns
Handling Multiple Concurrent Payments
If you're running a high-traffic agent, you might have 50 payment requests pending. Don't block the main loop:
import asyncio
from typing import Dict, List
class PaymentTracker:
def __init__(self, client: AgentPayClient):
self.client = client
self.pending: Dict[str, asyncio.Task] = {}
async def track_payment(self, payment_id: str, callback):
"""
Fire-and-forget settlement tracking.
Calls callback(result) when payment lands.
"""
async def _wait():
try:
result = await self.client.await_settlement(
payment_id=payment_id,
timeout_seconds=600
)
await callback(payment_id, result)
except asyncio.TimeoutError:
await callback(payment_id, {"status": "timeout"})
task = asyncio.create_task(_wait())
self.pending[payment_id] = task
return task
async def cleanup_completed(self):
"""Remove completed tasks from tracking."""
completed = [
pid for pid, task in self.pending.items()
if task.done()
]
for pid in completed:
del self.pending[pid]
Error Handling & Retries
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10)
)
async def resilient_settlement_check(client, payment_id):
"""
Retry up to 3 times with exponential backoff.
Handles network glitches gracefully.
"""
return await client.await_settlement(payment_id)
Webhook Confirmation (Alternative to Polling)
For production systems, avoid polling:
# Instead of await_settlement polling, your bank sends you a webhook:
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/webhooks/payment-confirmed")
async def handle_settlement_webhook(request: Request):
"""
Bank POST's here when payment lands.
Much more efficient than polling.
"""
payload = await request.json()
payment_id = payload["payment_id"]
amount = payload["amount"]
# Verify signature (prevent spoofing)
signature = request.headers.get("X-AgentPay-Signature")
if not verify_signature(payload, signature):
return {"error": "Invalid signature"}, 401
# Trigger fulfillment (deliver course, etc.)
await fulfill_order(payment_id)
return {"status": "received"}
Check the docs for webhook configuration.
Do's and Don'ts
| Do | Don't |
|---|---|
Store reference_code in your database to link payments to orders |
Store VietQR codes—they're one-time use |
Handle asyncio.TimeoutError gracefully (offer to retry) |
Assume payment is confirmed before await_settlement() returns |
| Use MCP server for Claude/LLM agents—it handles OAuth/secrets | Hardcode API keys in your agent prompt |
| Rotate API keys every 90 days | Reuse payment IDs across different transactions |
| Test in sandbox mode first (contact your bank) | Call await_settlement() more than once per payment |
| Log transaction IDs for reconciliation | Expose customer bank account numbers in logs |
FAQ
Q: Does AgentPay VN hold my money?
No. AgentPay VN never touches your funds. The VietQR points directly at your merchant bank account. We're a payment orchestration layer—you're always in control.
Q: How fast is settlement?
2-3 seconds on average from scan to confirmation. VietQR is faster than card networks because it's bank-to-bank within Vietnam. By contrast, card processors often take 24-48 hours.
Q: What if my AI agent crashes during payment?
The payment still goes to your bank account—that's the beauty of QR codes. Your agent can recover by checking await_settlement() when it restarts. Store payment_id in your database so you can resume.
Q: Can I use AgentPay VN outside Vietnam?
Currently no. VietQR and the underlying bank APIs are Vietnam-specific. We're exploring Asia expansion—watch the GitHub.
Key Takeaways
- AgentPay VN is minimal: 3 methods, ~50 lines of code to add payments to your Python agent
- It's secure: No sensitive data ever touches your agent—funds bypass directly to your bank
- It's fast: VietQR settlement in seconds, perfect for real-time agent interactions
- It's open-source (MIT): Audit the code yourself
- MCP integration means Claude agents can use it natively: Just add the JSON config
- Real-world use cases exist: Course sales, café orders, service bookings, donations
- Error handling matters: Always catch
TimeoutErrorand implement retries for production
Next Steps
- Install now:
pip install agentpay-vn - Read the full docs: https://agentpay.servicesai.vn/v1/docs
- Star the repo: https://github.com/phuocdu/agentpay-vn (open-source, MIT license)
- Get merchant credentials: Contact your Vietnamese bank's API team
- Build your agent: Use the examples above as templates
Your AI agent just became a payment-accepting system. Welcome to the future of conversational commerce in Vietnam.