# Collect VietQR Payments in Telegram Bots with AgentPay

> Source: <https://dev.to/ai_services_f9c382bdb33b9/collect-vietqr-payments-in-telegram-bots-with-agentpay-4neb>
> Published: 2026-07-09 00:35:20+00:00

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
python -c "import agentpay; print(agentpay.__version__)"
```

You'll need:

For testing, AgentPay provides a sandbox environment. Check the [official docs](https://agentpay.servicesai.vn/v1/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:

``` python
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:**

`/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:

``` python
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`

.

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:

``` python
import uuid

merchant_ref_id = f"order_{user_id}_{uuid.uuid4()}"
```

Not all webhooks arrive instantly. Implement retry logic:

``` python
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)
```

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 minutes`pip 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.
