Build a Telegram Payment Bot with VietQR & AgentPay
The Problem: Running a Telegram Business Without Payment Integration
Imagine you're selling online courses through a Telegram bot. A student messages: "How do I pay?" Right now, you're copying a bank account number into chat, hoping they'll transfer the right amount with the correct reference. You wait hours—sometimes days—to manually confirm payment. Half your customers get confused and abandon the purchase. Your dream of automated digital product sales hits a wall.
This is the daily reality for thousands of Vietnamese creators, service providers, and small businesses trying to monetize on Telegram. You have an audience. You have a product. But payment collection feels stuck in 2015.
Enter AgentPay VN: an open-source Python SDK that lets your Telegram bot generate VietQR payment links in seconds, automatically confirm settlements, and continue conversations without you lifting a finger. No merchant account overhead. No money sitting in escrow. Just direct transfers to your bank account.
Why VietQR + Telegram = Perfect Match for Vietnamese Creators
VietQR is ubiquitous in Vietnam—every bank app supports it. Telegram's API is bulletproof and cost-free. Together, they're unstoppable.
Here's why AgentPay VN solves this uniquely:
- No fund holding: Money goes straight to your merchant bank account. AgentPay is invisible.
- MIT open-source: Full transparency. You own the code.
- MCP server support: Integrates with Claude and other AI agents—automate payment workflows end-to-end.
- 3-line API:
create_payment_request()→send checkout_url()→await_settlement(). That's it. - Bank feed confirmation: Real-time settlement notification, no webhooks to babysit.
Getting Started: Installation & Setup
Install the SDK
pip install agentpay-vn
That's your first step. The SDK is lightweight (~50KB uncompressed) and depends only on standard HTTP libraries.
Set Up MCP Server (Optional but Powerful)
If you're using Claude or planning to delegate payment logic to an AI agent:
pip install agentpay-mcp
Then configure Claude's MCP settings (e.g., in your .cursor/rules or Claude config):
{
"mcpServers": {
"agentpay-vn": {
"command": "python",
"args": ["-m", "agentpay.mcp"],
"env": {
"AGENTPAY_MERCHANT_ID": "your_merchant_id",
"AGENTPAY_API_KEY": "your_api_key"
}
}
}
}
Once configured, Claude can natively call AgentPay functions—perfect for building conversational payment flows.
Real-World Walkthrough: A Telegram Course-Selling Bot
Let's build a concrete example: TechCourse Bot. A creator sells Python courses ($15–$50 each) via Telegram. When someone messages /buy, the bot:
- Asks which course
- Generates a VietQR payment link
- Sends the checkout link
- Waits for settlement confirmation
- Delivers course access automatically
Step 1: Create a Payment Request
from agentpay_vn import AgentPay
import json
# Initialize the SDK
agent_pay = AgentPay(
merchant_id="YOUR_MERCHANT_ID",
api_key="YOUR_API_KEY"
)
# User buys the "Advanced Python" course for 199,000 VND
payment = agent_pay.create_payment_request(
amount_vnd=199000,
order_id="order_adv_python_001",
description="Advanced Python Course - TechCourse Bot",
customer_phone="0912345678",
metadata={
"course_id": "adv_python",
"customer_name": "Nguyen Van A",
"expires_in_minutes": 30
}
)
print(f"Payment Request Created:")
print(f" Order ID: {payment['order_id']}")
print(f" Amount: {payment['amount_vnd']:,} VND")
print(f" QR Code URL: {payment['qr_code_url']}")
print(f" Checkout URL: {payment['checkout_url']}")
# Output:
# Payment Request Created:
# Order ID: order_adv_python_001
# Amount: 199,000 VND
# QR Code URL: https://agentpay.servicesai.vn/qr/abc123xyz
# Checkout URL: https://checkout.agentpay.vn/abc123xyz
Line-by-line:
- create_payment_request() takes the amount in VND (Vietnamese Dong), a unique order ID, and optional metadata.
- It returns a response with qr_code_url (for inline display) and checkout_url (for sharing via link).
- The QR points directly at your bank account—AgentPay is transparent infrastructure.
Step 2: Send to Telegram User
import logging
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Application, CommandHandler, ContextTypes
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
async def buy_course(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle /buy command in Telegram."""
user = update.message.from_user
course_id = "adv_python"
amount_vnd = 199000
order_id = f"order_{course_id}_{user.id}_{int(time.time())}"
# Create payment request
payment = agent_pay.create_payment_request(
amount_vnd=amount_vnd,
order_id=order_id,
description=f"Course: Advanced Python",
customer_phone=user.username or "unknown",
metadata={"course_id": course_id, "user_id": user.id}
)
# Build inline keyboard with payment button
keyboard = [
[
InlineKeyboardButton(
"💳 Pay 199,000 VND",
url=payment['checkout_url']
)
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
# Send message with QR code image and payment link
await update.message.reply_photo(
photo=payment['qr_code_url'],
caption=(
f"🎓 *Advanced Python Course*\n\n"
f"💰 Price: 199,000 VND\n"
f"⏱️ Link expires in 30 minutes\n\n"
f"Scan the QR or tap the button below 👇"
),
reply_markup=reply_markup,
parse_mode='Markdown'
)
# Store order in memory/database for settlement tracking
context.user_data['pending_order'] = {
'order_id': order_id,
'course_id': course_id,
'user_id': user.id
}
logger.info(f"Payment link sent to {user.id} for order {order_id}")
async def main():
"""Start the bot."""
app = Application.builder().token("YOUR_TELEGRAM_BOT_TOKEN").build()
app.add_handler(CommandHandler("buy", buy_course))
await app.run_polling()
if __name__ == '__main__':
import asyncio
asyncio.run(main())
What happens: User taps the button or scans the QR. Their bank app opens with pre-filled details. They confirm. Money lands in your account.
Step 3: Wait for Settlement & Auto-Deliver
import asyncio
from datetime import datetime
async def check_settlement(order_id: str, max_wait_seconds: int = 1800):
"""Poll for payment settlement (30-minute timeout)."""
start_time = datetime.now()
poll_interval = 10 # Check every 10 seconds
while (datetime.now() - start_time).total_seconds() < max_wait_seconds:
settlement = agent_pay.await_settlement(
order_id=order_id,
timeout_seconds=poll_interval
)
if settlement['status'] == 'settled':
logger.info(f"✅ Payment confirmed for {order_id}")
logger.info(f" Settled amount: {settlement['amount_vnd']:,} VND")
logger.info(f" Settled at: {settlement['settled_at']}")
return settlement
elif settlement['status'] == 'pending':
logger.info(f"⏳ Waiting for settlement: {order_id}")
await asyncio.sleep(poll_interval)
else: # 'expired', 'failed', 'cancelled'
logger.warning(f"❌ Order {order_id} has status: {settlement['status']}")
return settlement
logger.error(f"Timeout waiting for settlement: {order_id}")
return None
async def process_settlement(user_id: int, course_id: str, order_id: str, context: ContextTypes.DEFAULT_TYPE):
"""Background task to monitor payment and deliver course."""
try:
settlement = await check_settlement(order_id)
if settlement and settlement['status'] == 'settled':
# Payment confirmed!
# 1. Generate course access (e.g., unique enrollment link)
enrollment_link = f"https://courses.example.com/enroll/{course_id}?token=unique_token_123"
# 2. Send to user
await context.bot.send_message(
chat_id=user_id,
text=(
f"🎉 *Payment successful!*\n\n"
f"Your course access is ready:\n"
f"🔗 {enrollment_link}\n\n"
f"Questions? Reply to this message."
),
parse_mode='Markdown'
)
# 3. Log to your system
logger.info(f"Course delivered to user {user_id} for {order_id}")
except Exception as e:
logger.error(f"Settlement check failed: {e}")
await context.bot.send_message(
chat_id=user_id,
text="⚠️ Payment verification issue. Please contact support."
)
# In your /buy handler, spawn the background task:
# asyncio.create_task(process_settlement(user.id, course_id, order_id, context))
The flow: After the user pays, your bot polls await_settlement() every 10 seconds. When the bank confirms the transfer, status changes to 'settled'. You immediately deliver the course—no manual intervention.
Advanced Tips & Patterns
1. Handle Multiple Courses with Dynamic Pricing
COURSES = {
"python_basics": {"name": "Python Basics", "price_vnd": 99000},
"adv_python": {"name": "Advanced Python", "price_vnd": 199000},
"web_dev": {"name": "Web Dev Masterclass", "price_vnd": 349000}
}
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Show course list with /start."""
keyboard = [
[InlineKeyboardButton(f"{data['name']} ({data['price_vnd']:,} VND)",
callback_data=f"buy_{course_id}")]
for course_id, data in COURSES.items()
]
await update.message.reply_text(
"📚 Choose a course:",
reply_markup=InlineKeyboardMarkup(keyboard)
)
2. Retry Failed Payments Gracefully
MAX_RETRIES = 3
async def retry_payment(order_id: str, user_id: int, context: ContextTypes.DEFAULT_TYPE, attempt: int = 1):
"""Retry payment with backoff."""
if attempt > MAX_RETRIES:
await context.bot.send_message(
chat_id=user_id,
text="❌ Payment failed after retries. Request a new link: /buy"
)
return False
settlement = await check_settlement(order_id, max_wait_seconds=300)
if settlement and settlement['status'] == 'settled':
return True
else:
wait_seconds = 2 ** attempt # Exponential backoff: 2s, 4s, 8s
await asyncio.sleep(wait_seconds)
return await retry_payment(order_id, user_id, context, attempt + 1)
3. Use MCP for AI-Driven Payment Workflows
If you're using Claude or another AI agent as your Telegram bot brains:
# Claude can now call:
# tools = [
# {"name": "create_payment_request", "params": [...] },
# {"name": "await_settlement", "params": [...] },
# {"name": "list_orders", "params": [...] }
# ]
# Example: Claude-driven conversation
# User: "I want to buy the web dev course"
# Claude: Calls create_payment_request() → extracts checkout_url → tells user to pay
# User pays
# Claude: Calls await_settlement() → delivers course when confirmed
Do's and Don'ts
| Do ✅ | Don't ❌ |
|---|---|
Store order_id in a database for audit trails |
Store sensitive bank details in Telegram messages |
Use unique order_id per transaction (include timestamp/user ID) |
Rely solely on client-side confirmation—always poll await_settlement() |
| Set reasonable QR expiry (15–30 min) via metadata | Send payment requests without clearing old expired ones |
| Log all settlement events for reconciliation | Assume payment succeeded without backend verification |
| Test with small amounts ($1–$5) first | Hardcode prices—fetch from your database |
| Monitor bank feeds daily | Leave polling tasks unmonitored—add error handling |
FAQ
Q: Does AgentPay hold my money? No. The QR code points directly to your merchant bank account. AgentPay is infrastructure—it never touches funds.
Q: What if a user pays twice by accident?
Each payment request has a unique order_id. Check your bank feed for duplicates and process refunds manually (or automate via bank API). Log duplicates and contact the user.
Q: Can I refund payments? Yes, via your bank app or API. AgentPay doesn't refund—it's your transaction. Many Vietnamese banks support automated refunds; check yours.
Q: How fast is settlement? Most Vietnamese banks settle within 1–5 minutes for same-bank transfers, 2–24 hours for cross-bank. AgentPay notifies you via bank feed as soon as it clears.
Q: Is there a fee? AgentPay itself has no transaction fee (open-source, MIT license). Your bank may charge standard VietQR transfer fees (~0–0.5% depending on the bank).
Key Takeaways
- VietQR + Telegram is native to Vietnam: Use what customers already have.
- AgentPay is minimal, transparent, and free: No escrow, no complications.
- The 3-line flow is genuinely simple: Create → Send → Await. That's payment automation.
- Start small: Test with a demo course or coffee shop. Build confidence before scaling.
- Monitor settlements daily: Reconcile your bank feed with bot records weekly.
- Use metadata wisely: Store course ID, user ID, and expiry—everything you need for post-payment logic.
- Combine with AI agents: MCP server unlocks Claude-powered payment conversations—next-level automation.
Next Steps
You now have a blueprint for monetizing on Telegram without payment complexity. Ready to build?
- Install:
pip install agentpay-vn - Get your merchant ID and API key from your Vietnamese bank (most major banks support VietQR merchant registration—free or low-cost).
- Check the docs: agentpay.servicesai.vn/v1/docs for full API reference.
- Explore the code: github.com/phuocdu/agentpay-vn — it's open-source, so fork it, extend it, and own it.
- Deploy: Host on Railway, Render, or your own VPS. Test with real payments.
Your Telegram bot is about to earn its first payment. That's not far away.