Build a Telegram Payment Bot with VietQR & AgentPay

2026-08-21 · AgentPay VN

telegramvietqrpythonpaymentsagentpay

The Problem: Selling on Telegram Without Payment Friction

You've built a Telegram bot that delivers real value—course lessons, digital assets, coffee reservations, marketplace items. Users love it. But every time someone wants to buy, the conversation dies:

"Send me your bank details via DM. No wait, use this PayPal link. Actually, just Momo me..."

They abandon the chat. You lose the sale. Your bot sits incomplete—intelligent logic, zero monetization.

This is the gap AgentPay VN solves. In three lines of code, your Telegram bot becomes a payment-collecting machine. QR codes appear instantly. Money settles directly into your merchant account. No middleman. No complexity.

In this tutorial, you'll build a live Telegram bot that sells digital products using VietQR—and see deposits confirm in real time.

Why VietQR + Telegram Is the Vietnamese E-Commerce Sweet Spot

VietQR is ubiquitous in Vietnam. Every banking app displays it natively. Your customer scans a QR code from their Telegram chat, completes payment in seconds, and the bot instantly confirms delivery.

AgentPay VN eliminates the complexity. It's:

Part 1: Installation & Setup

Step 1: Install the SDK

pip install agentpay-vn

This gives you the core Python SDK. If you're using Claude or another agent framework:

pip install agentpay-mcp

This installs the MCP server wrapper, letting Claude call payment functions directly via tool use.

Step 2: Configure Your Merchant Account

You'll need:

  1. A Vietnamese bank account supporting VietQR (Vietcombank, Techcombank, Agribank, etc.).
  2. Your VietQR code (ask your bank, or generate via their online portal).
  3. API credentials from AgentPay (if using their default server; for self-hosting, use your own endpoint).

Store these as environment variables:

export AGENTPAY_MERCHANT_ID="your-merchant-id"
export AGENTPAY_SECRET="your-secret-key"
export VIETQR_ACCOUNT="your-bank-account-number"
export VIETQR_BANK_CODE="970407"  # Example: Techcombank

Step 3: Set Up Your Telegram Bot

You'll need the Telegram Bot API token. Create a bot via BotFather and save:

export TELEGRAM_BOT_TOKEN="your-telegram-token"

Part 2: The Three-Line Payment Flow Explained

AgentPay's philosophy is simplicity. Every payment follows three steps:

  1. create_payment_request() — Generate a unique payment order.
  2. send_checkout_url() — Push the QR checkout link to the user.
  3. await_settlement() — Wait for bank confirmation.

Understand this flow, and you've got 90% of what you need.

Part 3: Build Your First Bot

Here's a fully working Telegram bot that sells a digital product (say, a 10-lesson Python course for 149,000 VND):

import os
import asyncio
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Application, CommandHandler, CallbackContext, CallbackQueryHandler
from agentpay_vn import PaymentClient, PaymentRequest

# Initialize AgentPay and Telegram
payment_client = PaymentClient(
    merchant_id=os.getenv("AGENTPAY_MERCHANT_ID"),
    secret_key=os.getenv("AGENTPAY_SECRET"),
    api_url="https://agentpay.servicesai.vn/v1"  # Or your self-hosted endpoint
)

# Product catalog
PRODUCTS = {
    "python_course": {
        "name": "Complete Python Bootcamp",
        "price_vnd": 149000,
        "description": "10 lessons covering OOP, async, and web frameworks",
        "delivery_content": "https://example.com/course-link"
    }
}

async def start(update: Update, context: CallbackContext) -> None:
    """Send a welcome message with product options."""
    keyboard = [
        [
            InlineKeyboardButton(
                f"{PRODUCTS['python_course']['name']} (₫{PRODUCTS['python_course']['price_vnd']:,})",
                callback_data="buy_python_course"
            )
        ]
    ]
    reply_markup = InlineKeyboardMarkup(keyboard)
    await update.message.reply_text(
        "Welcome! 🎓 Check out our digital products:",
        reply_markup=reply_markup
    )

