Build a Telegram Payment Bot with VietQR & AgentPay

2026-08-27 · AgentPay VN

telegramvietqrpythonpaymentsagentpay

The Problem: Why Telegram Bots Need Native Payment Support

Imagine you're running an online Vietnamese course platform. Students find you via Telegram, join your bot, and ask about enrollment. You send them a payment link—but it takes them outside the app, adds friction, and half abandon the checkout. Or worse: you're a café owner using a Telegram bot for orders, and customers can't pay directly in chat. They text you asking for bank details, you manually verify transfers, and your back-office becomes a mess.

This is where most Telegram payment integrations fail. They either:

  1. Require API heavy lifting – integrating Stripe, MoMo, or ZaloPay feels overkill for small merchants
  2. Hold customer money – payment processors take days to settle, and you're liable for disputes
  3. Don't work with local QR codes – VietQR is ubiquitous in Vietnam, but few tools support it natively in chat apps

AgentPay VN solves all three. In under 50 lines of Python, you can embed instant, settlement-confirmed VietQR payments directly into your Telegram bot. No escrow. No delays. Money lands in your bank account within hours, and your bot confirms it automatically.

Let's build one.


Why VietQR + Telegram Is the Perfect Pairing

VietQR is Vietnam's standardized QR code format. It's printed on every receipt, every bill, every storefront. Your customers already trust it. When a Telegram bot shows them a VietQR code, they don't think "another scam payment link"—they think "normal bank payment."

AgentPay VN treats VietQR as a first-class citizen:

The typical flow takes 3 seconds:

  1. User says "I want to buy"
  2. Bot calls create_payment_request()
  3. Bot displays QR code, awaits bank settlement
  4. Payment confirmed → deliver product/service

Installation & Setup

Step 1: Install the SDK

pip install agentpay-vn

Verify it works:

python -c "from agentpay_vn import create_payment_request; print('✓ AgentPay VN installed')"

Step 2: Get Your Merchant Info

You'll need:

Find your bank code here: VietQR bank list

Step 3: Set Environment Variables (Optional)

For security, store your merchant details in .env:

export AGENTPAY_ACCOUNT_NAME="Nguyen Van A"
export AGENTPAY_ACCOUNT_NUMBER="0123456789"
export AGENTPAY_BANK_CODE="970418"

