VietQR Payment Automation for AI Agents: Stripe Alternative
The Problem: AI Agents Can't Accept Vietnamese Payments Today
You've built an AI agent that sells something valuable—Vietnamese language courses, consulting hours, or SaaS subscriptions. Your agent can chat, reason, and persuade users to buy. But the moment someone says "I want to pay," your agent hits a wall.
Stripe doesn't fully support VietQR bank transfers in Vietnam. International payment gateways charge 3–5% fees, eat into margins, and require USD bank accounts. Momo and ZaloPay APIs are fragmented and SDK-poor. Your agent sits there, unable to collect money directly into your Vietnamese merchant bank account.
This is the gap AgentPay VN fills. It's a lightweight, open-source Python SDK + MCP server that lets AI agents collect payments via VietQR—Vietnam's native QR code standard—with zero money-holding. The QR points straight at your bank. A webhook confirms settlement. Done.
In this tutorial, you'll learn to integrate VietQR payments into an AI agent in under 30 minutes.
Why VietQR Beats Traditional Solutions for AI Agents
VietQR is Vietnam's interbank QR code standard, adopted by all major banks (Vietcombank, Techcombank, MB, ACB, etc.). It's instant, free to the merchant, and trusted by 99% of Vietnamese smartphone users.
Why AgentPay VN is better than rolling your own:
- Agent-native design: Built specifically for AI workflows (Claude, GPT, local LLMs). The MCP server exposes payment tools to Claude as structured functions.
- No money-holding: Funds go directly to your bank account. No escrow, no platform risk.
- Bank-confirmed settlement: Payment webhooks verify the actual transfer completed (not just a pending order).
- MIT open-source: Inspect the code, self-host, no vendor lock-in.
- Vietnam-optimized: Handles VietQR quirks (bank codes, transfer limits, consumer behavior).
Compare to Stripe (5% fee, USD settlement), Momo (2% fee, delayed settlement), or building raw VietQR yourself (regulatory nightmare, 2–3 months of work).
Installation & Setup: 3 Commands
Step 1: Install the SDK
pip install agentpay-vn
Verify the install:
python -c "import agentpay_vn; print(agentpay_vn.__version__)"
Step 2: Configure Your Merchant Account
You'll need:
- Bank account in Vietnam (any major bank).
- Bank code (e.g., 970401 for Techcombank, 970010 for Vietcombank). Full list here.
- Account number and account holder name.
Set environment variables:
export AGENTPAY_BANK_CODE="970401"
export AGENTPAY_ACCOUNT_NUMBER="1234567890"
export AGENTPAY_ACCOUNT_NAME="Nguyen Van A"
export AGENTPAY_WEBHOOK_SECRET="your-secret-key"
Step 3: Run the MCP Server (for Claude Integration)
agentpay-mcp
The server runs on http://localhost:3000 by default. (Optional if using the SDK directly; required for Claude integration.)
Core Payment Flow: 3 Lines
AgentPay VN abstracts payment collection into three methods:
# 1. Create a payment request
request = create_payment_request(amount=500000, description="Online Course")
# 2. Send checkout URL to user
user.send_message(f"Pay here: {request.checkout_url}")
# 3. Wait for settlement confirmation
await_settlement(request_id=request.id, timeout_seconds=300)
Let's expand this into a real example.
Code Example: Building a Course Bot with AgentPay VN
Imagine a simple AI agent that sells a Python course. Here's the payment integration:
from agentpay_vn import (
create_payment_request,
await_settlement,
PaymentStatus
)
import time
class CourseBot:
def __init__(self):
self.course_price = 500000 # 500,000 VND
self.webhook_timeout = 300 # 5 minutes
def sell_course(self, user_id: str, user_phone: str) -> dict:
"""
Main payment flow: create request → send URL → await settlement.
"""
# Step 1: Create payment request
# amount: price in VND
# description: shown to user in their banking app
# reference: your internal order ID
payment = create_payment_request(
amount=self.course_price,
description="Python Mastery Course - 12 weeks",
reference=f"course_{user_id}_{int(time.time())}"
)
# Step 2: Compose checkout message
checkout_msg = f"""
📚 Course Ready to Buy!
Price: {self.course_price:,} VND
Scan this QR code to pay:
{payment.qr_image_url}
Or click: {payment.checkout_url}
⏰ Valid for 5 minutes.
"""
# Send to user (via Telegram, email, web, etc.)
print(checkout_msg)
# Step 3: Wait for settlement webhook
# The bank confirms the transfer → webhook fires → we resume
try:
result = await_settlement(
request_id=payment.id,
timeout_seconds=self.webhook_timeout
)
if result.status == PaymentStatus.SETTLED:
return {
"success": True,
"message": "Payment confirmed! Course access granted.",
"settlement_amount": result.amount,
"transaction_id": result.bank_transaction_id
}
else:
return {"success": False, "message": "Payment failed or timed out."}
except TimeoutError:
return {"success": False, "message": "Payment took too long."}
# Usage
bot = CourseBot()
result = bot.sell_course(user_id="user_123", user_phone="0912345678")
print(result)
Line-by-line breakdown:
- Line 17–26:
create_payment_request()generates a unique payment session. Returns aPaymentobject with.id,.qr_image_url(PNG base64), and.checkout_url(clickable link for mobile). - Line 29–40: Format the checkout message. The QR code and URL are both valid; users scan QR in banking apps or click the link on mobile.
- Line 43–60:
await_settlement()blocks until the bank webhook confirms the transfer (or timeout). It returns aSettlementResultwith status, actual amount, and bank transaction ID. - Line 53:
PaymentStatus.SETTLEDmeans the bank confirmed the funds arrived in your account.
Integrating AgentPay VN with Claude via MCP
If you want Claude (or another LLM) to call payment functions autonomously, use the MCP server.
MCP Configuration (Claude Desktop)
Add this to your Claude Desktop claude_desktop_config.json (typically ~/.claude_desktop_config.json on macOS/Linux):
{
"mcpServers": {
"agentpay-vn": {
"command": "agentpay-mcp",
"args": [],
"env": {
"AGENTPAY_BANK_CODE": "970401",
"AGENTPAY_ACCOUNT_NUMBER": "1234567890",
"AGENTPAY_ACCOUNT_NAME": "Nguyen Van A",
"AGENTPAY_WEBHOOK_SECRET": "your-secret-key"
}
}
}
}
Restart Claude Desktop. Claude will now see tools like:
- create_payment_request(amount, description, reference)
- await_settlement(request_id, timeout_seconds)
- get_payment_status(request_id)
Claud can call these in natural conversation:
User: "I want to buy your course for 500,000 VND."
Claude: I'll create a payment request for you.
[Claude calls create_payment_request(amount=500000, description="Python Mastery Course")]
Claude: Here's your payment QR: [image]. Valid for 5 minutes.
[User scans and pays]
[Bank webhook fires, await_settlement() returns SETTLED]
Claude: ✅ Payment confirmed! Sending course access link...
Real-World Walkthrough: Café Order Bot
Let's build a concrete example: a café that sells coffee subscriptions via an AI bot.
Scenario: - Café owner wants to sell monthly coffee boxes (300,000 VND/month). - AI bot (Claude) takes orders on Facebook Messenger. - Bot collects payment, confirms order, sends tracking number.
Implementation:
from agentpay_vn import create_payment_request, await_settlement
import asyncio
from datetime import datetime
class CafeSubscriptionBot:
def __init__(self):
self.subscription_price = 300000
self.orders = {}
async def handle_order(self, customer_name: str, email: str):
"""
Full order flow: payment → confirmation → fulfillment.
"""
# Create payment
payment = create_payment_request(
amount=self.subscription_price,
description=f"Monthly Coffee Box - {datetime.now().strftime('%B %Y')}",
reference=f"cafe_sub_{customer_name}_{int(datetime.now().timestamp())}"
)
# Store order metadata
self.orders[payment.id] = {
"customer_name": customer_name,
"email": email,
"status": "pending_payment"
}
# Send checkout URL
checkout_msg = f"""Hi {customer_name},
Thank you for subscribing! 🎉
Pay 300,000 VND to confirm your monthly box:
{payment.checkout_url}
After payment, we'll ship within 24 hours.
"""
print(checkout_msg) # In production, send via SMS/email/Messenger
# Wait for payment
try:
settlement = await_settlement(
request_id=payment.id,
timeout_seconds=600 # 10 minutes
)
if settlement.status.value == "settled":
# Mark order as paid
self.orders[payment.id]["status"] = "paid"
self.orders[payment.id]["transaction_id"] = settlement.bank_transaction_id
# Trigger fulfillment
tracking_number = self.ship_order(customer_name, email)
confirmation = f"""✅ Payment received!
Tracking: {tracking_number}
Ships today, arrives in 2-3 days.
"""
print(confirmation)
return {"success": True, "tracking": tracking_number}
except TimeoutError:
self.orders[payment.id]["status"] = "abandoned"
print(f"❌ Payment expired for {customer_name}")
return {"success": False, "error": "timeout"}
def ship_order(self, customer_name: str, email: str) -> str:
"""Dummy fulfillment (in production: call shipping API)."""
return f"VN{datetime.now().strftime('%Y%m%d%H%M%S')}"
# Run
async def main():
bot = CafeSubscriptionBot()
await bot.handle_order("Tran Quang", "tran@example.com")
if __name__ == "__main__":
asyncio.run(main())
This bot: 1. Creates a payment request tied to the order. 2. Sends the checkout URL to the customer. 3. Blocks until the bank confirms payment. 4. Automatically ships the order.
No manual reconciliation. No payment verification headaches.
Do's and Don'ts
| ✅ Do | ❌ Don't |
|---|---|
Use await_settlement() to confirm payment before fulfilling |
Assume payment is done until await_settlement() returns SETTLED |
| Set a reasonable timeout (5–10 min for quick purchases, 1h for large orders) | Timeout immediately; give users time to complete payment |
Store reference ID in your database for reconciliation |
Forget to log transaction IDs; you'll have no audit trail |
| Test with a small amount (50,000 VND) first | Deploy to production before testing end-to-end |
| Expire checkout URLs after 5–10 minutes | Keep URLs valid indefinitely; users might pay old invoices |
| Webhook to automatically confirm orders | Manually check bank account; you'll miss payments |
| Handle timeout exceptions gracefully | Crash if payment takes > 5 seconds |
Advanced Tips
1. Batch Payments (B2B Orders)
If you're selling to businesses, you might need to split a 10M VND order into multiple 3M VND payments (per-transaction limits):
def split_large_payment(total_amount: int, per_split_limit: int = 3000000):
num_splits = (total_amount + per_split_limit - 1) // per_split_limit
return [per_split_limit] * (num_splits - 1) + [total_amount % per_split_limit or per_split_limit]
# Example: 10M splits into [3M, 3M, 3M, 1M]
splits = split_large_payment(10000000)
for amount in splits:
payment = create_payment_request(amount=amount, description=f"Part {splits.index(amount)+1}")
2. Webhook Verification
Always verify the webhook signature to prevent forged settlement notifications:
from agentpay_vn import verify_webhook_signature
def handle_webhook(headers: dict, body: str):
signature = headers.get("X-AgentPay-Signature")
secret = os.getenv("AGENTPAY_WEBHOOK_SECRET")
if not verify_webhook_signature(body, signature, secret):
raise ValueError("Invalid webhook signature")
# Process settlement safely
settlement_data = json.loads(body)
print(f"Payment settled: {settlement_data['request_id']}")
3. Retry Logic for Flaky Networks
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
def robust_await_settlement(request_id: str):
return await_settlement(request_id=request_id, timeout_seconds=300)
# Automatically retries up to 3 times with backoff
4. Store QR Codes as Images
The qr_image_url is a base64 PNG. Save it for offline use:
import base64
from PIL import Image
from io import BytesIO
payment = create_payment_request(amount=500000, description="Course")
qr_base64 = payment.qr_image_url.split(",")[1] # Strip data:image/png;base64, prefix
qr_png = base64.b64decode(qr_base64)
img = Image.open(BytesIO(qr_png))
img.save(f"payment_{payment.id}.png")
FAQ
Q: Does AgentPay VN hold my money? A: No. The QR code and checkout URL point directly to your bank account. AgentPay VN never touches the funds—it only provides the payment infrastructure. Settlement goes straight to your bank within seconds.
Q: What are transaction limits? A: Per-transaction limits vary by bank (typically 3–10M VND per transfer). Use the batch splitting logic above for large orders.
Q: How do I handle currency (should prices be in VND)?
A: All prices are in VND (Vietnamese Dong). If you serve international customers, convert to VND before calling create_payment_request(). AgentPay VN doesn't handle multi-currency.
Q: Is my merchant account verified/KYC required? A: Your bank account must be real and verified (which it is if you use it normally). AgentPay VN doesn't require additional KYC—it just forwards payments to your existing bank account.
Q: Can I use AgentPay VN with Telegram/Discord bots?
A: Yes. Use the SDK directly (no MCP needed). Call create_payment_request(), send the checkout_url to the user, then await_settlement() when they confirm payment.
Key Takeaways
- VietQR is Vietnam's payment standard: Trusted by 99% of users, zero fees to merchants, instant settlement.
- AgentPay VN eliminates payment integration pain: 3-line flow, no money-holding, bank-verified settlements.
- Setup takes 5 minutes:
pip install agentpay-vn, set env vars, done. - MCP integration makes Claude autonomous: Your AI agent can collect payments without human intervention.
- Real-world use cases are immediate: Course sales, café subscriptions, SaaS trials, consulting bookings—all work today.
- Open-source + MIT license: Inspect code, self-host, no vendor lock-in.
Next Steps
- Install AgentPay VN:
pip install agentpay-vn - Read the full docs: https://agentpay.servicesai.vn/v1/docs
- Clone the GitHub repo for examples: https://github.com/phuocdu/agentpay-vn
- Test with a small order (50,000 VND) in your agent.
- Deploy to production once you've verified the full flow.
Your AI agent is now a payment-collecting machine. Happy selling! 🚀