async def buy_product(update: Update, context: CallbackContext) -> None:
    """Handle purchase requests."""
    query = update.callback_query
    await query.answer()

    product_key = query.data.replace("buy_", "")
    product = PRODUCTS[product_key]
    user_id = query.from_user.id

    # Step 1: Create a payment request
    payment_req = PaymentRequest(
        merchant_order_id=f"order_{user_id}_{int(asyncio.get_event_loop().time())}",
        amount_vnd=product["price_vnd"],
        description=f"Purchase: {product['name']}",
        customer_email=query.from_user.username or "telegram_user",
        return_url="https://t.me/your_bot"  # Where user returns after payment
    )

    try:
        payment_response = await payment_client.create_payment_request(payment_req)
        order_id = payment_response["order_id"]
        checkout_url = payment_response["checkout_url"]
        qr_code_data = payment_response["qr_code"]

        # Step 2: Send checkout message with QR code
        await query.edit_message_text(
            text=(
                f"📱 **{product['name']}**\n"
                f"💰 Price: ₫{product['price_vnd']:,}\n\n"
                f"Scan this QR code to pay:\n"
                f"```\n{qr_code_data}\n```\n\n"
                f"_Your bot is waiting for payment confirmation..._"
            ),
            parse_mode="Markdown"
        )

        # Step 3: Wait for settlement (non-blocking)
        asyncio.create_task(wait_and_deliver(query, order_id, product, user_id))

    except Exception as e:
        await query.edit_message_text(
            text=f"❌ Payment setup failed: {str(e)}\n\nPlease try again or contact support."
        )

async def wait_and_deliver(query, order_id: str, product: dict, user_id: int) -> None:
    """Wait for payment settlement, then deliver the product."""
    try:
        # Step 3: Await settlement confirmation
        settlement = await payment_client.await_settlement(
            order_id=order_id,
            timeout_seconds=900  # 15 minutes
        )

        if settlement["status"] == "settled":
            # Payment confirmed!
            await query.bot.send_message(
                chat_id=user_id,
                text=(
                    f"✅ **Payment Confirmed!**\n\n"
                    f"Your access to **{product['name']}** is ready.\n"
                    f"📥 Download link: {product['delivery_content']}\n\n"
                    f"Thank you for your purchase! 🙏"
                ),
                parse_mode="Markdown"
            )
        else:
            await query.bot.send_message(
                chat_id=user_id,
                text=f"⏳ Payment pending. Status: {settlement['status']}"
            )

    except asyncio.TimeoutError:
        await query.bot.send_message(
            chat_id=user_id,
            text="⏱️ Payment timeout. Please try again or contact support."
        )
    except Exception as e:
        await query.bot.send_message(
            chat_id=user_id,
            text=f"❌ Settlement check failed: {str(e)}"
        )

async def main() -> None:
    """Start the bot."""
    application = Application.builder().token(os.getenv("TELEGRAM_BOT_TOKEN")).build()

    application.add_handler(CommandHandler("start", start))
    application.add_handler(CallbackQueryHandler(buy_product))

    await application.run_polling()

if __name__ == "__main__":
    asyncio.run(main())

What's happening line by line:

Part 4: Using AgentPay with Claude (MCP Mode)

If you're letting Claude manage your bot's logic, use the MCP server. First, ensure it's installed:

pip install agentpay-mcp

Configure your Claude client with this MCP definition:

{
  "name": "agentpay-vn-mcp",
  "command": "python",
  "args": ["-m", "agentpay_mcp"],
  "env": {
    "AGENTPAY_MERCHANT_ID": "${AGENTPAY_MERCHANT_ID}",
    "AGENTPAY_SECRET": "${AGENTPAY_SECRET}",
    "VIETQR_ACCOUNT": "${VIETQR_ACCOUNT}",
    "VIETQR_BANK_CODE": "${VIETQR_BANK_CODE}"
  }
}

Now Claude can call:

claude_call("create_payment_request", {"amount_vnd": 149000, "description": "Python Course"})
claude_call("await_settlement", {"order_id": "order_123", "timeout_seconds": 900})

Claude manages the entire flow—from generating QR codes to confirming delivery—without you writing async code.

Part 5: Real-World Example: A Café Pre-Order Bot

Let's say you run a café and want customers to pre-order from Telegram. They browse a menu, order, pay via VietQR, and pick up at a specific time.

