Add VietQR Checkout to Your LLM Agent in 10 Minutes
The Problem: Your AI Agent Stops at the Sale
You've built an intelligent agent that engages customers brilliantly—it answers questions, builds trust, personalizes recommendations. But when it's time to convert, you hit a wall. Your chatbot can't accept payment. It hands off to a third-party checkout (losing context and conversion rates), or worse, asks the user to navigate away entirely.
Imagine a course-selling bot that can: - Chat about learning outcomes - Answer student objections in real-time - Generate a payment link right there in the conversation - Confirm settlement and unlock course access—all without a human in the loop
Or a café ordering agent that takes a customer's order and securely collects payment via their bank's QR code, with settlement confirmed within seconds.
That's what AgentPay VN unlocks. In this tutorial, you'll integrate VietQR payments into your LLM agent in under 10 minutes.
Why AgentPay VN? Three Reasons
1. It Never Touches Your Money Unlike payment platforms that hold funds in escrow, AgentPay VN generates QR codes that point directly at your merchant bank account. No intermediary. No settlement delays. No new custody risk.
2. Open-Source, No Vendor Lock-in Built on MIT license Python SDK. Audit the code. Deploy anywhere. No surprise pricing tiers.
3. Built for AI from Day One The MCP (Model Context Protocol) server integrates seamlessly with Claude and other agents. Your agent doesn't just call payment logic—it reasons about it naturally.
Installation: 30 Seconds
pip install agentpay-vn
That's it. The SDK is lightweight (no heavy dependencies). You also get the MCP server for Claude integration:
agentpay-mcp
The 3-Line Payment Flow
AgentPay VN distills payment collection to three core steps:
- Create a payment request → Define amount, description, merchant ID
- Send checkout URL → Agent shares the link (or QR) with the customer
- Await settlement → Confirm the bank feed; unlock whatever comes next
Let's code it.
Step-by-Step: Your First Payment Integration
Step 1: Import and Initialize
from agentpay_vn import PaymentClient
import os
# Initialize with your merchant details
# (Set these as environment variables for security)
merchant_id = os.getenv("AGENTPAY_MERCHANT_ID")
merchant_name = os.getenv("AGENTPAY_MERCHANT_NAME")
bank_account = os.getenv("AGENTPAY_BANK_ACCOUNT")
client = PaymentClient(
merchant_id=merchant_id,
merchant_name=merchant_name,
bank_account=bank_account
)
What's happening:
- merchant_id: Your unique AgentPay identifier (get from https://agentpay.servicesai.vn/v1/docs)
- merchant_name: Displayed on the VietQR code for customer confidence
- bank_account: Your actual bank account (in format: BANK_CODE|ACCOUNT_NUMBER)
Step 2: Create a Payment Request
# Example: Customer buying a Python course
payment_request = client.create_payment_request(
amount=299000, # VND
description="Python Advanced: Async & Concurrency",
customer_email="student@example.com",
order_id="ORDER_001_2024",
metadata={
"course_id": "python_async_101",
"student_name": "Nguyen Van A"
}
)
print(f"Checkout URL: {payment_request.checkout_url}")
print(f"QR Code: {payment_request.qr_code}")
print(f"Payment ID: {payment_request.payment_id}")
Line-by-line breakdown:
- amount: Cost in Vietnamese Dong. No decimals (299,000 VND, not 299.00)
- description: Shown on the QR code and settlement record (64 chars max for clarity)
- customer_email: Optional, but useful for follow-ups
- order_id: Your internal reference. Must be unique per request
- metadata: Attach custom data (course IDs, user IDs, etc.). Retrieved on settlement confirmation
The response gives you: - checkout_url: A shareable link (can be shortened or embedded) - qr_code: Base64-encoded QR image for immediate display - payment_id: Internal reference for tracking settlement
Step 3: Await Settlement
import asyncio
async def confirm_payment(payment_id, timeout_seconds=120):
"""
Poll for settlement confirmation.
In production, use webhooks for real-time updates.
"""
settlement = await client.await_settlement(
payment_id=payment_id,
timeout=timeout_seconds
)
if settlement.status == "confirmed":
print(f"✓ Payment confirmed!")
print(f" Amount: {settlement.amount} VND")
print(f" Settled at: {settlement.settled_at}")
print(f" Bank ref: {settlement.bank_reference}")
# Now unlock the course
course_id = settlement.metadata.get("course_id")
student_email = settlement.metadata.get("student_name")
return {"course_id": course_id, "student": student_email}
else:
print(f"Payment pending or failed: {settlement.status}")
return None
# Usage in your agent
result = asyncio.run(confirm_payment("payment_12345"))
What's important here:
- await_settlement blocks until the bank confirms the transfer (typically 5–30 seconds)
- settlement.status is either "confirmed", "pending", or "failed"
- settlement.bank_reference: The actual bank transaction ID (immutable proof)
- metadata: Your custom fields come back unchanged, so you know what was purchased
Integrating with Claude (MCP)
If you're using Claude as your agent, set up the MCP server for native payment handling:
{
"mcpServers": {
"agentpay-vn": {
"command": "agentpay-mcp",
"env": {
"AGENTPAY_MERCHANT_ID": "your_merchant_id",
"AGENTPAY_MERCHANT_NAME": "Your Shop",
"AGENTPAY_BANK_ACCOUNT": "970422|1234567890"
}
}
}
}
Add this to your Claude client configuration. Now Claude can:
- Call create_payment_request directly in its reasoning loop
- Suggest payment when appropriate ("Would you like to enroll now?")
- Handle settlement confirmation without custom Python code
Real-World Walkthrough: An Online Course Bot
Let's build a mini agent that sells courses:
from agentpay_vn import PaymentClient
import asyncio
class CourseBot:
def __init__(self):
self.client = PaymentClient(
merchant_id="demo_123",
merchant_name="TechSchool VN",
bank_account="970422|1234567890"
)
self.courses = {
"python_async": {"name": "Python Async & Concurrency", "price": 299000},
"go_web": {"name": "Go Web Development", "price": 399000},
}
async def sell_course(self, course_key, customer_email):
# Step 1: Get course details
course = self.courses[course_key]
# Step 2: Create payment request
payment = self.client.create_payment_request(
amount=course["price"],
description=course["name"],
customer_email=customer_email,
order_id=f"order_{course_key}_{int(time.time())}",
metadata={"course_key": course_key, "email": customer_email}
)
# Step 3: Share with customer
print(f"Course: {course['name']}")
print(f"Price: {course['price']:,} VND")
print(f"Pay here: {payment.checkout_url}")
# Step 4: Await confirmation
settlement = await self.client.await_settlement(payment.payment_id, timeout=300)
if settlement.status == "confirmed":
course_id = settlement.metadata["course_key"]
student = settlement.metadata["email"]
self.enroll_student(student, course_id)
return f"✓ {student} enrolled in {course_id}!"
else:
return f"✗ Payment failed or timed out."
def enroll_student(self, email, course_id):
# Your enrollment logic here
print(f"[DB] Enrolled {email} in {course_id}")
# Usage
bot = CourseBot()
result = asyncio.run(bot.sell_course("python_async", "student@example.com"))
print(result)
What happens: 1. Bot identifies the course and price 2. Creates a payment request with metadata (course ID, student email) 3. Shares the checkout link or displays QR in chat 4. Blocks until settlement is confirmed by the bank 5. On success, automatically enrolls the student 6. All without touching money or managing tokens
Best Practices: Do's and Don'ts
| ✅ DO | ❌ DON'T |
|---|---|
Store payment_id for auditing |
Hardcode merchant credentials |
| Set realistic timeouts (60–300s) | Trust client-side "payment confirmed" signals |
Include order_id to prevent duplicates |
Assume settlement without calling await_settlement |
| Use metadata for business logic | Store sensitive data in metadata |
| Test with small amounts first | Deploy without testing the full flow |
Handle await_settlement timeouts gracefully |
Show checkout URL without explaining payment protection |
Advanced Tips
Webhook Integration (Production)
Polling with await_settlement is simple but blocks your agent. For production, use webhooks:
from flask import Flask, request
app = Flask(__name__)
@app.route("/agentpay/webhook", methods=["POST"])
def handle_settlement():
data = request.json
payment_id = data["payment_id"]
status = data["status"]
metadata = data["metadata"]
if status == "confirmed":
# Async job: enroll student, send email, etc.
enroll_student_async(metadata["email"], metadata["course_id"])
return {"ok": True}, 200
Set the webhook URL in your AgentPay dashboard.
Idempotency: Prevent Double Charges
Always use unique order_id values. If your agent retries, the SDK detects duplicates:
import hashlib
def generate_order_id(course_id, customer_id, timestamp):
unique_str = f"{course_id}_{customer_id}_{timestamp}"
return hashlib.md5(unique_str.encode()).hexdigest()[:16]
order_id = generate_order_id("python_async", "user_42", 1704067200)
Batch Payments (Multiple Items)
For a cart with multiple courses:
total = sum([item["price"] for item in cart])
item_names = ", ".join([item["name"] for item in cart])
payment = client.create_payment_request(
amount=total,
description=f"Purchase: {item_names[:60]}...",
metadata={"items": cart, "cart_total": total}
)
FAQ
Q: Does AgentPay VN hold my money? No. QR codes point directly to your bank account. Settlement is confirmed by your bank's feed, not by AgentPay's database.
Q: What's the transaction fee? AgentPay VN is MIT-licensed and open-source. There's no transaction fee—use it freely. Your bank may charge standard VietQR fees (typically 0–0.5%).
Q: Can I use this with international customers? Not yet. VietQR requires Vietnamese bank accounts. For international payments, integrate Stripe or PayPal alongside AgentPay VN.
Q: How do I test without real payments?
Use AgentPay's sandbox mode (set AGENTPAY_SANDBOX=true in your environment). Sandbox QR codes won't charge real accounts.
Key Takeaways
- AgentPay VN = Trust without intermediaries. Your QR code → customer's bank → your account. No escrow, no holds.
- 3-line integration: Create request → share URL → await settlement. That's all your agent needs.
- Perfect for agents. MCP integration with Claude means your AI can reason about payments naturally.
- Production-ready: Open-source (MIT), webhook support, idempotency guards, metadata for business logic.
- Get started now:
pip install agentpay-vnand follow the docs at https://agentpay.servicesai.vn/v1/docs.
Next Steps
- Install:
pip install agentpay-vn - Grab credentials: Sign up at https://agentpay.servicesai.vn/v1/docs and get your
merchant_id - Test locally: Run the course bot example above with sandbox mode
- Deploy: Add MCP config to Claude or integrate the SDK into your agent framework
- Monitor: Check settlement confirmations and reconcile with your bank feed
Your AI agent is now ready to collect payments. No middleman. No delays. No risk. Go sell.
Questions or issues? Check the full documentation or GitHub repo.