AI Agent VietQR Payments in Python: AgentPay VN
The Pain: Your AI Agent Needs Money, But Can't Hold It
Imagine you've built a brilliant AI chatbot that tutors Vietnamese students in math. It works beautifully—students love it, engagement is high, and you're ready to charge $5 per session. But here's the problem: your bot has no way to collect payment that feels native to Vietnam, doesn't require you to integrate a heavy payment processor, and doesn't involve your agent holding customer funds (a legal and security nightmare).
You search for solutions. Stripe requires bank account verification that takes weeks. PayPal charges international fees that erode your margins. Building a custom payment system means wrestling with bank APIs, PCI compliance, and settlement schedules.
Then there's VietQR—Vietnam's beloved interbank QR code standard, already built into every major Vietnamese bank's mobile app. It's instant, it's trusted, and customers scan it daily. The missing piece? A way for your AI agent to generate and manage VietQR payment requests without holding money or building payment infrastructure from scratch.
That's exactly what AgentPay VN solves.
What Is AgentPay VN? The 30-Second Version
AgentPay VN is an open-source Python SDK (MIT license) paired with an MCP server that lets your AI agents create VietQR payment requests in three lines of code. Here's what makes it radical:
- No holding funds: Payment QR codes point directly to your merchant bank account. AgentPay never touches the money.
- Settlement confirmation: Bank feeds automatically confirm when payment lands in your account.
- AI-native: Built for Claude, GPT, and other LLM agents via the Model Context Protocol (MCP).
- Zero friction for Vietnamese users: VietQR is already in every phone. No new app downloads, no new habit changes.
Installation is one line: pip install agentpay-vn. The MCP server runs as agentpay-mcp. The workflow is: create request → send checkout URL → await settlement.
The Three-Line Payment Flow
Let's walk through the actual mechanics before we build anything.
The Conceptual Flow
1. Your agent calls → create_payment_request(amount, description, timeout)
↓
2. AgentPay returns a checkout URL with embedded VietQR
↓
3. Customer scans QR, pays from their bank app
↓
4. Bank feeds confirm settlement → your agent unlocks the service
No intermediate wallet. No escrow. No agency relationship. The QR code is cryptographically tied to your merchant bank account, and settlement is a bank-to-bank transfer you can verify in real time.
Setting Up Your First Payment Request in Python
Step 1: Install the SDK
pip install agentpay-vn
That's it. No additional dependencies, no configuration files yet.
Step 2: Create Your First Payment Request
Here's a real, working example:
from agentpay_vn import AgentPayClient
from datetime import datetime, timedelta
# Initialize the client with your merchant credentials
# (You'll get these from AgentPay VN dashboard after bank verification)
client = AgentPayClient(
merchant_id="YOUR_MERCHANT_ID",
merchant_secret="YOUR_MERCHANT_SECRET"
)
# Create a payment request for a tutoring session
payment_request = client.create_payment_request(
amount=50000, # VND (roughly $2 USD)
description="Math tutoring session - Algebra Level 2",
customer_id="student_12345",
order_id="session_20250115_001",
expires_in=300 # 5 minutes to pay
)
# Payment request now contains:
# - checkout_url: Send this to the student
# - qr_code: The actual VietQR string (for display)
# - request_id: Track this payment internally
# - expires_at: When the QR becomes invalid
print(f"Checkout URL: {payment_request['checkout_url']}")
print(f"Request ID: {payment_request['request_id']}")
Line-by-line breakdown:
- Lines 1-3: Import the AgentPay client and time utilities.
- Lines 6-10: Initialize with your merchant credentials (obtained after you register and verify your bank account with AgentPay VN).
- Lines 12-21:
create_payment_request()builds the payment, specifying amount in VND, a human description, customer tracking ID, order ID, and expiration (in seconds). This returns a dictionary with checkout URL and QR data. - Lines 23-28: Extract the checkout URL (send to customer) and request ID (store for settlement tracking).
Step 3: Wait for Settlement
Now your agent needs to know when the customer has paid. This is where bank feeds come in:
import asyncio
from agentpay_vn import AgentPayClient
client = AgentPayClient(
merchant_id="YOUR_MERCHANT_ID",
merchant_secret="YOUR_MERCHANT_SECRET"
)
async def wait_for_payment(request_id, timeout_seconds=300):
"""
Poll the AgentPay settlement service until payment confirms.
In production, you'd also listen to webhooks for real-time updates.
"""
start = asyncio.get_event_loop().time()
while (asyncio.get_event_loop().time() - start) < timeout_seconds:
# Check settlement status via bank feed
settlement = await client.await_settlement(request_id)
if settlement['status'] == 'confirmed':
# Payment landed in your bank account
print(f"✓ Payment confirmed: {settlement['amount']} VND")
print(f" Settlement ID: {settlement['settlement_id']}")
return settlement
elif settlement['status'] == 'failed':
# QR expired or transaction reversed
print("✗ Payment failed or expired.")
raise Exception("Payment not completed")
# Wait 2 seconds before checking again
await asyncio.sleep(2)
raise TimeoutError("Payment confirmation timeout")
# Usage in your agent logic
async def handle_tutoring_session(student_id, topic):
# Create payment
payment = client.create_payment_request(
amount=50000,
description=f"Tutoring: {topic}",
customer_id=student_id
)
# Send checkout URL to student (via chat, email, SMS, etc.)
await send_to_student(payment['checkout_url'])
# Wait for settlement
settlement = await wait_for_payment(payment['request_id'])
# Unlock tutoring content
await unlock_course_materials(student_id, topic)
return settlement
# Run it
asyncio.run(handle_tutoring_session("student_12345", "Calculus Basics"))
What's happening:
- Lines 9-31:
wait_for_payment()polls the settlement API every 2 seconds. When status isconfirmed, the bank has processed your payment.failedmeans the QR expired or customer cancelled. This is non-blocking async code, so your agent can handle multiple students in parallel. - Lines 33-45: Full workflow: create request, send URL, await settlement, unlock content.
- Line 48: Run the async function.
In production, replace polling with webhooks for true real-time updates (details in AgentPay VN docs).
Integrating with Claude via MCP Server
If you're building an agent that runs on Claude (or another LLM via MCP), you don't call the Python SDK directly—you configure the MCP server and Claude handles the function calls.
MCP Server Configuration
Add this to your Claude configuration file (e.g., claude_desktop_config.json):
{
"mcpServers": {
"agentpay": {
"command": "agentpay-mcp",
"args": [
"--merchant-id", "YOUR_MERCHANT_ID",
"--merchant-secret", "YOUR_MERCHANT_SECRET",
"--bank-name", "vietcombank" // or "techcombank", "acb", etc.
],
"env": {
"AGENTPAY_WEBHOOK_URL": "https://yourdomain.com/webhooks/agentpay"
}
}
}
}
Now Claude automatically has access to these tools:
- create_payment_request – Generate VietQR
- await_settlement – Check payment status
- get_settlement_history – Retrieve past transactions
When Claude detects that a user should pay, it calls these tools directly without you writing integration code.
Real-World Example: An AI Tutoring Bot
Let's build a complete scenario: a Vietnamese language tutoring bot that charges per lesson.
The Scenario
You're running a platform where Claude tutors students in Vietnamese grammar. Each lesson costs 100,000 VND (~$4 USD). When a student asks to start a lesson:
- Claude asks for payment.
- Claude generates a VietQR checkout.
- Student scans and pays from their bank app.
- Claude confirms settlement and delivers the lesson.
The Code
from agentpay_vn import AgentPayClient
import asyncio
client = AgentPayClient(
merchant_id="tutor_bot_001",
merchant_secret="your_secret_key"
)
class TutoringBot:
def __init__(self):
self.client = client
self.lessons = {}
async def start_lesson(self, student_name, student_id, grammar_topic):
"""
Student initiates a tutoring session. Payment required upfront.
"""
lesson_id = f"lesson_{student_id}_{int(asyncio.get_event_loop().time())}"
# Create payment request
payment = self.client.create_payment_request(
amount=100000,
description=f"Vietnamese Grammar: {grammar_topic}",
customer_id=student_id,
order_id=lesson_id,
expires_in=600 # 10 minutes to pay
)
# Store lesson metadata
self.lessons[lesson_id] = {
"student_name": student_name,
"topic": grammar_topic,
"request_id": payment['request_id'],
"status": "awaiting_payment"
}
# Respond to student with checkout link
checkout_message = f"""
Hello {student_name}! 👋
To start your lesson on {grammar_topic}, please pay 100,000 VND.
🔗 Click to pay: {payment['checkout_url']}
⏱️ Payment link expires in 10 minutes.
I'll confirm payment and start your lesson immediately!
"""
print(checkout_message)
# Wait for settlement (non-blocking)
try:
settlement = await self.client.await_settlement(
payment['request_id'],
timeout=600
)
self.lessons[lesson_id]["status"] = "paid"
self.lessons[lesson_id]["settlement_id"] = settlement['settlement_id']
# Lesson content delivered
await self.deliver_lesson(student_name, grammar_topic)
except Exception as e:
self.lessons[lesson_id]["status"] = "failed"
print(f"Payment failed: {e}")
async def deliver_lesson(self, student_name, topic):
"""
Content delivery after payment confirmed.
"""
lesson_content = f"""
✅ Payment confirmed! Let's start your lesson.
📚 Topic: {topic}
**Lesson 1: Basic Tones**
Vietnamese has 6 tones. Here's how to practice:
- Level tone: "ma" (ghost)
- Rising tone: "má" (mother)
- Question tone: "mả" (tomb)
... [full lesson content]
Let's practice! Try saying these words after me...
"""
print(lesson_content)
# Usage
bot = TutoringBot()
await bot.start_lesson("Hung", "student_789", "Vietnamese Tones")
What Happens Behind the Scenes
- Payment creation:
create_payment_request()generates a unique VietQR QR code pointing to your bank account. - Student action: Hung opens the checkout link on their phone, scans the QR in their bank app (e.g., Vietcombank, Techcombank, ACB), and pays 100,000 VND.
- Bank settlement: The bank confirms the transfer to your merchant account via ACH/real-time transfer.
- Settlement polling:
await_settlement()detects the bank feed confirmation and returnsstatus='confirmed'. - Lesson unlock: Your bot delivers lesson content immediately.
No escrow, no delays, no middleman holding funds.
Do's and Don'ts: Common Pitfalls
| Do | Don't |
|---|---|
Use await_settlement() with a timeout to prevent infinite waiting |
Don't poll settlement more than once per 2 seconds (wastes API calls) |
Store the request_id in your database for reconciliation |
Don't forget to validate merchant_id and merchant_secret in production |
| Send checkout URL via multiple channels (SMS, email, chat) for better UX | Don't hardcode credentials; use environment variables |
| Listen to webhooks for real-time settlement updates | Don't assume QR codes are valid indefinitely; always set expires_in |
| Test with your actual bank account in sandbox mode first | Don't charge customers before status='confirmed' |
Keep your merchant_secret private; rotate it every 90 days |
Don't display settlement IDs to customers (for security) |
Advanced Tips for Production
1. Webhook Integration for Real-Time Updates
Instead of polling, register a webhook to get instant payment notifications:
from agentpay_vn import AgentPayClient
from flask import Flask, request
app = Flask(__name__)
client = AgentPayClient(merchant_id="...", merchant_secret="...")
@app.route('/webhooks/agentpay', methods=['POST'])
def handle_settlement_webhook():
"""
AgentPay sends a POST here when payment confirms.
"""
payload = request.json
# Verify webhook signature (prevents spoofing)
if not client.verify_webhook_signature(payload, request.headers.get('X-AgentPay-Signature')):
return {'error': 'Invalid signature'}, 403
request_id = payload['request_id']
status = payload['status'] # 'confirmed' or 'failed'
amount = payload['amount']
if status == 'confirmed':
# Unlock immediately (no polling needed)
unlock_lesson(request_id, amount)
return {'ok': True}, 200
2. Batch Reconciliation
Daily reconciliation with your bank to catch any discrepancies:
from agentpay_vn import AgentPayClient
from datetime import datetime, timedelta
client = AgentPayClient(merchant_id="...", merchant_secret="...")
async def reconcile_daily():
"""
Check settlements from past 24 hours against your bank statement.
"""
yesterday = (datetime.now() - timedelta(days=1)).isoformat()
settlements = await client.get_settlement_history(
start_date=yesterday,
status='confirmed'
)
total_received = sum(s['amount'] for s in settlements)
print(f"Verified {len(settlements)} payments, total: {total_received} VND")
# Compare with your bank statement
# Alert if there's a mismatch
3. Retry Logic for Transient Failures
import asyncio
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=2, max=10))
async def create_payment_with_retry(amount, description, customer_id):
"""
Retry payment creation if the API is temporarily down.
"""
return client.create_payment_request(
amount=amount,
description=description,
customer_id=customer_id,
expires_in=300
)
Frequently Asked Questions
Q1: Does AgentPay hold my money?
A: No, never. AgentPay is a payment orchestration layer—the VietQR points directly to your merchant bank account. Settlement happens bank-to-bank. AgentPay's role is to generate the QR, confirm the transfer via bank feeds, and notify your agent. You own 100% of the funds immediately.
Q2: Which Vietnamese banks are supported?
A: AgentPay supports all major Vietnamese banks that participate in the VietQR interbank network, including Vietcombank, Techcombank, ACB, MB Bank, Agribank, Sacombank, and 25+ others. Customers can pay from any Vietnamese bank app.
Q3: How long does settlement take?
A: VietQR transfers are typically settled in real-time (within 30 seconds to 2 minutes). AgentPay confirms via bank feeds, so await_settlement() usually returns within that window. The API has a default timeout of 5 minutes.
Q4: What if a customer's payment fails?
A: await_settlement() will return status='failed' if the QR expires (default 10 minutes) or the customer cancels. You can automatically regenerate a new checkout URL and ask them to retry. The original request_id is logged for audit purposes.
Q5: Is there a minimum or maximum payment amount?
A: Amounts must be >= 1,000 VND and <= 50,000,000 VND per transaction (per Vietnamese banking regulations). For larger amounts, you can process multiple payments in sequence.
Key Takeaways
- AgentPay VN is AI-native payment infrastructure: Three-line flow, zero holding of funds, direct bank settlement.
- Installation is trivial:
pip install agentpay-vnand you're ready to code. - The payment flow is: create → send → await: Your agent generates a VietQR, shares the checkout URL, and waits for bank confirmation.
- VietQR is deeply integrated into Vietnamese banking: Customers already trust it, already use it, and don't need new apps or wallets.
- Real-time settlement confirmation via bank feeds: No escrow, no intermediary, no delays.
- MCP integration means Claude calls payment functions natively: If you use Claude, AgentPay is automatically available as a tool.
- Production-ready: Webhook support, reconciliation, retry logic, and audit trails come standard.
- No middleman risk: Your funds arrive in your bank account. AgentPay never touches money, never holds balances, never freezes accounts.
Get Started Now
- Install the SDK:
pip install agentpay-vn - Register your merchant account: AgentPay VN Dashboard
- Verify your bank account (takes 24–48 hours)
- Copy your merchant credentials to your
.envfile - Run the examples above in a Python script or Jupyter notebook
- Read the full API docs: https://agentpay.servicesai.vn/v1/docs
- Join the community: GitHub https://github.com/phuocdu/agentpay-vn
Your AI agent can now accept Vietnamese payments. Stop waiting for payment processors. Start collecting money in three lines of code.