Telegram Bot Payments with VietQR: AgentPay Guide

2026-07-31 · AgentPay VN

telegrampaymentsvietqrpythonagentpay

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:

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:

  1. Zero friction. User sees QR, scans with their banking app, pays in 10 seconds.
  2. Trust. VietQR is backed by the State Bank. Your customers know the payment is secure.
  3. 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:

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

Get Started Now

Your first payment-collecting Telegram bot is minutes away:

pip install agentpay-vn

Then head to:

Start collecting VietQR payments in your bot today. No fund holding, no vendor lock-in, just instant settlement straight to your Vietnamese bank account.

Get started →

← All posts