Sell Digital Products from AI Chatbots: No Fund Holding
The Problem: Your AI Chatbot Needs Real Payment Power
Imagine you've built an intelligent chatbot that sells online courses, design templates, or API credits. Users chat with it naturally, decide to buy, and then... what? Your bot vanishes into an error, redirects them to a third-party checkout, or worse—you have to handle fund custody yourself.
This isn't a small friction point. Every redirect kills 20–30% of conversions. And if you're holding customer money as an intermediary? You're now running a money services business without the compliance infrastructure. Regulatory headaches. Customer support nightmares. Refund disputes eating your margins.
What if your AI agent could accept payment, confirm settlement, and deliver the digital product—all without your hands ever touching the customer's money?
That's exactly what AgentPay VN solves.
What Is AgentPay VN? The Three-Minute Breakdown
AgentPay VN is an open-source Python SDK and MCP server that lets your AI agents collect payments via VietQR—Vietnam's national QR code standard—while the money flows directly to your merchant bank account. No escrow. No middleman. No regulatory gray zone.
Here's the magic:
- Zero fund holding: The QR code points straight at your bank account.
- Instant settlement confirmation: A bank feed tells you when the money landed.
- Open-source (MIT): Full transparency. No vendor lock-in.
- AI-native: Built for Claude, GPT, or any LLM running as an agent.
- Three-line payment flow: Create → Send → Await settlement.
You install it with one command (pip install agentpay-vn) and plug it into your chatbot or backend in minutes.
Why This Matters for Digital Product Sales
Digital products have unique economics:
- Instant delivery – No shipping, no inventory.
- High margin – You're selling intellectual property or software.
- Global reach – If you accept VietQR, you're optimized for the Vietnamese market but can easily expand.
- Repeatable – Courses, templates, plugins—high volume, low cost per unit.
But the payment layer has been broken for AI agents:
- Stripe/PayPal latency – API calls, webhook orchestration, error handling. Your agent burns tokens and time.
- Trust issues – If you hold the money, customers worry about refunds. If you don't, they worry about fraud.
- Compliance burden – Fund custody requires licensing in most jurisdictions.
With AgentPay VN, your agent becomes a true point-of-sale. The customer pays. The bank confirms. Your product ships. Done.
Architecture: How Your Agent Talks to Banks
Under the hood, AgentPay VN wraps Vietnam's VietQR standard and integrates with bank APIs. Here's what flows look like:
User → Chatbot (Claude/LLM) → AgentPay SDK → VietQR → Customer's Bank
↓
Merchant Bank (your account)
↓
Bank Feed Webhook
↓
Settlement Confirmed
↓
Deliver Product to User
Your agent never holds funds. The settlement is cryptographically confirmed by the banking system. No trust required—only math.
Step-by-Step: Build a Course-Selling Chatbot
Let's walk through a real example: a chatbot that sells a Python course for 299,000 VND.
Installation
pip install agentpay-vn
That's it. Now let's code.
Step 1: Initialize the Payment Service
from agentpay_vn import PaymentService, PaymentRequest
import os
from datetime import datetime, timedelta
# Load your merchant credentials (bank account, API key)
merchant_id = os.getenv("MERCHANT_ID")
merchant_key = os.getenv("MERCHANT_KEY")
merchant_account = os.getenv("MERCHANT_ACCOUNT") # Your bank account number
# Initialize the service
payment_service = PaymentService(
merchant_id=merchant_id,
merchant_key=merchant_key,
account_number=merchant_account
)
print("✓ Payment service initialized")
What's happening:
- You load credentials from environment variables (never hardcode secrets).
- The PaymentService object becomes your agent's payment interface.
- It's stateless—no database required yet.
Step 2: Create a Payment Request
When your chatbot user says "I want to buy the course," your agent creates a payment request:
# A user (Alice) wants to buy your course
user_id = "alice_123"
course_id = "python_fundamentals_2024"
price_vnd = 299_000 # 299k VND
# Create a payment request
payment_request = payment_service.create_payment_request(
order_id=f"{user_id}_{course_id}_{datetime.now().timestamp()}",
amount=price_vnd,
description=f"Python Fundamentals Course - User: {user_id}",
customer_id=user_id,
metadata={
"course_id": course_id,
"course_name": "Python Fundamentals",
"customer_email": "alice@example.com"
},
expiry_minutes=15 # QR code valid for 15 minutes
)
print(f"Payment request created: {payment_request.order_id}")
print(f"Checkout URL: {payment_request.checkout_url}")
print(f"Amount: {payment_request.amount:,} VND")
Line-by-line breakdown:
- order_id: Unique transaction identifier (combining user ID, product, and timestamp prevents collisions).
- amount: The price in VND (integers; no decimals—banking requirement).
- description: Human-readable label visible in bank statements.
- metadata: Custom data your agent can use later (course ID, email for delivery).
- expiry_minutes: QR codes are time-limited for security and UX (users don't linger).
Step 3: Send the Checkout URL to Your User
Your chatbot returns the QR code and URL:
# Agent sends this to the user via chat
checkout_message = f"""
🎓 Python Fundamentals Course
💰 Price: {payment_request.amount:,} VND
📱 Scan with your banking app:
{payment_request.qr_code_url}
Or click here: {payment_request.checkout_url}
⏰ Expires in 15 minutes. Let me know when you've paid!
"""
print(checkout_message)
# In a real chatbot, this would be sent via WebSocket or API
Step 4: Wait for Settlement
Once the user scans and pays, your agent needs to confirm. This is where bank feeds come in:
import time
from agentpay_vn import SettlementStatus
def wait_for_payment(order_id, timeout_seconds=60, poll_interval=2):
"""
Poll for payment settlement.
In production, use webhooks instead of polling.
"""
start_time = time.time()
while time.time() - start_time < timeout_seconds:
settlement = payment_service.check_settlement(order_id)
if settlement.status == SettlementStatus.CONFIRMED:
return settlement
elif settlement.status == SettlementStatus.FAILED:
raise Exception(f"Payment failed: {settlement.error_message}")
# Still pending, wait and retry
time.sleep(poll_interval)
print(f"Awaiting payment confirmation... ({int(time.time() - start_time)}s elapsed)")
raise TimeoutError(f"Payment not confirmed within {timeout_seconds} seconds")
# Your agent waits for Alice's payment
try:
settlement = wait_for_payment(payment_request.order_id, timeout_seconds=120)
print(f"✓ Payment confirmed!")
print(f" Transaction ID: {settlement.transaction_id}")
print(f" Settled at: {settlement.settled_at}")
print(f" Amount received: {settlement.amount:,} VND")
except TimeoutError:
print("❌ Payment timeout. Course purchase cancelled.")
except Exception as e:
print(f"❌ Payment error: {e}")
Key insights:
- check_settlement() queries the bank feed for transaction status.
- Three outcomes: CONFIRMED (money arrived), FAILED (rejected), or PENDING (still waiting).
- In production, use webhooks instead of polling (more efficient, instant updates).
- The metadata from Step 2 is returned, so you know which user to deliver the course to.
Step 5: Deliver the Product
When settlement is confirmed, unlock the course:
def deliver_course(user_id, course_id, settlement):
"""
Deliver the digital product after payment confirmation.
"""
# Log the transaction
order_log = {
"user_id": user_id,
"course_id": course_id,
"amount_vnd": settlement.amount,
"transaction_id": settlement.transaction_id,
"timestamp": settlement.settled_at
}
# Store in your database (e.g., PostgreSQL, MongoDB)
# db.orders.insert_one(order_log)
# Generate access credentials or download link
access_token = generate_unique_token(user_id, course_id)
download_link = f"https://yoursite.com/courses/{course_id}/access?token={access_token}"
# Send delivery via email
send_email(
to=settlement.metadata["customer_email"],
subject="🎓 Your Python Course is Ready!",
body=f"""Welcome, {user_id}!
Thank you for purchasing Python Fundamentals.
Start learning here: {download_link}
Your access lasts 1 year.
"""
)
return {
"status": "delivered",
"download_link": download_link,
"expires_at": (datetime.now() + timedelta(days=365)).isoformat()
}
# After settlement is confirmed
delivery_status = deliver_course(
user_id="alice_123",
course_id="python_fundamentals_2024",
settlement=settlement
)
print(f"Course delivered: {delivery_status}")
Integrating with Claude via MCP Server
If you want Claude (or any LLM) to run as your chatbot agent, use the AgentPay MCP server. MCP (Model Context Protocol) is Anthropic's standard for connecting AI models to tools.
Install the MCP Server
pip install agentpay-mcp
Configure Claude
Add this to your Claude client config (e.g., claude_desktop_config.json):
{
"mcpServers": {
"agentpay": {
"command": "agentpay-mcp",
"env": {
"MERCHANT_ID": "your_merchant_id",
"MERCHANT_KEY": "your_merchant_key",
"MERCHANT_ACCOUNT": "your_account_number"
}
}
}
}
Now Claude can call these functions directly:
User: "I want to buy your Python course."
Claude (internal reasoning):
1. Call `agentpay_create_payment_request` with amount=299000, description="Python Course"
2. Return QR code and checkout URL to user
3. Poll `agentpay_check_settlement` every 5 seconds
4. Once confirmed, call internal function `deliver_course_to_user`
5. Confirm with user: "✓ Your course access is ready!"
Real-World Walkthrough: A Café's AI Order Bot
Let's concrete this. A café in Ho Chi Minh City uses an AI chatbot on their website to sell digital gift cards.
Scenario:
- Customer visits café website, clicks "Buy Gift Card"
- Chatbot asks: "How much do you want to gift? 100k, 200k, or 500k VND?"
- Customer: "500k"
- Chatbot creates payment request → QR code appears
- Customer scans with banking app, pays
- Within 3 seconds, bank confirms to café's account
- Chatbot detects settlement, generates gift card code
- Chatbot sends email: "Your gift card code: GC-ABC123XYZ (500k VND). Share with friends!"
- Friend redeems code at café → Gets 500k VND credit
The café's bank balance increases immediately. No fund custody. No compliance risk. Just commerce.
Advanced: Webhook-Based Settlement (Production)
Polling is inefficient. For high volume, set up webhooks:
from flask import Flask, request, jsonify
import hmac
import hashlib
app = Flask(__name__)
@app.route("/webhooks/agentpay/settlement", methods=["POST"])
def settlement_webhook():
"""
Receives settlement confirmations from AgentPay bank feed.
Called once per transaction—instant notification.
"""
payload = request.get_json()
signature = request.headers.get("X-AgentPay-Signature")
# Verify webhook authenticity
expected_sig = hmac.new(
os.getenv("MERCHANT_KEY").encode(),
json.dumps(payload).encode(),
hashlib.sha256
).hexdigest()
if signature != expected_sig:
return jsonify({"error": "Invalid signature"}), 403
# Process settlement
order_id = payload["order_id"]
transaction_id = payload["transaction_id"]
amount = payload["amount"]
metadata = payload["metadata"]
print(f"Settlement confirmed: {order_id} ({amount:,} VND)")
# Deliver product immediately
deliver_course(
user_id=metadata["customer_id"],
course_id=metadata["course_id"],
settlement=payload
)
return jsonify({"status": "processed"}), 200
if __name__ == "__main__":
app.run(port=5000)
Why webhooks? - Instant: Settlement notification arrives in milliseconds. - Scalable: No polling overhead; 10,000 transactions work as easily as 10. - Reliable: Automatic retries if your server is down.
Do's and Don'ts
| Do | Don't |
|---|---|
Set expiry_minutes to 10–15 for UX clarity |
Leave QR codes open indefinitely (fraud risk) |
Store transaction_id for auditing and disputes |
Forget to log transactions for compliance |
| Use webhooks in production, polling for testing | Hardcode API keys in source code |
Validate metadata matches your order database |
Trust QR scans without settlement confirmation |
| Regenerate unique tokens per delivery | Reuse the same download link across users |
FAQ
Q: What if the customer's bank rejects the payment?
A: check_settlement() returns FAILED status with an error code (insufficient funds, wrong account, etc.). Your agent can inform the user and suggest retry or alternative payment method.
Q: Can I use AgentPay VN for subscription products?
A: Currently optimized for one-time purchases. For recurring billing, you'd create a new payment request each billing cycle and automate with await_settlement loops.
Q: Is my merchant account information secure? A: AgentPay VN doesn't store credentials—you pass them via environment variables. The SDK communicates with banks via encrypted channels (TLS 1.3). Open-source code means you can audit it yourself.
Q: What currencies does AgentPay VN support? A: Primarily Vietnamese Dong (VND) via VietQR. USD/EUR conversions depend on your merchant bank's forex rates.
Key Takeaways
- AgentPay VN removes the payment friction from AI agents. In three steps (create → send → await), your bot becomes a point-of-sale.
- You never hold customer funds. Money flows directly to your bank account. Compliance simplified.
- Settlement is instant and confirmed by banks. No escrow delays. No third-party trust required.
- It's open-source and free. MIT license. Plug it into Claude, GPT, or any LLM agent.
- Real-world use cases are immediate: course sales, digital gift cards, API credits, design templates, software licenses.
- Production deployments use webhooks, not polling, for scalability and latency.
Get Started Now
Your AI chatbot is already talking to customers. Now let it collect payments.
# Install the SDK
pip install agentpay-vn
# Read the full docs
# https://agentpay.servicesai.vn/v1/docs
# Explore the source code
# https://github.com/phuocdu/agentpay-vn
Start with the 15-minute example above. Deploy to production with webhooks. Scale to thousands of transactions. And never hold a customer's VND again.
Your agent is ready to sell. Let's make it happen.