Or pass them directly in your code (we'll show both below).


Building Your First Payment Flow

The 3-Step Pattern

Here's the complete flow for a Telegram bot:

from agentpay_vn import create_payment_request, await_settlement
import os
from telegram import Update
from telegram.ext import ContextTypes

# Initialize AgentPay with your merchant details
merchant_account = {
    "account_name": os.getenv("AGENTPAY_ACCOUNT_NAME"),
    "account_number": os.getenv("AGENTPAY_ACCOUNT_NUMBER"),
    "bank_code": os.getenv("AGENTPAY_BANK_CODE"),
}

async def handle_purchase(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """
    User sends /buy command → bot creates payment request → displays QR → awaits settlement
    """

    # STEP 1: Create the payment request
    payment_request = create_payment_request(
        amount=299_000,  # 299,000 VND for example course
        description="Online Python Course - 3 Months Access",
        merchant=merchant_account,
        request_id=f"telegram_{update.effective_user.id}_{update.message.date}",  # Unique ID
    )

    # payment_request contains:
    # - qr_code (PNG bytes)
    # - checkout_url (for sharing outside Telegram)
    # - settlement_reference (to verify later)
    # - expires_at (usually 15 minutes)

    # STEP 2: Send the QR code to user
    await update.message.reply_photo(
        photo=payment_request["qr_code"],
        caption=(
            f"💳 Scan to pay: {payment_request['amount']:,} VND\n"
            f"📝 {payment_request['description']}\n"
            f"⏰ Expires in 15 minutes\n\n"
            f"Your course access unlocks instantly after payment."
        ),
    )

    # Store the settlement reference for later verification
    context.user_data["pending_settlement"] = payment_request["settlement_reference"]

    # STEP 3: Wait for settlement (non-blocking)
    # In production, you'd use a background job queue. For this example:
    try:
        settlement = await_settlement(
            settlement_reference=payment_request["settlement_reference"],
            timeout_seconds=900,  # Wait up to 15 minutes
        )

        # Payment confirmed! Deliver the product
        await update.effective_user.send_message(
            text=(
                f"✅ Payment confirmed!\n\n"
                f"Amount: {settlement['amount']:,} VND\n"
                f"Settled at: {settlement['settled_at']}\n\n"
                f"Your course access is active. Login here: https://example.com/courses"
            )
        )

        # Log the transaction
        print(f"[PAYMENT] User {update.effective_user.id} paid {settlement['amount']} VND")

    except TimeoutError:
        await update.effective_user.send_message(
            "⏱️ Payment window expired. Please try again or contact support."
        )

# Wire this into your telegram.ext.Application

Line-by-Line Breakdown

Line 18-26 (create_payment_request): - amount: Payment in VND (must be integer) - description: What's being sold (shown on QR + bank statement) - merchant: Your bank details - request_id: Unique per transaction; we use user ID + timestamp

Line 32 (reply_photo): - Telegram automatically renders PNG QR codes. User scans with any banking app. - Caption explains the amount and deadline (15 minutes by default)

Line 42 (await_settlement): - Blocks until payment clears or times out - In production, wrap this in a Celery/RQ background task so your bot doesn't hang - settlement_reference ties the QR to the bank transaction


Real-World Example: A Café Order Bot

Let's say you run a café and want customers to order & pay via Telegram:

from agentpay_vn import create_payment_request, await_settlement
import asyncio

class CaféOrderBot:
    def __init__(self):
        self.orders = {}  # Track pending orders
        self.merchant = {
            "account_name": "Cafe Nguyen Coffee",
            "account_number": "0987654321",
            "bank_code": "970418",
        }

    async def process_order(self, user_id: str, items: list, total: int):
        """
        Example: user orders 2 iced coffees + 1 pastry = 85,000 VND
        """

        # Create payment
        payment = create_payment_request(
            amount=total,
            description=f"Café Order: {', '.join(items)}",
            merchant=self.merchant,
            request_id=f"cafe_{user_id}_{int(asyncio.get_event_loop().time())}",
        )

        # Store order
        self.orders[payment["settlement_reference"]] = {
            "user_id": user_id,
            "items": items,
            "amount": total,
            "qr_code": payment["qr_code"],
            "status": "awaiting_payment",
        }

        # Return QR for display
        return payment

    async def confirm_and_fulfill(self, settlement_reference: str):
        """
        Called when payment settles—trigger order fulfillment
        """

        order = self.orders[settlement_reference]
        order["status"] = "paid"

        # Send to kitchen
        print(f"🍳 [KITCHEN] Order for {order['user_id']}: {order['items']}")

        # Could integrate with kitchen display system (KDS) here
        # Notify customer
        return {
            "message": f"✅ Order confirmed! Ready in ~10 minutes. Pickup #42",
            "pickup_number": 42,
        }

In your Telegram handler, you'd call this and show the QR:

café = CaféOrderBot()

async def /order(update, context):
    # User selected items in inline menu
    items = ["Iced Americano", "Iced Americano", "Croissant"]
    total = 85_000

    payment = await café.process_order(str(update.effective_user.id), items, total)

    await update.message.reply_photo(
        photo=payment["qr_code"],
        caption=f"Pay {total:,} VND to confirm your order 🍵"
    )

    # Background task: await settlement
    settlement = await_settlement(payment["settlement_reference"], timeout_seconds=600)

    result = await café.confirm_and_fulfill(payment["settlement_reference"])
    await update.effective_user.send_message(result["message"])

Using AgentPay with AI Agents (MCP Server)

If you're building AI agents (Claude, ChatGPT plugins), use the MCP server:

pip install agentpay-mcp
agentpay-mcp --port 3001

Then configure Claude's MCP client (e.g., in Claude Desktop):

{
  "mcpServers": {
    "agentpay": {
      "command": "python",
      "args": ["-m", "agentpay_mcp"],
      "env": {
        "AGENTPAY_ACCOUNT_NAME": "Your Business",
        "AGENTPAY_ACCOUNT_NUMBER": "0123456789",
        "AGENTPAY_BANK_CODE": "970418"
      }
    }
  }
}

Now an AI agent can autonomously:

  1. Listen for payment requests from users
  2. Call agentpay:create_payment_request to generate QR codes
  3. Monitor settlements and unlock content without human intervention

Example:

User: "I want to buy your AI course. How much?"

Claude (via MCP): Calls create_payment_request(amount=500000, description="AI Mastery Course"), gets QR code, returns it.

User: Scans QR, pays from their banking app.

Claude (via polling/webhook): Detects settlement, sends course access link automatically.


Do's & Don'ts

✅ DO ❌ DON'T
Store settlement_reference to match QR to bank transactions Hardcode merchant details—use env vars
Set unique request_id per payment (prevents duplicates) Display the same QR twice (expires in 15 min)
Use background jobs for await_settlement() (don't block the bot) Trust QR codes without verifying bank feed
Log all transactions for accounting/audit Accept payments before await_settlement() completes
Test in sandbox mode first (if available) Share your AGENTPAY_BANK_CODE publicly

Advanced Tips

1. Custom Expiry Times

payment = create_payment_request(
    amount=100_000,
    description="Premium feature unlock",
    merchant=merchant_account,
    request_id="feature_unlock_user_123",
    expires_in_minutes=30,  # Default is 15; extend for high-value items
)

2. Handling Payment Failures Gracefully

async def safe_await_settlement(settlement_ref: str, max_retries: int = 3):
    for attempt in range(max_retries):
        try:
            return await_settlement(settlement_ref, timeout_seconds=60)
        except TimeoutError:
            if attempt < max_retries - 1:
                await asyncio.sleep(5)  # Wait 5s, then retry
            else:
                raise  # Final timeout

3. Batch Payments for Subscriptions

# Monthly subscription bot
async def renew_subscription(user_id: str, tier: str):
    prices = {"starter": 99_000, "pro": 299_000, "enterprise": 999_000}

    payment = create_payment_request(
        amount=prices[tier],
        description=f"{tier.title()} Plan - Monthly Renewal",
        merchant=merchant_account,
        request_id=f"sub_{user_id}_{date.today().isoformat()}",
    )

    # Store subscription state
    subscriptions[user_id] = {
        "tier": tier,
        "settlement_ref": payment["settlement_reference"],
        "expires_at": date.today() + timedelta(days=30),
    }

    return payment["qr_code"]

FAQ

Q: Does AgentPay hold my money?

No. The QR code points directly to your bank account. When customers scan and pay, the money goes straight to your bank. AgentPay only confirms settlement via bank feeds—it never touches the funds.

Q: Which banks are supported?

All major Vietnamese banks that support VietQR: Vietcombank, Techcombank, BIDV, Agribank, DongA Bank, etc. Check the bank code list for your bank's 6-digit code.

Q: How long does settlement take?

Typically 1-4 hours. Techcombank and BIDV are fastest (~30 minutes). Bank feeds notify AgentPay when money arrives, so your bot confirms near-instantly.

Q: Can I use AgentPay for international payments?

Currently, AgentPay supports Vietnamese bank accounts and VND only. If you need multi-currency, integrate with Stripe or PayPal separately.

Q: Is it secure?

Yes. AgentPay uses official VietQR standards. All payment requests are tied to your real bank account via standardized banking protocols. There's no custom wallet or payment processor—just standard banking APIs.


Key Takeaways


Next Steps

  1. Install AgentPay VN today: bash pip install agentpay-vn

  2. Read the full documentation: https://agentpay.servicesai.vn/v1/docs

  3. Explore the source code and contribute: https://github.com/phuocdu/agentpay-vn

  4. Build your first bot: Start with the café example above, adapt to your use case (courses, subscriptions, e-commerce orders), and go live in minutes.

Happy shipping. 🚀

Get started →

← All posts