Flow:

  1. Bot shows menu: "Cà phê đen (35k) | Matcha Latte (55k) | Croissant (25k)"
  2. User taps "Matcha Latte."
  3. Bot creates a payment request for 55,000 VND.
  4. User scans QR code → pays from their banking app.
  5. Bot confirms: "Your order is ready at 3 PM, table 7. Pick-up code: ABC123."
  6. Settlement notification reaches your phone.

The entire process takes ~2 minutes. No SMS, no phone calls, no manual verification.

Part 6: Do's and Don'ts

✅ Do ❌ Don't
Store order metadata (product name, user ID, timestamp) in your own database before creating the payment request. Assume the QR code is valid indefinitely—they expire after a time window (default: 15 min). Refresh if needed.
Test with small amounts (5,000–10,000 VND) first to validate your bank integration. Hard-code merchant IDs or secrets. Use environment variables.
Use await_settlement() with a reasonable timeout (e.g., 900 seconds = 15 min). Users take time to open banking apps. Implement your own polling loop instead of await_settlement()—AgentPay's method is optimized for bank feed latency.
Log the order_id and merchant_order_id for every transaction—essential for reconciliation. Deliver digital goods before await_settlement() returns status == "settled".
Monitor your bot's logs for PaymentClient errors. They're your first sign of bank connectivity issues. Forget that AgentPay never holds money. If your bot crashes post-settlement, the user's payment is already safe in your bank account.

Part 7: Advanced Tips

Tip 1: Reconciliation Dashboard

Create a simple admin endpoint that shows settlements:

async def reconciliation_report():
    """Fetch all settled orders from the last 7 days."""
    settled_orders = await payment_client.list_settlements(
        start_date="2024-01-01",
        end_date="2024-01-07",
        status="settled"
    )
    total_vnd = sum(order["amount_vnd"] for order in settled_orders)
    print(f"Settled this week: {total_vnd:,} VND across {len(settled_orders)} orders")

Tip 2: Retry Logic for Flaky Networks

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
async def create_payment_with_retry(payment_req):
    return await payment_client.create_payment_request(payment_req)

Tip 3: Multi-Product Catalog with Webhooks

If AgentPay supports webhooks (check the docs), register a webhook endpoint so settlement notifications arrive immediately:

app.post("/agentpay-webhook")
async def handle_settlement_webhook(request):
    payload = await request.json()
    order_id = payload["order_id"]
    status = payload["status"]

    if status == "settled":
        # Immediately deliver without polling
        await deliver_product_for_order(order_id)

    return {"success": True}

FAQ

Q1: Does AgentPay take a cut of my payments? No. AgentPay is a non-custodial payment SDK. 100% of the customer's payment flows to your merchant bank account. AgentPay doesn't hold, freeze, or redirect funds. You only pay for infrastructure (if using their hosted MCP server) or self-host for free.

Q2: What if a customer scans the QR code but doesn't pay within 15 minutes? The QR code expires (default: 15 minutes). You can either prompt the user to request a fresh one or increase the timeout in PaymentRequest. The bot should re-call create_payment_request() to generate a new QR.

Q3: Can I use AgentPay with a Viettelpay or Momo wallet instead of a bank? AgentPay is VietQR-specific, which routes to bank accounts. Momo and Viettel operate separate payment rails. For multi-wallet support, you'd integrate those SDKs separately. However, VietQR covers ~95% of Vietnamese banking, so one integration often suffices.

Q4: Is my bot compliant with Vietnamese fintech regulations? AgentPay handles the technical plumbing. You should ensure you have a business license and comply with SBV (State Bank of Vietnam) guidelines for payment service users. Consult a local legal advisor—AgentPay's non-custodial model reduces compliance burden, but due diligence is still your responsibility.

Key Takeaways

Next Steps

You're ready to monetize your Telegram bot. Here's the action plan:

  1. Install AgentPay: pip install agentpay-vn
  2. Read the full documentation: https://agentpay.servicesai.vn/v1/docs
  3. Explore the GitHub repo for more examples: https://github.com/phuocdu/agentpay-vn
  4. Build your first payment request with a test amount.
  5. Deploy to production and watch settlements flow into your account.

The Vietnamese e-commerce landscape is moving toward bot-native commerce. With AgentPay, you're not just building a chatbot—you're building a payment-enabled business. Start small, ship fast, and scale.

Happy selling. 🚀

Get started →

← All posts