Build a Telegram Payment Bot with VietQR & AgentPay
The Problem: Why Most Telegram Bots Can't Take Money
Imagine you've built a Telegram bot that sells digital courses. Every day, 50+ users ask "how do I pay?" You're answering manually, copying bank details, waiting for transfers, manually confirming each one. By week two, you're drowning—and half your customers abandon the flow before payment.
This is the #1 blocker for Vietnamese developers monetizing Telegram bots. Payment gateways either require complex merchant registration (Stripe, 2Checkout), don't support VietQR natively, or demand a cut for holding customer funds. You need something lightweight, instant, and trustless.
Enter AgentPay VN—an open-source Python SDK that lets your Telegram bot generate VietQR QR codes pointing straight to your bank account. No middleman. No fund-holding. Settlement confirms automatically via bank feeds.
What Is AgentPay VN?
AgentPay VN is an MIT-licensed toolkit with two layers:
- Python SDK (
agentpay-vnpip package)—handles payment request creation, QR generation, and settlement confirmation. - MCP Server (
agentpay-mcp)—lets Claude and other AI agents orchestrate payments programmatically.
The 3-line payment flow:
- create_payment_request() → generates a unique VietQR request
- send_checkout_url() → delivers QR to the user (Telegram message, link, etc.)
- await_settlement() → blocks until bank confirms the transfer
Why it's different: - ✅ No fund custody: QR points straight at merchant's bank. You keep every ₫. - ✅ Instant settlement: Bank feed confirms within seconds of transfer. - ✅ AI-native: MCP server means Claude can create payments, check status, refund—all without human intervention. - ✅ Open-source: Full transparency. Run on your own infrastructure.
Step 1: Install and Configure AgentPay
Installation
pip install agentpay-vn
Verify the install:
python -c "import agentpay; print(agentpay.__version__)"
Get Your Bank Credentials
AgentPay uses your actual bank account for settlement. You'll need:
- Bank account number (chính chủ, VietQR-compatible)
- Bank code (e.g.,
970012for Techcombank,970022for ACB) - Account holder name (full legal name)
Create a .env file in your project:
AGENTPAY_BANK_ACCOUNT=1234567890
AGENTPAY_BANK_CODE=970012
AGENTPAY_MERCHANT_NAME="Nguyen Van A"
AGENTPAY_MERCHANT_ID="your-unique-id"
AGENTPAY_API_KEY="your-api-key"
TELEGRAM_BOT_TOKEN="your-telegram-token"
Initialize the SDK
import os
from agentpay import AgentPay
from dotenv import load_dotenv
load_dotenv()
# Initialize AgentPay client
client = AgentPay(
bank_account=os.getenv("AGENTPAY_BANK_ACCOUNT"),
bank_code=os.getenv("AGENTPAY_BANK_CODE"),
merchant_name=os.getenv("AGENTPAY_MERCHANT_NAME"),
merchant_id=os.getenv("AGENTPAY_MERCHANT_ID"),
api_key=os.getenv("AGENTPAY_API_KEY")
)
print("✓ AgentPay initialized successfully")
Step 2: Build Your Telegram Bot Payment Handler
Let's create a bot that sells a course ($5 USD ≈ 127,000 ₫). When a user types /buy_course, the bot generates a payment request and sends a VietQR QR code.
import asyncio
import logging
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Application, CommandHandler, ContextTypes
from agentpay import AgentPay
import os
from dotenv import load_dotenv
load_dotenv()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Initialize clients
agentpay_client = AgentPay(
bank_account=os.getenv("AGENTPAY_BANK_ACCOUNT"),
bank_code=os.getenv("AGENTPAY_BANK_CODE"),
merchant_name=os.getenv("AGENTPAY_MERCHANT_NAME"),
merchant_id=os.getenv("AGENTPAY_MERCHANT_ID"),
api_key=os.getenv("AGENTPAY_API_KEY")
)
async def buy_course(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""
Handles /buy_course command.
Creates a payment request, generates QR, and sends to user.
"""
user = update.effective_user
course_price_vnd = 127000 # ~$5 USD
transaction_id = f"course-{user.id}-{int(asyncio.get_event_loop().time())}"
try:
# Step 1: Create payment request via AgentPay
payment_req = await agentpay_client.create_payment_request(
amount=course_price_vnd,
description=f"Python Course - User {user.id}",
transaction_id=transaction_id,
customer_name=user.first_name or "Customer",
customer_phone="", # Optional
due_date=None # No expiry
)
# Step 2: Get VietQR checkout URL and QR image
checkout_url = payment_req.get("checkout_url")
qr_image_url = payment_req.get("qr_image_url")
logger.info(f"Payment request created: {transaction_id}")
# Step 3: Send QR to user via Telegram
keyboard = [
[InlineKeyboardButton("🏦 Pay Now", url=checkout_url)],
[InlineKeyboardButton("ℹ️ Instructions", callback_data="payment_help")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
message = (
f"🎓 *Python Mastery Course*\n\n"
f"💰 Price: {course_price_vnd:,} ₫\n"
f"📱 Scan the QR or click 'Pay Now'\n\n"
f"_Transaction ID: {transaction_id}_"
)
# Send QR image
if qr_image_url:
await update.message.reply_photo(
photo=qr_image_url,
caption=message,
reply_markup=reply_markup,
parse_mode="Markdown"
)
else:
await update.message.reply_text(
message + f"\n\n[Pay here]({checkout_url})",
reply_markup=reply_markup,
parse_mode="Markdown"
)
# Step 4: Wait for settlement (runs in background)
asyncio.create_task(
wait_for_settlement(transaction_id, user.id, context)
)
except Exception as e:
logger.error(f"Payment error: {e}")
await update.message.reply_text(
"❌ Payment setup failed. Please try again later."
)
async def wait_for_settlement(
transaction_id: str,
user_id: int,
context: ContextTypes.DEFAULT_TYPE
) -> None:
"""
Polls AgentPay for settlement confirmation.
Once confirmed, delivers course access to user.
"""
try:
# Block until payment is confirmed
settlement = await agentpay_client.await_settlement(
transaction_id=transaction_id,
timeout_seconds=600 # Wait up to 10 minutes
)
if settlement.get("status") == "settled":
logger.info(f"✓ Payment settled for {transaction_id}")
# Grant course access
# TODO: Update your database, send course link, etc.
await context.bot.send_message(
chat_id=user_id,
text=(
"✅ *Payment Confirmed!*\n\n"
"Your course access is ready.\n"
"Download here: [Course Materials](https://your-course-link)\n\n"
"Happy learning! 🚀"
),
parse_mode="Markdown"
)
else:
await context.bot.send_message(
chat_id=user_id,
text="⏳ Payment pending. We'll notify you when confirmed."
)
except asyncio.TimeoutError:
logger.warning(f"Settlement timeout for {transaction_id}")
await context.bot.send_message(
chat_id=user_id,
text="⏱️ Payment check timed out. Please verify manually."
)
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Start command."""
await update.message.reply_text(
"👋 Welcome to AgentPay Course Bot!\n"
"Type /buy_course to purchase."
)
async def main():
# Create bot application
application = Application.builder().token(
os.getenv("TELEGRAM_BOT_TOKEN")
).build()
# Add handlers
application.add_handler(CommandHandler("start", start))
application.add_handler(CommandHandler("buy_course", buy_course))
# Run bot
await application.run_polling()
if __name__ == "__main__":
asyncio.run(main())
Line-by-line breakdown:
- Line 11–22: Initialize AgentPay with credentials from
.env. - Line 31:
/buy_coursehandler triggered when user types the command. - Line 38–46:
create_payment_request()generates a unique payment with amount, description, and transaction ID. - Line 49–50: Extract checkout URL and QR image from response.
- Line 53–57: Build an inline keyboard with "Pay Now" button.
- Line 69: Send QR photo to user with Telegram's
reply_photo()method. - Line 76: Spawn a background task to wait for settlement.
- Line 81–125:
wait_for_settlement()polls AgentPay's bank feed. Once the transfer arrives, it confirms and grants course access.
Step 3: Integrate with Claude via MCP Server
Want Claude to handle payments autonomously? Use the MCP server.
Install MCP Server
pip install agentpay-mcp
Configure Claude to Use AgentPay Tools
Add this to your Claude configuration (e.g., claude_config.json):
{
"mcp_servers": {
"agentpay": {
"command": "python",
"args": ["-m", "agentpay_mcp"],
"env": {
"AGENTPAY_BANK_ACCOUNT": "1234567890",
"AGENTPAY_BANK_CODE": "970012",
"AGENTPAY_MERCHANT_NAME": "Nguyen Van A",
"AGENTPAY_MERCHANT_ID": "your-id",
"AGENTPAY_API_KEY": "your-key"
}
}
}
}
Now Claude can: - Create payment requests - Check settlement status - Issue refunds - Query transaction history
Example Claude prompt:
"A customer wants to buy 3 courses at 127k each. Create a payment request, generate the QR, and wait for settlement. Then send them the download link."
Claude will automatically call agentpay.create_payment_request(), agentpay.await_settlement(), and your delivery logic.
Real-World Case Study: Café Bot
Let's say you run a café and want customers to pre-order via Telegram.
Workflow:
1. Customer: /menu
2. Bot shows items with prices (cappuccino 45k, pastry 30k, etc.)
3. Customer clicks "Add to Cart" → /checkout
4. Bot creates a payment request for the total (e.g., 75k)
5. Customer scans QR with their banking app, pays instantly
6. Bot detects settlement within 3–5 seconds
7. Bot sends order confirmation + pickup time
Code snippet:
async def checkout(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
cart = context.user_data.get("cart", [])
total = sum(item["price"] for item in cart)
payment = await agentpay_client.create_payment_request(
amount=total,
description=f"Café Order - {len(cart)} items",
transaction_id=f"cafe-{update.effective_user.id}-{time()}"
)
await update.message.reply_text(
f"Total: {total:,} ₫\nScan to pay:",
photo=payment["qr_image_url"]
)
Best Practices & Do's/Don'ts
| ✅ DO | ❌ DON'T |
|---|---|
Store transaction_id in database for audit trail |
Hardcode bank credentials in code |
Use .env for sensitive data |
Create multiple payment requests for the same order |
| Set a reasonable timeout (5–10 min) for settlement | Assume payment is settled without await_settlement() |
| Log all payment events for debugging | Expose API keys in Git commits |
| Handle exceptions (network, timeout, invalid amount) | Manually confirm payments—trust the bank feed |
| Test with real amounts on testnet first | Process refunds without verifying settlement first |
Advanced Tips
1. Webhook-Based Settlement (No Polling)
Instead of await_settlement() blocking, configure AgentPay to send webhooks:
from flask import Flask, request
app = Flask(__name__)
@app.route("/webhook/agentpay", methods=["POST"])
def agentpay_webhook():
data = request.json
transaction_id = data.get("transaction_id")
status = data.get("status")
amount = data.get("amount")
if status == "settled":
# Deliver course/product immediately
deliver_product(transaction_id)
return {"ok": True}
2. Batch Refunds
Issue refunds programmatically:
await agentpay_client.create_refund(
original_transaction_id="course-123-456",
refund_amount=127000,
reason="Customer requested cancellation"
)
3. Multi-Currency Pricing
Store prices in USD, convert at request time:
USD_TO_VND_RATE = 24500 # Update daily via API
def get_vnd_price(usd: float) -> int:
return int(usd * USD_TO_VND_RATE)
# Usage
await agentpay_client.create_payment_request(
amount=get_vnd_price(5.0), # $5 = ~122,500 ₫
description="Digital product",
transaction_id=txn_id
)
FAQ
Q: Does AgentPay hold my money? A: No. The VietQR QR code points directly to your bank account. Money goes straight from the customer's bank to yours. AgentPay only monitors the transaction.
Q: How fast is settlement confirmation?
A: Bank feeds update within 3–5 seconds. await_settlement() typically confirms within 10 seconds of a successful transfer.
Q: Can I use AgentPay for refunds?
A: Yes. Call create_refund() with the original transaction ID. The SDK handles the reverse transfer to the customer's account.
Q: What happens if the customer closes the bot before paying? A: The payment request stays valid (unless you set an expiry). The customer can still scan the QR later. Your bot can retry settlement checks via background jobs or webhooks.
Key Takeaways
- 🎯 AgentPay VN = instant VietQR payments for Telegram bots, no middleman.
- 🔧 3-step flow:
create_payment_request()→send_checkout_url()→await_settlement(). - 🤖 MCP integration lets Claude autonomously create and confirm payments.
- 💾 Bank custody: Your funds go straight to your account; AgentPay confirms via bank feed.
- 📦 Open-source MIT license: Full transparency, run anywhere.
- 🚀 Production-ready: Used by 50+ Telegram bots, course platforms, and e-commerce stores.
Get Started Now
- Install:
pip install agentpay-vn - Read the docs: https://agentpay.servicesai.vn/v1/docs
- Explore the code: https://github.com/phuocdu/agentpay-vn
- Build your bot: Copy the examples above and adapt to your use case.
Have questions? Open an issue on GitHub or join the AgentPay community. Happy building! 🚀