Sell Digital Products from AI Chatbots Without Holding Funds
The Problem: Building Payment-Enabled AI Agents Is a Compliance Nightmare
Imagine you've built a brilliant AI chatbot that sells online courses, e-books, or software licenses. Your users love it. They're ready to pay. But now you're facing a wall of problems:
- You need a payment processor, which means fees (2-3% per transaction), complex KYC requirements, and months of approval.
- You can't hold customer funds—not legally, not ethically. Yet most payment APIs require you to collect money first, then reconcile with the merchant.
- Compliance overhead eats your engineering time. PCI-DSS, fraud detection, chargebacks—suddenly your simple bot is a financial institution in disguise.
- Users distrust third-party payment flows. Why should they trust another platform with their card data?
This is where AgentPay VN changes the game.
What Is AgentPay VN? A Zero-Custody Payment Layer for AI
AgentPay VN is an open-source (MIT license) Python SDK and MCP server that lets AI agents generate VietQR payment requests that point directly at the merchant's bank account. No middleman. No fund holding. No compliance theater.
Here's the core principle: when your chatbot creates a payment request, it generates a QR code that directs the customer to pay the merchant's actual bank account—not a holding account. The merchant's bank feed then confirms settlement in real-time. Your AI agent is the orchestrator, not the custodian.
Key facts:
- Open-source under MIT license (you own your integration)
- Lightweight Python SDK: pip install agentpay-vn
- MCP server for Claude and other AI frameworks
- Supports VietQR (Vietnam's interbank QR standard)
- Zero fund holding—payments go straight to merchant banks
- Bank feed integration for instant settlement confirmation
Why This Matters: The Three-Line Payment Flow
Traditional payment APIs require you to understand state machines, webhook handling, and reconciliation logic. AgentPay VN collapses payment collection into three lines:
# 1. Create a payment request
payment = await agent.create_payment_request(
amount=500_000, # VND
description="E-book: Python for AI Agents",
merchant_id="your_merchant_id"
)
# 2. Send the checkout URL to the user
await chatbot.send_message(f"Scan here: {payment.checkout_url}")
# 3. Wait for settlement confirmation
await agent.await_settlement(payment.request_id)
That's it. No webhooks to debug. No reconciliation loops. The payment request contains everything needed: amount, merchant bank details, and a unique reference ID. The user scans, pays, and your bank feed confirms—all asynchronously.
Setting Up AgentPay VN: A Step-by-Step Guide
Step 1: Install the SDK
pip install agentpay-vn
This installs the core Python library. It's lightweight—no heavy dependencies.
Step 2: Configure Your Merchant Account
You'll need: - A Vietnamese business bank account (or your merchant's account) - Your bank's API credentials for the feed integration - A merchant ID (provided by your bank or AgentPay partner)
Store these securely in environment variables:
export AGENTPAY_MERCHANT_ID="your_merchant_id"
export AGENTPAY_BANK_ACCOUNT="0123456789"
export AGENTPAY_BANK_CODE="VCCB" # Vietcombank example
Step 3: Initialize the Agent
from agentpay_vn import AgentPayClient
import asyncio
# Create the client
client = AgentPayClient(
merchant_id=os.getenv("AGENTPAY_MERCHANT_ID"),
bank_account=os.getenv("AGENTPAY_BANK_ACCOUNT"),
bank_code=os.getenv("AGENTPAY_BANK_CODE")
)
# Verify connection
await client.health_check()
print("AgentPay VN is ready!")
Step 4: Add to Your Chatbot
If you're using Claude with MCP, register the AgentPay MCP server:
{
"mcpServers": {
"agentpay-mcp": {
"command": "agentpay-mcp",
"args": [],
"env": {
"AGENTPAY_MERCHANT_ID": "your_merchant_id",
"AGENTPAY_BANK_ACCOUNT": "0123456789",
"AGENTPAY_BANK_CODE": "VCCB"
}
}
}
}
Once registered, Claude can call AgentPay functions directly. No extra code needed in your main bot.
Real-World Walkthrough: Building a Course-Selling Chatbot
Let's build a concrete example: a chatbot that sells Python courses.
The scenario: - User asks: "I want to buy the Advanced Python course." - Bot responds with price and payment QR. - User scans and pays via mobile banking. - Bot detects settlement and delivers the course immediately.
The code:
import asyncio
from agentpay_vn import AgentPayClient
class CourseBot:
def __init__(self):
self.client = AgentPayClient(
merchant_id="course_seller_123",
bank_account="0123456789",
bank_code="VCCB"
)
self.courses = {
"python-advanced": {"price": 500_000, "title": "Advanced Python Mastery"},
"django-pro": {"price": 750_000, "title": "Django for Enterprise"},
}
async def sell_course(self, user_id: str, course_id: str):
"""Execute a course sale end-to-end."""
# Validate course
if course_id not in self.courses:
return {"error": "Course not found"}
course = self.courses[course_id]
# Create payment request
payment = await self.client.create_payment_request(
amount=course["price"],
description=f"Course: {course['title']}",
merchant_id="course_seller_123",
metadata={"user_id": user_id, "course_id": course_id}
)
# Send checkout URL to user
print(f"\n📲 Payment link for {user_id}:")
print(f"Scan this: {payment.checkout_url}")
print(f"Reference: {payment.request_id}\n")
# Wait for settlement (non-blocking)
try:
settlement = await asyncio.wait_for(
self.client.await_settlement(payment.request_id),
timeout=3600 # Wait up to 1 hour
)
# Payment confirmed—deliver course
print(f"✅ Payment confirmed! Delivering {course['title']}...")
return {
"status": "success",
"course": course["title"],
"download_link": f"https://yourcdn.com/courses/{course_id}/access.zip?user={user_id}"
}
except asyncio.TimeoutError:
return {"status": "timeout", "request_id": payment.request_id}
async def check_payment_status(self, request_id: str):
"""Manual check for a payment request."""
return await self.client.get_payment_status(request_id)
# Usage
async def main():
bot = CourseBot()
result = await bot.sell_course("user_42", "python-advanced")
print(result)
asyncio.run(main())
What happens step-by-step:
create_payment_request()generates a unique VietQR payload with the merchant's bank details, amount, and reference ID.- The QR code is displayed in the chat.
- User scans with any banking app and pays directly to the merchant's account.
await_settlement()polls the merchant's bank feed (via API or webhook) for confirmation.- Once confirmed, the bot delivers the course immediately.
Cost breakdown: Zero platform fees. Only standard bank transfer fees (typically free or ₫0–5,000).
Advanced Tips: Production Hardening
Tip 1: Handle Payment Timeouts Gracefully
Not all users complete payment instantly. Design for this:
async def sell_with_retry(self, user_id: str, course_id: str, max_attempts=3):
"""Allow user to re-check payment status."""
payment = await self.client.create_payment_request(
amount=self.courses[course_id]["price"],
description=f"Course: {self.courses[course_id]['title']}"
)
print(f"Payment reference: {payment.request_id}")
print(f"Check payment status anytime with: /status {payment.request_id}")
# Non-blocking—user can come back later
return payment.request_id
async def check_and_deliver(self, user_id: str, request_id: str):
"""User runs this later if payment wasn't detected."""
status = await self.client.get_payment_status(request_id)
if status["settled"]:
# Deliver course
print(f"✅ Payment confirmed. Delivering...")
else:
print(f"⏳ Still waiting. Amount received: {status['amount_received']} VND")
Tip 2: Idempotency for Webhook Duplicates
Bank feeds can fire duplicate confirmations. Make your settlement handler idempotent:
async def handle_settlement_webhook(self, request_id: str, amount: int):
"""Handle bank feed webhook safely."""
# Check if already processed
if await self.db.payment_delivered(request_id):
return {"status": "already_delivered", "request_id": request_id}
# Mark as delivered BEFORE external actions
await self.db.mark_as_delivered(request_id)
# Now deliver course (safe to retry if this fails)
await self.deliver_course(request_id)
return {"status": "delivered", "request_id": request_id}
Tip 3: Reconciliation Checks
Periodically verify your records against the bank:
async def reconcile(self):
"""Daily: check for orphaned payments."""
pending_payments = await self.db.get_pending_payments()
for payment in pending_payments:
if payment.created_at < datetime.now() - timedelta(days=7):
# Over a week old—check bank directly
bank_status = await self.client.get_payment_status(payment.request_id)
if bank_status["settled"]:
await self.deliver_course(payment.request_id)
print(f"✅ Reconciled: {payment.request_id}")
Comparison: AgentPay VN vs. Traditional Payment Processors
| Feature | AgentPay VN | Stripe / PayPal | Square |
|---|---|---|---|
| Fund holding | None (direct to merchant) | Yes (7-day reserve) | Yes (varies) |
| Setup time | Minutes | Days/weeks | Days |
| Regional focus | Vietnam / VietQR | Global | US/Global |
| Fees | Bank transfer only (₫0–5k) | 2.9% + $0.30 per transaction | 2.6% + $0.20 |
| Compliance burden | Minimal | PCI-DSS (unless hosted) | PCI-DSS |
| Open source | Yes (MIT) | No | No |
| AI-agent native | Yes (MCP server) | No | No |
| Settlement time | Real-time (bank feed) | 1–7 days | 1–3 days |
For a Vietnamese course seller making 50 sales @ ₫500,000 each: - AgentPay VN: ₫0–250,000 total (bank fees only) - Stripe: ₫7,250,000 (2.9% + $0.30 × 50)
Do's and Don'ts
Do:
- ✅ Store the request_id in your database immediately after creating a payment request.
- ✅ Use await_settlement() with a reasonable timeout (1–24 hours).
- ✅ Enable bank feed webhooks for real-time settlement notifications.
- ✅ Test with small amounts first (₫10,000–50,000).
- ✅ Log all payment events for reconciliation.
Don't:
- ❌ Deliver digital goods before settlement is confirmed.
- ❌ Store customer bank details—AgentPay handles that.
- ❌ Re-create payment requests for the same order (use get_payment_status() instead).
- ❌ Assume payment succeeded just because the QR was scanned.
- ❌ Ignore reconciliation—monthly audits prevent lost revenue.
FAQ
Q: What if a customer scans the QR but never pays?
A: The payment request expires after 24 hours (configurable). Your bot can remind the user or generate a fresh QR. Unpaid requests incur no fees.
Q: Can I use AgentPay VN for subscriptions?
A: Not directly—VietQR is designed for one-time transfers. For subscriptions, you'd need to regenerate payment requests monthly. We're exploring recurring payment support in a future release.
Q: What if the bank feed doesn't confirm settlement?
A: Implement a manual reconciliation flow. You can check the payment status API, or have users confirm payment with a screenshot. The await_settlement() function will time out gracefully.
Q: Is AgentPay PCI-DSS compliant?
A: Yes—but since you never touch customer bank details (they're handled by VietQR), you inherit the compliance of Vietnam's national payment system. No card data in your app = no PCI-DSS burden.
Key Takeaways
- Zero fund holding: Payments go straight to the merchant's bank account. You're never a financial intermediary.
- Three-line integration:
create_payment_request(), send the URL,await_settlement(). - AI-native: MCP server built for Claude and other agents. Payment collection is first-class.
- Cost-effective: Only standard bank transfer fees (₫0–5k per transaction).
- Open source: Full control. No vendor lock-in. MIT license.
- Real-time settlement: Bank feed integration confirms payment instantly—deliver digital goods without delay.
- Regional advantage: Optimized for Vietnam's VietQR ecosystem. Perfect for Vietnamese merchants and Southeast Asian expansion.
Whether you're building a course-selling bot, an e-book marketplace, or a SaaS sign-up flow, AgentPay VN removes the compliance friction and lets you focus on what matters: your AI product.
Get Started Now
Install AgentPay VN today:
pip install agentpay-vn
Set up the MCP server for Claude:
agentpay-mcp
Read the full documentation: https://agentpay.servicesai.vn/v1/docs
Explore the source code: https://github.com/phuocdu/agentpay-vn
Have questions? Join the community or open an issue on GitHub. Happy selling! 🚀