Collect VietQR Payments in Telegram Bots with AgentPay

2026-07-09 · AgentPay VN

telegrampaymentsvietqrpythonagentpay

The Problem: Why Most Telegram Payment Bots Fail

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.


What Is AgentPay VN?

AgentPay VN is an MIT-licensed Python SDK + MCP server that orchestrates VietQR payment requests. Here's the mental model:

  1. You create a payment request in your bot (e.g., "Customer ordered coffee for 50,000 VND")
  2. AgentPay generates a checkout URL with an embedded QR code
  3. Customer scans → pays → bank confirms settlement in seconds
  4. Your bot receives a webhook callback and fulfills the order

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.


Why Telegram + VietQR?

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.


Step 1: Installation & Setup

Install AgentPay SDK

pip install agentpay-vn

Verify the installation:

python -c "import agentpay; print(agentpay.__version__)"

Get Your Merchant Credentials

You'll need: - Merchant ID (from your bank's VietQR partner) - API Key (provided by the bank or AgentPay partner) - Bank Account details (IBAN/ACN)

For testing, AgentPay provides a sandbox environment. Check the official docs for your bank.

Set Environment Variables

export AGENTPAY_MERCHANT_ID="your_merchant_id"
export AGENTPAY_API_KEY="your_api_key"
export TELEGRAM_BOT_TOKEN="your_telegram_token"

Step 2: Build Your First Payment Bot

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

# Initialize AgentPay
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

    # Step 1: Create a payment request
    # This tells AgentPay: "I want 50,000 VND from user X"
    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"},
    )

    # Step 2: Get the checkout URL with embedded QR
    checkout_url = payment_request.get("checkout_url")
    payment_id = payment_request.get("payment_id")

    # Step 3: Send to user with inline button
    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
    )

    # Store payment_id for later confirmation
    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

    # Step 3: Await settlement confirmation
    # In production, listen to bank webhooks instead of polling
    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)"
        )
        # Grant access to course, send API key, etc.
    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:


Step 3: Production Setup with Webhooks

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

    # Verify the signature (AgentPay docs explain this)
    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":
        # Extract user ID from merchant_ref_id
        user_id = int(merchant_ref_id.split("_")[1])

        # Grant access to the user
        # e.g., send them an API key, activate subscription, etc.
        grant_access(user_id)

        # Notify user via Telegram
        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.


Step 4: Use AgentPay with Claude (MCP Server)

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/...

Real-World Example: Coffee Shop Telegram Bot

Imagine a café selling espresso shots via Telegram:

  1. Customer: /order → "1x Espresso (40,000 VND)"
  2. Bot: Creates payment request, sends QR code
  3. Customer: Scans QR, confirms in banking app (10 seconds)
  4. Bank: Settles to café's account instantly
  5. Webhook: Fires, bot receives settlement notification
  6. Bot: "✅ Order confirmed! Your coffee is ready at counter #3"
  7. Café owner: Checks dashboard, sees +40,000 VND received

The entire flow takes 30 seconds. No payment processor fees. No settlement delays.


Advanced Tips & Gotchas

Tip 1: Use Idempotent Keys

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()}"

Tip 2: Handle Webhook Timeouts

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):
    # Attempt up to 5 times with exponential backoff
    await bot.send_message(user_id, message)

Tip 3: Store Payment State

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)

Tip 4: Test on Sandbox First

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's and Don'ts

✅ 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

FAQ

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.


Key Takeaways


Next Steps

  1. Install AgentPay: pip install agentpay-vn
  2. Get credentials: Contact your bank or visit agentpay.servicesai.vn
  3. Clone the repo: github.com/phuocdu/agentpay-vn
  4. Read the docs: agentpay.servicesai.vn/v1/docs
  5. Build your bot: Follow the examples above and ship!

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.

Get started →

← All posts