Telegram Bot Payments with VietQR: AgentPay Guide
The Problem: Payments Break Telegram Bots
Imagine you've built a Telegram bot that sells online courses. A user types /buy, sees your course, and hits "Pay Now." Then… nothing. You have no way to collect money directly in the chat. You'd need to redirect them to Stripe, PayPal, or a separate checkout page—and half your users abandon the flow right there.
Worse: if you do integrate a payment processor, you're trusting a third party to hold customer funds, dealing with settlement delays, and paying transaction fees that eat into your margin. For Vietnamese merchants selling digital products, physical goods, or services via Telegram, the friction is even higher—most global processors don't support VietQR or Vietnamese bank accounts seamlessly.
That's where AgentPay VN changes the game.
What Is AgentPay VN?
AgentPay VN is an open-source MIT-licensed Python SDK (plus MCP server) that lets AI agents and bots collect payments via VietQR—Vietnam's instant payment standard. Here's what makes it different:
- No fund holding. The QR code points directly to your merchant bank account. You get paid instantly.
- Bank-verified settlement. A feed confirms when money lands—no waiting, no disputes.
- AI-native. Built for Claude, ChatGPT, and other agents to create payments on the fly.
- Simple 3-line flow. Create a payment request → send the checkout URL → await settlement.
- Free to start. MIT license, zero vendor lock-in.
Install it today:
pip install agentpay-vn
Or use the MCP server for Claude integration:
agentpay-mcp
Why Telegram + VietQR + AgentPay?
Telegram has 900+ million users and millions of bots. Vietnamese Telegram users are highly engaged and prefer instant payments over web checkout. VietQR (launched by the State Bank of Vietnam in 2021) is now the standard for peer-to-peer and merchant payments—almost every Vietnamese bank supports it.
Combining all three means:
- Zero friction. User sees QR, scans with their banking app, pays in 10 seconds.
- Trust. VietQR is backed by the State Bank. Your customers know the payment is secure.
- Developer simplicity. A few lines of Python handle the entire flow.
Step-by-Step: Build Your First Payment-Ready Bot
1. Set Up Your Merchant Account
Before coding, you'll need: - A Vietnamese bank account (or a business account that supports VietQR). - Your bank's merchant ID or account number for QR generation. - Access to your bank's transaction feed (most major banks: VCB, Techcombank, MB, ACB, etc.).
Refer to AgentPay docs for bank-specific setup.
2. Install and Initialize
pip install agentpay-vn python-telegram-bot
3. Create Your First Payment Request
Here's a minimal Telegram bot that collects a payment:
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Application, CommandHandler, ContextTypes
from agentpay_vn import AgentPayClient
import os
# Initialize AgentPay
AGENTPAY_API_KEY = os.getenv("AGENTPAY_API_KEY")
client = AgentPayClient(api_key=AGENTPAY_API_KEY)
# Initialize Telegram bot
TG_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
app = Application.builder().token(TG_TOKEN).build()
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Send welcome message with a sample product."""
await update.message.reply_text(
"Welcome to TechCourse Bot!\n"
"We sell premium Python courses. Use /buy to get started."
)
async def buy(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Create a payment request and send checkout URL."""
user_id = update.effective_user.id
# Step 1: Create payment request
payment = await client.create_payment_request(
amount=499000, # 499,000 VND
description="Advanced Python Mastery Course",
merchant_ref=f"course_{user_id}_{int(time.time())}",
customer_email="user@example.com",
redirect_url="https://yourdomain.com/success" # Optional: where to send them after payment
)
# Step 2: Extract the VietQR checkout URL
checkout_url = payment["checkout_url"]
payment_id = payment["id"]
# Step 3: Send inline button to user
keyboard = InlineKeyboardMarkup([
[InlineKeyboardButton("💳 Pay 499K VND", url=checkout_url)]
])
await update.message.reply_text(
"*Advanced Python Mastery*\n"
"📚 12 hours of video content\n"
"💻 50+ real-world projects\n"
"🎓 Certificate included\n\n"
"Price: 499,000 VND",
reply_markup=keyboard,
parse_mode="Markdown"
)
# Step 4: Store payment_id for later settlement check
context.user_data["payment_id"] = payment_id
async def check_payment(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Check if payment settled."""
if "payment_id" not in context.user_data:
await update.message.reply_text("No pending payment found. Use /buy first.")
return
payment_id = context.user_data["payment_id"]
# Step 5: Await settlement (polls or webhook)
status = await client.await_settlement(
payment_id=payment_id,
timeout_seconds=300 # Wait up to 5 minutes
)
if status["settled"]:
await update.message.reply_text(
"✅ Payment confirmed!\n"
f"Amount: {status['amount']:,} VND\n"
f"Transaction ID: {status['transaction_id']}\n\n"
"Your course access link: https://yourdomain.com/course/xyz"
)
else:
await update.message.reply_text("⏱️ Payment timeout. Please try again.")
# Register handlers
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("buy", buy))
app.add_handler(CommandHandler("check", check_payment))
# Run bot
if __name__ == "__main__":
app.run_polling()
Line-by-line explanation:
- Lines 7–8: Initialize AgentPay with your API key (get it from dashboard).
- Lines 10–11: Load Telegram token from environment.
- Lines 13–18:
/startsends a welcome message. - Lines 20–49:
/buycreates a payment request withcreate_payment_request(), which returns a checkout URL. The URL embeds the VietQR code. We send it as an inline button so users tap → pay in their banking app. - Lines 51–67:
/checkcallsawait_settlement()to poll for payment confirmation. Once settled, you unlock the course.
4. Integrate with Claude via MCP
If you want your bot powered by Claude (for smarter responses), use the MCP server. Create an .mcp-config.json:
{
"mcpServers": {
"agentpay": {
"command": "agentpay-mcp",
"env": {
"AGENTPAY_API_KEY": "your-api-key-here"
}
}
}
}
Now Claude can create payments directly:
User: "I want to buy the course."
Claude: (calls agentpay.create_payment_request with course details)
Claude: "Here's your checkout link: [QR image]"
Real-World Walkthrough: Café Pre-Order Bot
Let's say you run a specialty coffee shop in Hanoi and want to let customers pre-order drinks via Telegram.
Scenario:
1. Customer texts the bot: /menu
2. Bot shows coffee options with prices (e.g., Cà Phê Sữa: 35K, Espresso: 50K).
3. Customer picks a drink and clicks "Order & Pay."
4. Bot creates a payment request for 35,000 VND.
5. Customer scans the VietQR code with their banking app and pays instantly.
6. Bot confirms: "Your order is ready for pickup at 3 PM."
In code (simplified):
MENU = {
"ca_phe_sua": {"name": "Cà Phê Sữa", "price": 35000},
"espresso": {"name": "Espresso", "price": 50000},
"matcha_latte": {"name": "Matcha Latte", "price": 55000},
}
async def order_coffee(update: Update, context: ContextTypes.DEFAULT_TYPE):
item_key = context.args[0] # e.g., "ca_phe_sua"
item = MENU[item_key]
payment = await client.create_payment_request(
amount=item["price"],
description=f"Pre-order: {item['name']}",
merchant_ref=f"cafe_{update.effective_user.id}_{item_key}"
)
keyboard = InlineKeyboardMarkup([
[InlineKeyboardButton(
f"💳 Pay {item['price']:,} VND",
url=payment["checkout_url"]
)]
])
await update.message.reply_photo(
photo="path/to/coffee/image.jpg",
caption=f"*{item['name']}*\n{item['price']:,} VND",
reply_markup=keyboard,
parse_mode="Markdown"
)
After payment settles, trigger fulfillment (notify barista, send pickup time, etc.).
Do's and Don'ts
| Do | Don't |
|---|---|
✅ Store payment_id to track orders |
❌ Re-create the same payment request twice |
| ✅ Set a reasonable timeout (5–10 min) | ❌ Assume payment is instant; always confirm |
| ✅ Log transactions for accounting | ❌ Skip bank feed verification |
| ✅ Use descriptive merchant_ref (e.g., user ID, timestamp) | ❌ Use generic refs; you'll lose order context |
| ✅ Validate amount before creating request | ❌ Trust user input directly |
| ✅ Handle network errors gracefully | ❌ Crash if settlement times out |
Advanced Tips
Batch Payments
If your bot handles multiple concurrent orders, create a queue:
import asyncio
from collections import defaultdict
payment_queue = defaultdict(list)
async def process_payment_queue():
"""Periodically check settled payments."""
while True:
for user_id, payments in list(payment_queue.items()):
for payment in payments:
status = await client.get_payment_status(payment["id"])
if status["settled"]:
# Handle success
await notify_user(user_id, "Order ready!")
payments.remove(payment)
await asyncio.sleep(10) # Check every 10 seconds
# Run in background
asyncio.create_task(process_payment_queue())
Webhook Integration (Production)
For real-time updates, use webhooks instead of polling:
@app.post("/webhook/agentpay")
async def webhook_handler(request: Request):
payload = await request.json()
payment_id = payload["id"]
status = payload["status"] # "settled", "pending", "failed"
if status == "settled":
user_id = extract_user_id_from_ref(payload["merchant_ref"])
await send_telegram_message(user_id, "✅ Payment confirmed!")
return {"ok": True}
Multi-Currency Support
AgentPay supports VND only (Vietnam-specific), but you can convert prices:
import httpx
async def usd_to_vnd(usd_amount: float) -> int:
async with httpx.AsyncClient() as client:
r = await client.get("https://api.exchangerate-api.com/v4/latest/USD")
rate = r.json()["rates"]["VND"]
return int(usd_amount * rate)
# Usage
price_vnd = await usd_to_vnd(9.99) # ~240K VND
FAQ
Q: Does AgentPay hold my money? No. The VietQR points directly to your bank account. AgentPay is just the orchestration layer; settlement happens directly with your bank.
Q: Can I use AgentPay outside Vietnam? No, AgentPay is Vietnam-specific (it uses VietQR, a Vietnamese standard). If your users are abroad, they won't have VietQR-capable banks.
Q: How long does payment settlement take? VietQR transfers are instant (real-time gross settlement). Money appears in your account within seconds.
Q: Can I refund a payment?
Yes, you can initiate a refund via the SDK (client.refund_payment(payment_id)) or your bank's portal. The amount goes back to the customer's account.
Q: What if my Telegram bot goes offline?
You'll miss real-time updates, but the bank feed still confirms settlement. When your bot restarts, query await_settlement() to catch up on pending payments.
Key Takeaways
- 🎯 Telegram + VietQR = instant payment collection with zero friction for Vietnamese users.
- 💰 AgentPay holds zero funds. Money goes straight to your bank account.
- 🚀 Simple 3-step flow:
create_payment_request()→ send checkout URL →await_settlement(). - 🤖 AI-native. Claude can create payments on the fly via MCP server.
- 📱 Production-ready. Webhook support, batch processing, and error handling built in.
- 🏦 Bank-verified. Transactions confirmed by your bank's feed, not a third-party API.
Get Started Now
Your first payment-collecting Telegram bot is minutes away:
pip install agentpay-vn
Then head to:
- Documentation: https://agentpay.servicesai.vn/v1/docs
- GitHub: https://github.com/phuocdu/agentpay-vn
- API Key: Sign up on the AgentPay dashboard.
Start collecting VietQR payments in your bot today. No fund holding, no vendor lock-in, just instant settlement straight to your Vietnamese bank account.