VietQR Payment Automation for AI Agents: Stripe Alternative
Why Vietnamese AI Projects Fail at Payment Collection
You've just built an intelligent chatbot for your client—a language tutoring platform targeting Ho Chi Minh City professionals. The bot onboards students, tracks progress, and recommends premium lessons. One problem: when a student wants to enroll in a 4-week intensive course (₫2,000,000), your agent has no way to collect payment.
Stripe doesn't support VietQR in Vietnam without complex workarounds. PayPal's unpopular locally. International gateways add 3-5% fees. You're left manually processing bank transfers or building fragile custom integrations with Vietnamese banks—risky, slow, and unmaintainable.
This is where AgentPay VN changes the game. It's a lightweight, open-source Python SDK that lets your AI agents accept VietQR payments directly into the merchant's bank account—no middle-man, no escrow, no API keys juggling.
What Is AgentPay VN? The Core Concept
AgentPay VN is an MIT-licensed Python SDK + Model Context Protocol (MCP) server that enables AI agents to generate VietQR payment requests and await settlement confirmations. Here's the mental model:
- Agent initiates → Creates a unique payment request tied to a transaction ID
- Customer scans → VietQR code points directly to your merchant bank account
- Bank confirms → Webhook or polling tells your agent payment arrived
Crucially: AgentPay never touches your money. You're not running a payment processor. The funds go straight from customer to your bank account, and AgentPay just orchestrates the handshake.
Why this matters: - ✅ Compliant with Vietnam's VietQR standard - ✅ Instant settlement (no 2-3 day holds) - ✅ Works with any Vietnamese bank - ✅ Fits seamlessly into AI agent workflows (Claude, GPT, custom) - ✅ Zero monthly fees (open-source)
Installation & Quick Setup
Step 1: Install the SDK
pip install agentpay-vn
This adds the core Python module to your environment. Verify:
python -c "import agentpay_vn; print(agentpay_vn.__version__)"
Step 2: Set Environment Variables
Create a .env file in your project root:
BANK_ACCOUNT_NUMBER=0123456789
BANK_CODE=970400 # MB = 970400, Vietcombank = 970436, etc.
MERCHANT_NAME=My Shop
OTP_SECRET=your_otp_secret_if_using_2fa
Bank codes for major Vietnamese banks: - MB (MBBank): 970400 - Vietcombank: 970436 - Techcombank: 970407 - ACB: 970005
Step 3: Initialize the Client
from agentpay_vn import AgentPayClient
import os
client = AgentPayClient(
bank_account=os.getenv("BANK_ACCOUNT_NUMBER"),
bank_code=os.getenv("BANK_CODE"),
merchant_name=os.getenv("MERCHANT_NAME")
)
You're ready to accept payments.
The 3-Step Payment Flow Explained
Step 1: Create a Payment Request
When a customer decides to purchase, your agent creates a payment request. This generates a unique VietQR code tied to the transaction.
from agentpay_vn import AgentPayClient
import os
from datetime import datetime, timedelta
client = AgentPayClient(
bank_account="0123456789",
bank_code="970400",
merchant_name="TutorBot Academy"
)
# Customer enrolls in course
transaction_id = f"course_001_{int(datetime.now().timestamp())}"
amount_vnd = 2_000_000 # ₫2 million
user_email = "student@example.com"
# Create the payment request
payment_req = client.create_payment_request(
transaction_id=transaction_id,
amount=amount_vnd,
description="4-Week Intensive English Course",
customer_email=user_email,
expires_in_minutes=30 # QR expires in 30 mins
)
print(f"VietQR Code URL: {payment_req.qr_url}")
print(f"Checkout Link: {payment_req.checkout_url}")
print(f"Transaction ID: {payment_req.transaction_id}")
Line-by-line breakdown:
- transaction_id: Unique identifier for this sale. Use timestamps or UUIDs to avoid collisions.
- amount: Always in VND (Vietnamese Dong).
- description: Shown to customer on their bank statement.
- expires_in_minutes: Prevents stale QR codes. 30 minutes is standard.
- qr_url: Image URL of the VietQR code (PNG/SVG).
- checkout_url: Full-page checkout link (optional backup).
Step 2: Send Checkout URL to Customer
Your agent now delivers the QR code and payment link. In a web context:
# In your FastAPI/Flask endpoint
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.post("/enroll")
async def enroll_student(user_id: str, course_id: str):
# Create payment
payment_req = client.create_payment_request(
transaction_id=f"{user_id}_{course_id}",
amount=2_000_000,
description=f"Course {course_id}"
)
# Return QR for frontend to display
return JSONResponse({
"status": "awaiting_payment",
"qr_url": payment_req.qr_url,
"checkout_url": payment_req.checkout_url,
"amount_vnd": 2_000_000,
"expires_in_seconds": 30 * 60
})
Or for a ChatBot context, your Claude-powered agent might respond:
"Perfect! I've generated your enrollment link.
Scan this QR code with your banking app:
[QR IMAGE]
Or tap here for web checkout: [LINK]
Payment expires in 30 minutes."
Step 3: Await Settlement
Once the customer scans and pays, your agent must confirm receipt before granting access. AgentPay supports two modes:
Mode A: Polling (Simpler)
import time
from agentpay_vn.exceptions import PaymentNotFoundError, PaymentTimeoutError
transaction_id = "course_001_1704067200"
max_wait_seconds = 60 # Poll for up to 1 minute
try:
settlement = client.await_settlement(
transaction_id=transaction_id,
timeout_seconds=max_wait_seconds
)
print(f"✅ Payment settled!")
print(f"Amount received: ₫{settlement.amount_received}")
print(f"Bank reference: {settlement.bank_reference_id}")
print(f"Settled at: {settlement.settlement_timestamp}")
# Safely grant course access
db.update_student(user_id, status="enrolled", course_id=course_id)
except PaymentTimeoutError:
print("❌ Payment not received within 60 seconds")
# Don't grant access, let customer retry
except PaymentNotFoundError:
print("❌ Transaction not found (invalid ID?)")
Mode B: Webhook (Production)
For higher-traffic apps, use bank webhooks instead of polling:
from fastapi import FastAPI, Request
import hmac
import hashlib
app = FastAPI()
@app.post("/webhooks/agentpay")
async def handle_payment_webhook(request: Request):
"""
Bank sends settlement confirmation here.
Verify HMAC signature to prevent spoofing.
"""
payload = await request.json()
# Verify signature
signature = request.headers.get("X-AgentPay-Signature")
expected_sig = hmac.new(
key=os.getenv("WEBHOOK_SECRET").encode(),
msg=str(payload).encode(),
digestmod=hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected_sig):
return {"status": "unauthorized"}
# Process settlement
transaction_id = payload["transaction_id"]
amount = payload["amount"]
bank_ref = payload["bank_reference_id"]
# Update database immediately
db.update_student(
user_id=extract_user_from_txn(transaction_id),
status="enrolled",
bank_reference=bank_ref
)
# Send confirmation email
send_email(user_email, "Welcome to your course!")
return {"status": "processed"}
Real-World Example: Online Café Product Shop
Let's trace a complete flow for a coffee roastery selling beans online.
Scenario: Linh's Coffee Roastery has built an AI agent that recommends blends based on customer taste profiles, then automates orders and payment.
from agentpay_vn import AgentPayClient
from datetime import datetime
import json
client = AgentPayClient(
bank_account="1234567890",
bank_code="970400", # MBBank
merchant_name="Linh's Coffee Roastery"
)
class CoffeeShopAgent:
def recommend_and_checkout(self, customer_id, profile):
"""
Step 1: AI recommends blend based on profile
"""
if profile["roast_preference"] == "dark":
recommendation = {
"blend": "Robusta Dalat Dark Roast",
"price_vnd": 450_000, # ₫450k per 1kg bag
"quantity": 2
}
total = recommendation["price_vnd"] * recommendation["quantity"]
# Step 2: Create payment request
txn_id = f"cafe_{customer_id}_{int(datetime.now().timestamp())}"
payment = client.create_payment_request(
transaction_id=txn_id,
amount=total,
description=f"{recommendation['quantity']}kg {recommendation['blend']}",
customer_email=customer_id,
expires_in_minutes=15
)
# Step 3: Return checkout to agent/UI
return {
"recommendation": recommendation,
"total_vnd": total,
"qr_code_url": payment.qr_url,
"payment_link": payment.checkout_url,
"txn_id": txn_id
}
def confirm_and_ship(self, txn_id, customer_id):
"""
Step 4: Await payment, then trigger shipment
"""
try:
settlement = client.await_settlement(
transaction_id=txn_id,
timeout_seconds=120 # 2 min timeout
)
# Payment confirmed → create shipment
print(f"🎉 Order {txn_id} paid!")
print(f"Amount: ₫{settlement.amount_received}")
print(f"Bank ref: {settlement.bank_reference_id}")
# Trigger warehouse system
warehouse_api.create_shipment(txn_id, customer_id)
# Send SMS to customer
send_sms(customer_id, f"Your coffee order is being roasted! Shipping in 2 days.")
return {"status": "processing_shipment", "txn_id": txn_id}
except Exception as e:
print(f"❌ Payment failed: {e}")
return {"status": "payment_failed", "retry_url": payment.checkout_url}
# Usage
agent = CoffeeShopAgent()
# Customer conversation
profile = {"roast_preference": "dark", "origin": "vietnam", "grind": "whole_bean"}
checkout_info = agent.recommend_and_checkout("customer_123", profile)
print(f"Agent says: 'I recommend {checkout_info['recommendation']['blend']}'")
print(f"Show QR: {checkout_info['qr_code_url']}")
print(f"Total: ₫{checkout_info['total_vnd']:,}")
# After customer pays...
result = agent.confirm_and_ship(checkout_info['txn_id'], "customer_123")
This entire flow—recommendation, payment creation, awaiting settlement, and shipping trigger—happens autonomously.
Using AgentPay with Claude / MCP Server
To give Claude (or other AI models) direct payment capabilities, run the MCP server:
agentpay-mcp --bank-account "1234567890" --bank-code "970400" --merchant-name "My Business"
Then configure your Claude client (e.g., via Anthropic API):
{
"mcpServers": {
"agentpay": {
"command": "agentpay-mcp",
"args": [
"--bank-account", "1234567890",
"--bank-code", "970400",
"--merchant-name", "My Business"
],
"env": {
"BANK_ACCOUNT_NUMBER": "1234567890",
"BANK_CODE": "970400",
"MERCHANT_NAME": "My Business"
}
}
}
}
Now Claude can call tools like:
Claude: "The customer wants to buy a ₫5M subscription. Let me create a payment request."
[Claude calls: create_payment_request(amount=5000000, description="Monthly subscription")]
Claude: "Here's your QR code: [image]. Payment expires in 30 minutes."
AgentPay vs. Stripe: A Comparison
| Feature | AgentPay VN | Stripe |
|---|---|---|
| VietQR Support | ✅ Native | ❌ Workarounds only |
| Monthly Fees | ✅ None (open-source) | ❌ $20-300+ |
| Settlement Speed | ✅ Instant to bank | ❌ 2-3 days |
| Transaction Fee | ✅ None | ❌ 2.9% + ₫3k |
| Integration Effort | ✅ 10 lines of code | ❌ Complex OAuth |
| Works in Vietnam | ✅ Yes | ⚠️ Limited |
| AI Agent Native | ✅ MCP built-in | ❌ Requires custom wrappers |
| PCI Compliance | ✅ Bank-to-bank (no card data) | ✅ But adds overhead |
TL;DR: For Vietnamese merchants accepting local payments from local customers, AgentPay wins on cost, speed, and simplicity.
Do's and Don'ts
✅ DO:
- Store transaction IDs securely in your database (use UUIDs if possible)
- Set reasonable timeouts (30-120 seconds for polling)
- Use webhooks in production (polling is fine for testing)
- Verify bank_reference_id in settlement response (unique per transaction)
- Test with small amounts first (₫10,000 test payments)
- Log all transactions for reconciliation
❌ DON'T:
- Hardcode bank credentials (use environment variables always)
- Trust client-side amount validation (confirm on server before payment creation)
- Assume QR codes are immutable (regenerate if needed)
- Ignore payment timeouts (always handle the exception)
- Process the same settlement twice (check idempotency)
- Expose transaction IDs in client-side URLs (use session tokens instead)
Advanced Tips
Idempotency
If your network fails mid-request, retry safely:
idempotency_key = "order_12345_attempt_1"
payment = client.create_payment_request(
transaction_id="order_12345",
amount=1_000_000,
idempotency_key=idempotency_key # Same key = same result
)
Handling Failed Payments
def handle_failed_payment(txn_id, customer_email):
# Generate fresh QR with extended deadline
payment = client.create_payment_request(
transaction_id=f"{txn_id}_retry_1",
amount=amount,
expires_in_minutes=60, # Give more time
description=f"Retry for {txn_id}"
)
# Email customer
send_email(customer_email, f"""Your payment didn't go through.
New QR: [image]
Or pay here: {payment.checkout_url}
""")
Multi-Currency (VND only, but convert externally)
def create_payment_usd_to_vnd(amount_usd, exchange_rate=24500):
amount_vnd = int(amount_usd * exchange_rate)
return client.create_payment_request(
transaction_id=...,
amount=amount_vnd,
description=f"${amount_usd} USD = ₫{amount_vnd:,} VND"
)
FAQ
Q: Does AgentPay hold my money?
A: No. AgentPay is a transaction orchestrator only. Funds go directly from customer's bank → your bank account. AgentPay just confirms the settlement.
Q: What if a customer disputes the payment?
A: Disputes are handled by your bank directly. AgentPay provides the bank reference ID for easy reconciliation.
Q: Can I use AgentPay without internet?
A: The Python SDK is offline-first for QR generation, but settlement confirmation requires bank API connectivity. Use polling during brief outages.
Q: How do I test locally?
A: AgentPay provides a sandbox mode (coming in v1.1). Currently, use test amounts (₫10,000) with a test bank account.
Key Takeaways
- 🎯 AgentPay VN is a zero-fee, open-source Python SDK for VietQR payments in AI agents
- 💳 No escrow, no middle-man: funds settle instantly to your merchant bank account
- 🤖 MCP-native: integrate with Claude and other AI models with 3 lines of code
- 🚀 3-step flow: create_payment_request → send_qr → await_settlement
- 📊 Perfect for: Vietnamese e-commerce, SaaS, education platforms, and autonomous agents
- 🔒 Secure: bank-to-bank transfers, no card data exposure, HMAC-signed webhooks
- 💰 Save money: zero monthly fees compared to Stripe's $20-300+ in Vietnam
Get Started Now
Install AgentPay VN today:
pip install agentpay-vn
Read the full documentation:
https://agentpay.servicesai.vn/v1/docs
Explore the source code:
https://github.com/phuocdu/agentpay-vn
Questions? Open an issue on GitHub or reach out to the maintainers.
Your AI agents can now accept VietQR payments. Build something amazing! 🚀