Collect VietQR Payments in Telegram Bots with AgentPay
The Problem: Getting Paid Through Telegram Is Messy
You've built a useful Telegram bot—maybe it sells online courses, takes café orders, or books freelance services. Your users love it. But when payment time comes, everything breaks down. You're manually sending payment screenshots, users are using unsafe third-party links, and nobody's sure if money actually arrived. You definitely don't want to hold customer funds in your own account (legal liability, licensing headaches). What you need is a way for your bot to generate a VietQR code pointing directly at your bank account, and then automatically confirm when the payment settles. That's where AgentPay VN comes in.
Why VietQR + AgentPay Is the Right Move
VietQR is Vietnam's national instant payment standard—every bank supports it, every Vietnamese smartphone has a QR scanner built-in, and users trust it because it uses their actual bank app. AgentPay VN is an MIT-licensed Python SDK that wraps the complexity away. It never touches your money (the QR points straight to your bank), and a real bank feed confirms settlement in seconds. No middleman fees, no foreign exchange hassles, no merchant account approval delays.
The magic is in three lines: 1. create_payment_request — generate a unique request tied to your bot order 2. send checkout_url — post the QR code to your user 3. await_settlement — block until the bank confirms payment
That's it. Let's build it.
Installation and Setup
Step 1: Install the SDK
pip install agentpay-vn
Verify it works:
python -c "import agentpay; print(agentpay.__version__)"
Step 2: Set Up Your Bank Feed (One-Time)
AgentPay needs a way to know when your bank receives money. You'll configure a webhook or use AgentPay's bank integration partner. Visit https://agentpay.servicesai.vn/v1/docs for your bank's specific setup—most Vietnamese banks (Vietcombank, Techcombank, Agribank, etc.) are already supported.
Step 3: Get Your API Credentials
Sign up at https://agentpay.servicesai.vn and grab:
- AGENTPAY_API_KEY — your secret key
- AGENTPAY_MERCHANT_ID — identifies your account
- TELEGRAM_BOT_TOKEN — from BotFather
Store these in a .env file (never commit it):
AGENTPAY_API_KEY=your_key_here
AGENTPAY_MERCHANT_ID=your_merchant_id
TELEGRAM_BOT_TOKEN=your_bot_token
Building Your First Payment Flow
Full Working Example: Course Sales Bot
Let's say you're selling a "Python Mastery" course for 299,000 VND via Telegram. Here's the complete flow:
import asyncio
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Application, CommandHandler, ContextTypes
from agentpay import AgentPayClient, PaymentRequest
import os
from dotenv import load_dotenv
load_dotenv()
# Initialize AgentPay
client = AgentPayClient(
api_key=os.getenv("AGENTPAY_API_KEY"),
merchant_id=os.getenv("AGENTPAY_MERCHANT_ID")
)
app = Application.builder().token(os.getenv("TELEGRAM_BOT_TOKEN")).build()
# Store pending payments in memory (use Redis in production)
pending_payments = {}
async def start_course_purchase(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""
User sends /buy_course — we create a payment request
"""
user_id = update.effective_user.id
course_price = 299000 # VND
course_name = "Python Mastery 2024"
# Step 1: Create a payment request with AgentPay
payment_request = await client.create_payment_request(
amount=course_price,
description=f"Course: {course_name}",
order_id=f"course_{user_id}_{int(asyncio.get_event_loop().time())}",
customer_name=update.effective_user.full_name or "Anonymous",
customer_phone="", # Optional; collect if needed
redirect_url="https://t.me/your_bot_username" # Where to send user after payment
)
# Step 2: Store the payment request locally (for later confirmation)
pending_payments[payment_request.order_id] = {
"user_id": user_id,
"amount": course_price,
"course": course_name
}
# Step 3: Send checkout URL to user with QR code
checkout_url = payment_request.checkout_url
keyboard = InlineKeyboardMarkup(
[[InlineKeyboardButton("💳 Pay with VietQR", url=checkout_url)]]
)
await update.message.reply_text(
f"🎓 Ready to buy **{course_name}**?\n\n"
f"Price: **{course_price:,} VND**\n\n"
f"Click the button below to scan the QR code with your bank app. "
f"You'll be redirected back here once payment is confirmed.",
reply_markup=keyboard,
parse_mode="Markdown"
)
# Step 4 (Background): Wait for settlement
asyncio.create_task(wait_for_payment(payment_request.order_id, user_id, course_name))
async def wait_for_payment(order_id: str, user_id: int, course_name: str):
"""
Poll AgentPay until payment settles (timeout: 30 minutes)
"""
max_wait = 1800 # 30 minutes
elapsed = 0
while elapsed < max_wait:
try:
# Check if payment has settled
settlement = await client.await_settlement(
order_id=order_id,
timeout=10 # Check every 10 seconds
)
if settlement.status == "confirmed":
# Payment successful! Grant access to course
await app.bot.send_message(
chat_id=user_id,
text=f"✅ **Payment confirmed!**\n\n"
f"Your access to {course_name} is now active.\n"
f"Check your email for login details.\n\n"
f"Transaction ID: `{settlement.transaction_id}`",
parse_mode="Markdown"
)
# Log the successful transaction
print(f"Payment confirmed: {order_id} | Amount: {settlement.amount} VND")
return
except asyncio.TimeoutError:
elapsed += 10
except Exception as e:
print(f"Error checking payment {order_id}: {e}")
await asyncio.sleep(10)
elapsed += 10
# Timeout — notify user
await app.bot.send_message(
chat_id=user_id,
text=f"⏱️ Payment timeout. If you already paid, please wait a few seconds and try /check_payment\n"
f"If not, use /buy_course to try again."
)
async def check_payment(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""
User can manually check status with /check_payment
"""
user_id = update.effective_user.id
user_orders = [o for o, p in pending_payments.items() if p["user_id"] == user_id]
if not user_orders:
await update.message.reply_text("No pending payments found.")
return
for order_id in user_orders:
try:
settlement = await client.get_payment_status(order_id)
if settlement.status == "confirmed":
await update.message.reply_text(f"✅ Payment {order_id} is confirmed!")
else:
await update.message.reply_text(f"⏳ Payment {order_id} is still pending...")
except Exception as e:
await update.message.reply_text(f"Error checking status: {e}")
app.add_handler(CommandHandler("buy_course", start_course_purchase))
app.add_handler(CommandHandler("check_payment", check_payment))
if __name__ == "__main__":
app.run_polling()
Line-by-line explanation:
- Lines 10-14: Initialize AgentPayClient with your credentials.
- Lines 23-39:
create_payment_request()generates a unique payment link tied to this user's order. - Lines 42-44: Store the order locally so we can track who paid for what.
- Lines 46-54: Build a Telegram inline button linking to the checkout URL (which displays the VietQR code).
- Lines 57: Kick off a background task to wait for settlement—the user doesn't have to stay in the chat.
- Lines 62-86:
await_settlement()polls the bank feed until payment is confirmed. Once confirmed, we grant course access. - Lines 88-105: Optional
/check_paymentcommand lets users manually check if payment went through.
Using AgentPay with Claude and MCP
If you're using AI agents (Claude, etc.) to manage your bot, AgentPay provides an MCP (Model Context Protocol) server. Install it:
pip install agentpay-mcp
Configure it in your Claude settings (or .mcp.json):
{
"mcpServers": {
"agentpay": {
"command": "python",
"args": ["-m", "agentpay_mcp.server"],
"env": {
"AGENTPAY_API_KEY": "your_api_key",
"AGENTPAY_MERCHANT_ID": "your_merchant_id"
}
}
}
}
Now Claude can call AgentPay functions directly:
User: "A customer wants to buy my e-book for 99,000 VND. Create a payment link."
Claude: I'll use AgentPay to create a payment request for you.
[Calls: create_payment_request(amount=99000, description="E-book", ...)]
→ Returns checkout_url
Claude: "Here's your payment link: [link]. Share it with your customer."
Real-World Walkthrough: Café Ordering Bot
Imagine a small coffee shop in Saigon. They built a Telegram bot where customers can order coffee, pay, and pick up. Here's how AgentPay fits:
- Order placed: Customer types
/order→ selects "2x Cà Phê Đen + 1x Trà Lạnh" (120,000 VND). - Payment link sent: Bot calls
create_payment_request()→ generates VietQR code → sends link to customer. - Customer scans: User opens their Agribank/Vietcombank app → scans QR → confirms transfer.
- Settlement confirmed: Bank webhook hits AgentPay →
await_settlement()resolves → bot sends "Order ready! Pick up at counter #3." - Record kept: Shop owner can see all payments in their AgentPay dashboard (linked to their actual bank statement).
Total time: 2 minutes. No middle app, no fees beyond normal bank transfer, no fraud risk.
Do's and Don'ts
| ✅ Do | ❌ Don't |
|---|---|
Use .env files for secrets |
Hardcode API keys in source code |
| Implement retry logic on timeouts | Assume QR codes are unique per user (they're not—reuse is safe) |
| Log transaction IDs for reconciliation | Hold customer funds in a separate account |
| Set reasonable timeouts (5–30 min) | Use old/revoked QR codes |
| Test with small amounts first | Ignore bank feed errors silently |
| Store order metadata (product, user, timestamp) | Forget to handle network disconnects |
Advanced Tips
Tip 1: Batch Payments
If you're selling multiple items, don't create separate payment requests. Sum the total and create one request:
total = sum([item["price"] for item in cart])
await client.create_payment_request(
amount=total,
description=f"{len(cart)} items",
order_id=f"cart_{user_id}_{timestamp}"
)
Tip 2: Idempotency
If your bot crashes mid-payment, reuse the same order_id to avoid double-charging:
order_id = f"course_{user_id}_{course_id}" # Deterministic
settlement = await client.get_payment_status(order_id)
if settlement and settlement.status == "confirmed":
# Already paid—grant access
pass
else:
# Create new request
await client.create_payment_request(order_id=order_id, ...)
Tip 3: Webhooks Instead of Polling
For production bots with thousands of users, polling is inefficient. Configure a webhook with AgentPay:
@app.post("/agentpay/webhook")
async def handle_settlement(payload: dict):
order_id = payload["order_id"]
status = payload["status"]
if status == "confirmed":
user_id = pending_payments[order_id]["user_id"]
await app.bot.send_message(user_id, "✅ Payment confirmed!")
This way, AgentPay notifies you when payment settles—no polling required.
FAQ
Q: Does AgentPay hold my money? No. The VietQR code points directly to your bank account. AgentPay only confirms settlement via your bank feed—it never touches the funds.
Q: What if a customer disputes the payment? AgentPay logs all transactions with bank-confirmed transaction IDs. Disputes go through your bank's normal chargeback process—AgentPay provides the proof.
Q: Can I use AgentPay outside Vietnam? Currently, VietQR is Vietnam-only. If you want to accept international payments, you'll need a separate solution (Stripe, PayPal).
Q: How long does settlement confirmation take? Typically 2–10 seconds after the customer completes the transfer. Occasionally up to 2 minutes depending on the bank.
Q: Do I need to register my bot with the government? Regulations vary. For educational or small commercial bots, you're usually fine. For large-scale commerce, consult a Vietnamese lawyer about merchant licensing.
Key Takeaways
- VietQR + AgentPay = friction-free payments in Telegram bots without holding customer funds.
- Installation is one line:
pip install agentpay-vn - The flow is simple:
create_payment_request()→send checkout_url()→await_settlement() - Bank confirmation is real: Your bank feed confirms every transaction—no fake payments.
- Works at scale: Use webhooks for thousands of concurrent users; use polling for hobby bots.
- MIT licensed & open-source: Full transparency, no vendor lock-in.
- Vietnam-specific: Built for VietQR, integrated with major Vietnamese banks.
Get Started Now
You have everything you need. Install AgentPay VN, wire up a test bot, and accept your first payment in under 20 minutes.
pip install agentpay-vn
Read the full API reference and bank-specific setup guides at https://agentpay.servicesai.vn/v1/docs and explore the source code on GitHub.
Have questions? Join the community, open an issue, or reach out. Happy collecting! 🎉