DEV Community

ServicesAI VN
ServicesAI VN

Posted on • Originally published at agentpay.servicesai.vn

Collect VietQR Payments in Telegram Bots with AgentPay

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:

  • Payment gateways demand hefty setup fees and compliance audits
  • You end up holding customer money in an intermediary account (legal liability)
  • Settlement takes 3–5 days, frustrating both you and buyers
  • Integration is a maze of webhooks, IPNs, and error handling

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:

  • Customer receives a payment link in chat
  • Opens the QR code (in-app or screenshot)
  • Scans with their banking app (2–3 taps)
  • Money clears to your account in minutes
  • Bot auto-confirms and ships the digital good / service

No authentication screens. No signup. Pure banking rails.


Step 1: Installation & Setup

Install AgentPay SDK

pip install agentpay-vn
Enter fullscreen mode Exit fullscreen mode

Verify the installation:

python -c "import agentpay; print(agentpay.__version__)"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Line-by-line breakdown:

  • Lines 10–12: Initialize AgentPayClient with credentials from environment
  • Lines 19–21: /start sends a welcome message
  • Lines 23–37: /buy creates a payment request with amount, reference ID, and metadata
  • Lines 39–41: Extract the checkout URL (contains QR) and payment ID
  • Lines 43–47: Send an inline button linking to the checkout URL
  • Lines 51–65: /check polls for settlement status (use webhooks in production!)
  • Lines 67–74: On settlement, confirm and grant access

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Configure Claude's MCP settings:

{
  "mcpServers": {
    "agentpay": {
      "command": "agentpay-mcp",
      "env": {
        "AGENTPAY_MERCHANT_ID": "your_merchant_id",
        "AGENTPAY_API_KEY": "your_api_key"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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/...
Enter fullscreen mode Exit fullscreen mode

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()}"
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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

  • No middleman: AgentPay never holds funds; QR codes point straight to your bank account
  • Fast setup: Install with pip install agentpay-vn and get your first payment in under 10 minutes
  • Telegram-native: Customers pay without leaving Telegram; no signup required
  • Low fees: Pay only your bank's VietQR fees (0–0.5%), not 3% payment processor cuts
  • Production-ready: Use webhooks, idempotent IDs, and proper error handling
  • Open-source: MIT license means full transparency and community support
  • MCP integration: Use with Claude or other AI agents for autonomous payment orchestration

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.

Top comments (0)