Build AI Chatbot Payment Flow Without Holding Funds
The Problem: Why Most AI Commerce Solutions Get Payment Wrong
You've built an AI chatbot that sells digital products—online courses, design templates, API credits. It's smart, responsive, and users love it. But there's a friction point nobody talks about: payment processing.
Most solutions force you into one of two uncomfortable positions:
- Integrate a traditional payment gateway (Stripe, PayPal) and deal with KYC requirements, settlement delays, and fees that stack up as your transaction volume grows.
- Build a custom bank integration and suddenly you're holding customer funds in escrow—a regulatory minefield that turns your side project into a fintech compliance nightmare.
In Vietnam, there's a third way that's been hiding in plain sight: VietQR—a standardized QR payment system that points directly at your merchant bank account. But until now, there's been no simple way to wire it into AI agents.
Enter AgentPay VN. It's a lightweight, open-source (MIT) Python SDK + MCP server that lets your AI agent generate payment requests, send checkout URLs, and confirm settlements—all without ever touching the money.
What AgentPay VN Actually Does (And Doesn't Do)
Let's be crystal clear about what this isn't: it's not a payment processor or a payment gateway. AgentPay VN is a bridge layer. Think of it like this:
- Your customer scans a QR code.
- The QR points directly at your bank account (via VietQR).
- They send money to you.
- Your bank sends a webhook confirmation that the money arrived.
- AgentPay VN logs that settlement and your agent knows the payment cleared.
No escrow. No fund custody. No regulatory gray area. The money goes straight into your business bank account, and AgentPay VN simply records the fact that it happened.
For AI agent developers, this means:
- Your agent can sell instantly: Generate a payment request, show the QR, and grant access the moment settlement is confirmed.
- Your agent can customize the flow: Different prices for different users? Different expiry times? Build it in 10 lines of code.
- Your agent integrates with Claude (or any LLM): AgentPay VN ships with an MCP server, so Claude can call it natively.
Installation: Get Up and Running in 60 Seconds
If you have Python 3.9+ and pip installed, you're ready:
pip install agentpay-vn
For Claude integration via the MCP server:
pip install agentpay-mcp
Verify the install:
import agentpay_vn
print(agentpay_vn.__version__)
If you see a version number, you're good. Now let's build.
The Three-Line Payment Flow Explained
AgentPay VN condenses the entire payment lifecycle into three operations:
1. Create a Payment Request
Your agent calls this when a user wants to buy. You specify the amount, product ID, and optional metadata.
from agentpay_vn import AgentPayClient
# Initialize the client with your merchant bank details
client = AgentPayClient(
bank_account="1234567890", # Your bank account number
bank_code="MB", # Bank code (MB, VCB, TCB, etc.)
merchant_name="My AI Shop" # How it shows on the QR
)
# Create a payment request for a digital product
payment = client.create_payment_request(
amount=49000, # Amount in VND
description="Python Mastery Course",
order_id="order_12345", # Unique ID for this transaction
expiry_minutes=30 # QR expires in 30 minutes
)
print(f"Payment ID: {payment.id}")
print(f"Checkout URL: {payment.checkout_url}")
print(f"QR Code (as SVG): {payment.qr_svg}")
What happens here:
- create_payment_request() generates a unique payment ID and a VietQR-formatted checkout URL.
- The checkout_url contains all the bank routing info—the customer's phone or browser will recognize it as a bank transfer instruction.
- The qr_svg is an SVG string you can embed directly in your chatbot's UI.
- You're not holding any money. The QR just encodes your bank account and the amount.
2. Send the Checkout URL to Your Customer
Your AI agent now presents this to the user:
# In your chatbot/agent loop
def sell_digital_product(user_id, product_name, price_vnd):
payment = client.create_payment_request(
amount=price_vnd,
description=f"Purchase: {product_name}",
order_id=f"user_{user_id}_product_{product_name}",
expiry_minutes=15
)
# Return the checkout URL to your agent
return {
"status": "payment_pending",
"message": f"Scan this QR to pay {price_vnd:,} VND for {product_name}",
"checkout_url": payment.checkout_url,
"qr_svg": payment.qr_svg,
"payment_id": payment.id
}
Your UI (web, Telegram, Discord, etc.) renders this QR code. The user scans it with their banking app and completes the transfer. That's it—the money is now in your bank account.
3. Await Settlement Confirmation
Your agent polls for confirmation:
import time
def wait_for_payment(payment_id, timeout_seconds=300):
"""
Poll for settlement. Returns True if money arrived, False if timeout.
"""
start = time.time()
while time.time() - start < timeout_seconds:
settlement = client.await_settlement(payment_id)
if settlement.confirmed:
print(f"✓ Payment confirmed! Amount: {settlement.amount} VND")
print(f" Timestamp: {settlement.settled_at}")
print(f" Bank reference: {settlement.bank_reference}")
return True
# Not settled yet; check again in 2 seconds
time.sleep(2)
print("✗ Payment timed out (user didn't scan/pay within 5 minutes)")
return False
Key insight: await_settlement() does not hold funds. It just checks your bank's webhook/feed to see if a matching transaction arrived. Once it returns confirmed=True, your agent can:
- Grant access to the digital product (send download link, API key, course enrollment, etc.)
- Log the transaction
- Send a thank-you message
Real-World Example: An Online Course Seller Bot
Let's walk through a concrete scenario: a chatbot that sells Python courses.
Setup:
from agentpay_vn import AgentPayClient
import json
class CourseSellerBot:
def __init__(self):
self.client = AgentPayClient(
bank_account="1029384756",
bank_code="MB",
merchant_name="Tech Learning Hub"
)
self.courses = {
"python-101": {"name": "Python Basics", "price": 99000},
"django-pro": {"name": "Django Mastery", "price": 299000},
"ai-agents": {"name": "AI Agents in Python", "price": 499000}
}
def buy_course(self, user_id, course_key):
if course_key not in self.courses:
return {"error": "Course not found"}
course = self.courses[course_key]
payment = self.client.create_payment_request(
amount=course["price"],
description=f"Course: {course['name']}",
order_id=f"{user_id}_{course_key}_{int(time.time())}",
expiry_minutes=20
)
# Store this payment ID so we can track it later
with open(f"pending_{payment.id}.json", "w") as f:
json.dump({
"user_id": user_id,
"course_key": course_key,
"payment_id": payment.id,
"amount": course["price"]
}, f)
return {
"status": "waiting_for_payment",
"course": course["name"],
"amount": f"{course['price']:,} VND",
"qr": payment.qr_svg,
"payment_id": payment.id
}
def confirm_purchase(self, payment_id):
settlement = self.client.await_settlement(payment_id)
if settlement.confirmed:
# Load the pending payment info
with open(f"pending_{payment_id}.json", "r") as f:
order = json.load(f)
user_id = order["user_id"]
course_key = order["course_key"]
course_name = self.courses[course_key]["name"]
# Grant access (in real system, call your LMS/database)
self.grant_course_access(user_id, course_key)
return {
"success": True,
"message": f"Welcome! You now have access to {course_name}",
"access_link": f"https://yoursite.com/learn/{course_key}?user={user_id}"
}
else:
return {"success": False, "message": "Payment not yet confirmed"}
def grant_course_access(self, user_id, course_key):
# TODO: Call your database/LMS to enroll the user
print(f"✓ Enrolled user {user_id} in {course_key}")
# Usage
bot = CourseSellerBot()
# User asks: "I want the Django course"
response = bot.buy_course("user_abc123", "django-pro")
print(response["qr"]) # Render this QR in the UI
# After user scans and pays, check settlement
time.sleep(5) # In real code, use a queue/webhook
result = bot.confirm_purchase(response["payment_id"])
print(result["message"]) # "Welcome! You now have access to Django Mastery"
What's happening: 1. User says "I want the Django course." 2. Bot creates a payment request (₫299,000). 3. Bot shows the QR code in the chat. 4. User scans → their banking app shows "Transfer ₫299,000 to Tech Learning Hub" → they confirm → money lands in your account. 5. AgentPay VN detects the settlement. 6. Bot grants access and sends the course link.
Total time: ~30 seconds from QR to course access. No fund custody. No regulatory complexity.
Integrating with Claude via MCP Server
If you want Claude to orchestrate the entire payment flow autonomously, use the MCP server:
Claude Configuration (claude_desktop_config.json):
{
"mcpServers": {
"agentpay": {
"command": "python",
"args": ["-m", "agentpay_mcp"],
"env": {
"AGENTPAY_BANK_ACCOUNT": "1029384756",
"AGENTPAY_BANK_CODE": "MB",
"AGENTPAY_MERCHANT_NAME": "My AI Shop"
}
}
}
}
Now Claude can call these tools directly in a conversation:
User: "I want to buy the API Pro plan for 199,000 VND"
Claude: I'll set up a payment for you. [calls create_payment_request]
→ Here's your payment QR:
[QR CODE]
Scanning this will transfer 199,000 VND to our account.
User: [scans and pays]
Claude: [polls await_settlement every 2 seconds]
→ Payment confirmed! Your API key is: sk_live_abc123...
Claude handles the entire flow without your code touching the money.
Advanced Tips & Best Practices
1. Webhook Integration (Better Than Polling)
Instead of time.sleep(2) loops, set up your bank to send webhooks when payments arrive:
from flask import Flask, request
app = Flask(__name__)
client = AgentPayClient(...)
@app.route("/webhooks/settlement", methods=["POST"])
def handle_settlement():
data = request.json
# Bank sends: {"bank_reference": "...", "amount": 199000}
settlement = client.verify_settlement(data)
if settlement.verified:
grant_access(settlement.order_id)
return {"status": "ok"}
This is faster and more reliable than polling.
2. Idempotency: Prevent Double-Granting
Always store order_id uniquely:
order_id = f"{user_id}_{product_id}_{timestamp}_{random_nonce}"
payment = client.create_payment_request(
amount=price,
order_id=order_id # Globally unique
)
If a webhook arrives twice, your database sees the same order_id and skips re-granting.
3. Expiry Times for Abandoned Carts
Set expiry_minutes based on your use case:
- High-friction (course sales): 30–60 minutes
- Impulse buys (stickers, digital art): 5–10 minutes
- API credits: 20 minutes
4. Testing Without Real Payments
Use sandbox mode (if available in your bank):
client = AgentPayClient(
bank_account="1029384756",
bank_code="MB",
merchant_name="My Shop",
sandbox=True # Generates test QRs
)
Do's and Don'ts
| ✅ Do | ❌ Don't |
|---|---|
Create a new order_id for every request |
Reuse order_id across different transactions |
Store payment_id in your database immediately |
Assume settlement happened without confirming |
Set sensible expiry_minutes (5–60) |
Leave QRs active forever |
Verify settlement.bank_reference matches your bank feed |
Trust only the SDK's response |
| Use MCP for Claude integration | Implement custom payment parsing |
| Log all transactions for accounting | Treat AgentPay as a "set and forget" tool |
FAQ
Q: Does AgentPay VN hold my money? A: No, never. The QR points directly at your bank account. AgentPay VN only records that a settlement happened. Your money goes straight to your bank.
Q: Can I use this with Telegram bots, Discord bots, web apps? A: Yes. AgentPay VN generates a checkout URL and QR SVG—you can render those anywhere. No special payment UI needed.
Q: What if a customer scans the QR twice and pays twice?
A: Each create_payment_request() call generates a unique order_id. Your code checks settlement.order_id before granting access. You can refund the duplicate payment manually (it's just a regular bank transfer).
Q: Which Vietnamese banks are supported?
A: Any bank with VietQR support (VCB, MB, TCB, ACB, Techcombank, VIB, etc.). The bank_code parameter controls where your money lands.
Key Takeaways
- AgentPay VN removes payment custody risk from your AI agent. Customers send money directly to your bank; the SDK just tracks when it arrives.
- Three operations = complete flow:
create_payment_request()→ send QR →await_settlement(). - Works with any UI: web, mobile, Telegram, Discord, email—wherever you can render an image or URL.
- Claude-native via MCP: Delegate the entire payment workflow to your AI with zero custom payment code.
- Built for digital products: Courses, templates, API keys, ebooks, design files—anything that ships instantly after payment.
- Lightweight & open-source (MIT): No vendor lock-in. ~500 lines of code you can audit and customize.
Get Started Now
Your first payment-enabled AI agent is 15 minutes away:
pip install agentpay-vn
Then copy the course seller example above and adapt it to your product. Full docs and examples live at:
📖 AgentPay VN Docs 💻 GitHub Repository
Questions? Check the docs, review the examples, or open an issue on GitHub. The MIT license means you can modify, sell, and redistribute—go build something cool.