You've built a Telegram bot that sells digital courses, takes coffee orders, or offers freelance services. Traffic is flowing. But when your bot tries to collect payment, you hit a wall:
A founder we know spent two weeks wrestling with Stripe's Telegram integration, only to discover the monthly fee ate 40% of his margins on $2β5 transactions. He needed something lighter, faster, and truly peer-to-peer.
Enter AgentPay VN: an open-source Python SDK that lets your Telegram bot generate QR codes pointing directly to your bank account. No middleman. No holding funds. Just instant VietQR payments via the banking system Vietnameses already use daily.
AgentPay VN is an MIT-licensed Python SDK + MCP server that orchestrates VietQR payment requests. Here's the mental model:
The key: AgentPay never touches money. The QR points straight at your merchant bank account. A bank feed confirms settlement. You own the transaction end-to-end.
Telegram has 180+ million users, with especially strong adoption in Southeast Asia. Vietnamese merchants already use banking apps (MB Bank, Techcombank, VCB) that natively support VietQR scanning. Your bot becomes a natural extension of their daily workflow:
No authentication screens. No signup. Pure banking rails.
pip install agentpay-vn
Verify the installation:
python -c "import agentpay; print(agentpay.__version__)"
You'll need:
For testing, AgentPay provides a sandbox environment. Check the official docs for your bank.
export AGENTPAY_MERCHANT_ID="your_merchant_id"
export AGENTPAY_API_KEY="your_api_key"
export TELEGRAM_BOT_TOKEN="your_telegram_token"
Here's a minimal Telegram bot that collects a payment:
import os
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Application, CommandHandler, ContextTypes
from agentpay import AgentPayClient
agentpay_client = AgentPayClient(
merchant_id=os.getenv("AGENTPAY_MERCHANT_ID"),
api_key=os.getenv("AGENTPAY_API_KEY"),
)
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Send welcome message with a /buy command."""
await update.message.reply_text(
"π Welcome! Use /buy to purchase an item.\n"
"Type /help for more info."
)
async def buy(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Create a payment request and send checkout link."""
user_id = update.effective_user.id
payment_request = agentpay_client.create_payment_request(
amount=50000, # VND
currency="VND",
merchant_ref_id=f"order_{user_id}_{int(time.time())}",
description="Premium Course - Python Mastery",
metadata={"user_id": user_id, "product": "course_python"},
)
checkout_url = payment_request.get("checkout_url")
payment_id = payment_request.get("payment_id")
keyboard = InlineKeyboardMarkup([
[InlineKeyboardButton("π³ Pay with VietQR", url=checkout_url)]
])
await update.message.reply_text(
f"π¦ Order Summary:\n"
f"Product: Premium Course\n"
f"Price: 50,000 VND\n\n"
f"Tap the button below to pay with your banking app.",
reply_markup=keyboard
)
context.user_data["pending_payment_id"] = payment_id
async def check_payment(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Poll for payment settlement (in production, use webhooks)."""
payment_id = context.user_data.get("pending_payment_id")
if not payment_id:
await update.message.reply_text("No pending payment found.")
return
status = agentpay_client.await_settlement(
payment_id=payment_id,
timeout=300, # Wait max 5 minutes
)
if status["settled"]:
await update.message.reply_text(
f"β
Payment confirmed!\n"
f"Amount: {status['amount']:,} VND\n"
f"Your premium course access is now active.\n"
f"π [Open Course](https://example.com/course)"
)
else:
await update.message.reply_text(
"β³ Payment not yet confirmed. Try again in a moment."
)
def main():
"""Start the bot."""
app = Application.builder().token(
os.getenv("TELEGRAM_BOT_TOKEN")
).build()
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("buy", buy))
app.add_handler(CommandHandler("check", check_payment))
print("π€ Bot running...")
app.run_polling()
if __name__ == "__main__":
import time
main()
Line-by-line breakdown:
/start
sends a welcome message/buy
creates a payment request with amount, reference ID, and metadata/check
polls for settlement status (use webhooks in production!)Polling is fine for demos, but slow and unreliable. Use bank webhooks to get instant payment confirmation:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/webhook/agentpay", methods=["POST"])
def handle_payment_webhook():
"""AgentPay calls this when payment settles."""
payload = request.json
if not agentpay_client.verify_webhook_signature(
payload, request.headers.get("X-Signature")
):
return {"error": "Invalid signature"}, 401
payment_id = payload["payment_id"]
status = payload["status"] # "settled" or "failed"
amount = payload["amount"]
merchant_ref_id = payload["merchant_ref_id"]
if status == "settled":
user_id = int(merchant_ref_id.split("_")[1])
grant_access(user_id)
asyncio.run(
send_telegram_message(
user_id,
f"β
Payment received! ({amount:,} VND)\n"
"Your access is active."
)
)
return {"status": "ok"}, 200
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Configure your webhook URL in the AgentPay dashboard to https://yourdomain.com/webhook/agentpay
.
If you're using Claude as your AI agent orchestrator, install the MCP server:
pip install agentpay-mcp
Configure Claude's MCP settings:
{
"mcpServers": {
"agentpay": {
"command": "agentpay-mcp",
"env": {
"AGENTPAY_MERCHANT_ID": "your_merchant_id",
"AGENTPAY_API_KEY": "your_api_key"
}
}
}
}
Now Claude can call AgentPay directly:
User: "I want to sell a course for 200,000 VND."
Claude: I'll create a payment request for you.
[Calls agentpay.create_payment_request()]
Claude: Payment link: https://checkout.agentpay.vn/...
Imagine a cafΓ© selling espresso shots via Telegram:
/order
β "1x Espresso (40,000 VND)"The entire flow takes 30 seconds. No payment processor fees. No settlement delays.
Always include a unique merchant_ref_id
to prevent duplicate charges if the network retries:
import uuid
merchant_ref_id = f"order_{user_id}_{uuid.uuid4()}"
Not all webhooks arrive instantly. Implement retry logic:
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(5), wait=wait_exponential())
async def notify_user_via_telegram(user_id, message):
await bot.send_message(user_id, message)
Use a database (PostgreSQL, MongoDB) to track payment status:
class PaymentRecord(db.Model):
payment_id = db.Column(db.String, primary_key=True)
user_id = db.Column(db.Integer)
amount = db.Column(db.Integer) # VND
status = db.Column(db.String) # pending, settled, failed
created_at = db.Column(db.DateTime, default=datetime.now)
AgentPay provides a sandbox environment. Always test before going live:
agentpay_client = AgentPayClient(
merchant_id="sandbox_merchant_id",
api_key="sandbox_api_key",
environment="sandbox", # Switch to "production" later
)
| β Do | β Don't |
|---|---|
| Use webhook callbacks for payment confirmation | Poll the API in a tight loop |
| Store merchant_ref_id; match it in webhook | Trust the order of webhook events |
| Verify webhook signatures | Skip signature verification |
| Implement idempotent request IDs | Assume network calls always succeed |
| Start with sandbox environment | Go live without testing |
| Monitor settlement times | Assume instant settlement (usually seconds, but be safe) |
| Log all payment events | Ignore payment failures silently |
Q: Can AgentPay handle payments in other currencies (USD, etc.)?
A: Currently, AgentPay VN is designed for VND (Vietnamese Dong) via VietQR, which is a Vietnamese banking standard. International currency support would require additional bank partnerships.
Q: What if the customer's bank doesn't support VietQR?
A: Most Vietnamese banks (MB, Vietcombank, Techcombank, ACB, VP Bank, etc.) support VietQR. If a customer's bank doesn't, they can ask their bank to enable it, or you can provide a fallback (e.g., direct bank transfer details).
Q: How much does AgentPay cost?
A: AgentPay itself is free (MIT open-source). You pay your bank's standard VietQR transaction fees (typically 0β0.5%), which is much cheaper than Stripe (2.9% + $0.30) or PayPal (3.5%).
Q: How long does settlement take?
A: Usually 1β5 seconds. The money goes directly to your bank account, so it's as fast as an interbank transfer. Some banks may batch settlements end-of-day, but that's rare.
pip install agentpay-vn
and get your first payment in under 10 minutespip install agentpay-vn
Your Telegram payment bot is minutes away. No more payment processor headaches. Just you, your customers, and the Vietnamese banking system doing what it does best.