Let Your AI Agent Accept VietQR Payments in Python

2026-10-15 · AgentPay VN

pythonai-agentsvietqrpaymentsopen-source

The Problem: AI Agents That Can't Take Money

You've built an AI chatbot that books consultations, sells digital products, or reserves café tables. It handles conversations flawlessly. But when a customer says "I'm ready to pay," your agent hits a wall. There's no way to collect payment without breaking character, handing off to Stripe, or setting up a complex webhook maze. You're leaving revenue on the table—literally—because your agent can't complete the transaction itself.

In Vietnam, this problem is even sharper. VietQR is ubiquitous and trusted, but integrating it into an AI workflow has meant choosing between heavy payment platforms (that take cuts) or building from scratch. AgentPay VN changes this. It's an open-source Python SDK that lets your AI agent generate a VietQR code, send it to the customer, and wait for bank settlement—all without touching the money. The QR points straight at the merchant's real bank account.

What Is AgentPay VN?

AgentPay VN is a lightweight, MIT-licensed Python SDK paired with an MCP (Model Context Protocol) server. Think of it as a bridge between your AI agent and Vietnam's banking system.

Key facts: - No fund custody. AgentPay VN never holds or transfers money. The QR code directs payment straight to your merchant bank account. - Bank-confirmed settlement. A real bank feed verifies when money arrives. - 3-line flow. Create a payment request → send the checkout URL → await settlement. That's it. - Open-source (MIT). Full transparency. Run it on your own infrastructure. - Works with Claude, o1, any LLM via MCP. Your agent gets payment tools as native functions.

Use cases: - Course-selling bots ("Pay now to access the module") - AI barista taking café orders ("Your total is 50,000 VND—scan this code") - Consultation booking ("Reserve 30 mins for 200,000 VND") - Freelance agent marketplaces - Donation campaigns with AI verification

Installation and Setup

Step 1: Install the SDK

pip install agentpay-vn

Verify the installation:

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

Step 2: Set Environment Variables

AgentPay VN requires minimal config. You'll need: - MERCHANT_ID: Your VietQR merchant identifier (provided by your bank or VietQR partner) - BANK_CODE: Your bank code (e.g., MB for MBBank, ACB for ACB) - ACCOUNT_NUMBER: Your receiving account number

Create a .env file:

MERCHANT_ID=your_merchant_id
BANK_CODE=MB
ACCOUNT_NUMBER=1234567890

Load it in your Python app:

import os
from dotenv import load_dotenv

load_dotenv()
MERCHANT_ID = os.getenv("MERCHANT_ID")
BANK_CODE = os.getenv("BANK_CODE")
ACCOUNT_NUMBER = os.getenv("ACCOUNT_NUMBER")

If you're using Claude or another LLM, run the MCP server:

pip install agentpay-mcp
agentpay-mcp

This exposes AgentPay as a set of callable tools for your AI agent.

Building Your First Payment-Enabled Agent

The 3-Step Flow

Every AgentPay transaction follows the same pattern:

  1. create_payment_request() – Generate a unique payment session
  2. send checkout_url() – Give the customer the VietQR code
  3. await_settlement() – Wait for bank confirmation

Code Example: A Course-Selling Bot

Let's build a simple AI agent that sells Python courses. The agent chats with the customer, and when they decide to buy, it handles payment end-to-end.

from agentpay_vn import AgentPayClient, PaymentRequest
import os
import time

# Initialize client (reads env vars)
client = AgentPayClient(
    merchant_id=os.getenv("MERCHANT_ID"),
    bank_code=os.getenv("BANK_CODE"),
    account_number=os.getenv("ACCOUNT_NUMBER")
)

def sell_course(customer_name: str, course_title: str, price_vnd: int) -> dict:
    """
    Orchestrate a course sale from request to settlement.

    Args:
        customer_name: Name of the buyer
        course_title: Course being sold
        price_vnd: Price in Vietnamese Dong

    Returns:
        Settlement confirmation dict
    """

    # Step 1: Create a payment request
    # This generates a unique session and prepares the QR data.
    payment_request = PaymentRequest(
        amount=price_vnd,
        description=f"Course: {course_title}",
        reference_id=f"COURSE_{customer_name}_{int(time.time())}",
        timeout_seconds=600  # 10 minute window to pay
    )

    # Step 2: Send to customer
    # In a real bot, you'd display the QR or send a link.
    checkout_url = client.create_payment_request(payment_request)
    print(f"\n📱 Pay here: {checkout_url}")
    print(f"💰 Amount: {price_vnd:,} VND")

    # Step 3: Wait for settlement
    # This polls the bank feed until payment is confirmed.
    print("\n⏳ Waiting for payment confirmation...")
    settlement = client.await_settlement(
        reference_id=payment_request.reference_id,
        max_wait_seconds=600
    )

    if settlement['status'] == 'confirmed':
        print(f"\n✅ Payment received!")
        print(f"   Settlement time: {settlement['settled_at']}")
        print(f"   Bank ref: {settlement['bank_reference']}")

        # Send course access
        send_course_access(customer_name, course_title)
        return {"success": True, "course": course_title}
    else:
        print(f"\n❌ Payment failed or timed out.")
        return {"success": False, "reason": settlement.get('error')}

def send_course_access(customer_name: str, course_title: str):
    """Mock function to grant course access."""
    print(f"\n🎓 Sending course link to {customer_name}...")
    print(f"   Download: https://courses.example.com/{course_title.replace(' ', '-')}")

# Run it
if __name__ == "__main__":
    result = sell_course(
        customer_name="Nguyễn Văn A",
        course_title="Advanced Python for AI",
        price_vnd=299000
    )
    print(f"\nResult: {result}")

Line-by-line breakdown: - Line 10–13: Initialize the AgentPayClient with your merchant credentials from env vars. - Line 19–30: Define the payment request with amount, description, and a unique reference ID (prevents duplicate payments). - Line 33: create_payment_request() returns a checkout URL containing the VietQR code data. - Line 39–41: await_settlement() blocks until the customer pays or the timeout expires. It polls your bank feed every few seconds. - Line 43–51: Check the settlement status and act accordingly (grant access, send confirmation, etc.).

Integrating with Claude via MCP

If you want Claude (or another LLM) to call these functions directly, use the MCP server. First, start the MCP server:

agentpay-mcp --port 3000

Then configure your LLM's Claude integration with this JSON config:

{
  "mcpServers": {
    "agentpay": {
      "command": "agentpay-mcp",
      "args": [],
      "env": {
        "MERCHANT_ID": "your_merchant_id",
        "BANK_CODE": "MB",
        "ACCOUNT_NUMBER": "1234567890"
      }
    }
  }
}

Now Claude can call create_payment_request, await_settlement, etc. as first-class functions in its tool set.

Real-World Walkthrough: AI Café Ordering Bot

Imagine a Telegram bot for a Saigon café. Customers chat to order, and the bot processes payment.

from agentpay_vn import AgentPayClient, PaymentRequest
import asyncio

client = AgentPayClient(
    merchant_id="cafe_123",
    bank_code="ACB",
    account_number="1234567890"
)

async def process_cafe_order(customer_id: str, items: list, total_vnd: int):
    """
    Customer has finished ordering. Take payment.
    Items: [{"name": "Cà phê đen", "qty": 2, "price": 20000}]
    """

    # Build order summary
    order_desc = "; ".join([f"{item['qty']}x {item['name']}" for item in items])

    # Create payment request
    payment = PaymentRequest(
        amount=total_vnd,
        description=f"Café order: {order_desc}",
        reference_id=f"CAFE_{customer_id}_{int(time.time())}"
    )

    qr_url = client.create_payment_request(payment)

    # Send QR to customer via Telegram/chat
    message = f"""☕ Your order:
{order_desc}

💰 Total: {total_vnd:,} VND
📱 Scan to pay: {qr_url}

Order ready in 10 minutes!
"""

    # Wait for payment (with timeout)
    settlement = client.await_settlement(
        reference_id=payment.reference_id,
        max_wait_seconds=300  # 5 minute window
    )

    if settlement['status'] == 'confirmed':
        # Mark order as paid, start preparing
        print(f"✅ Order {customer_id} paid. Start prep!")
        return {"paid": True, "ready_in_minutes": 10}
    else:
        print(f"❌ Order {customer_id} payment timeout.")
        return {"paid": False}

# Simulate
async def main():
    result = await process_cafe_order(
        customer_id="tran_123",
        items=[
            {"name": "Cà phê đen", "qty": 2, "price": 20000},
            {"name": "Bánh mì", "qty": 1, "price": 25000}
        ],
        total_vnd=65000
    )
    print(result)

asyncio.run(main())

The café bot now seamlessly collects payment without leaving Telegram. Customers scan the QR, the bot confirms, and the café's account receives the money directly.

Best Practices & Common Pitfalls

✅ Do ❌ Don't
Use unique reference_id for each payment to prevent duplicates Reuse the same reference ID across multiple payments
Set an appropriate timeout_seconds (5–15 minutes for most use cases) Leave timeout too short (customers need time to open banking app)
Store settlement['bank_reference'] for accounting records Ignore settlement details—you'll need them for reconciliation
Test with small amounts first (e.g., 1,000 VND) Go live with production without testing
Handle await_settlement() timeouts gracefully (offer retry or refund) Crash if payment doesn't arrive within timeout
Run the MCP server on a separate process/container for reliability Mix MCP server logic with your main agent code

Advanced Tips

1. Retry Logic for Timeouts

If the customer takes longer than expected:

max_retries = 3
for attempt in range(max_retries):
    settlement = client.await_settlement(
        reference_id=payment.reference_id,
        max_wait_seconds=600
    )
    if settlement['status'] == 'confirmed':
        break
    if attempt < max_retries - 1:
        print(f"Retry {attempt + 1}...")
        time.sleep(30)

2. Idempotency & Duplicate Prevention

Always include a unique reference ID tied to the customer and timestamp:

import hashlib

def generate_reference_id(customer_id: str, order_id: str) -> str:
    data = f"{customer_id}_{order_id}_{int(time.time())}"
    return hashlib.md5(data.encode()).hexdigest()[:16].upper()

3. Logging Settlements for Accounting

Store settlement data for reconciliation:

import json
from datetime import datetime

def log_settlement(settlement: dict):
    record = {
        "timestamp": datetime.utcnow().isoformat(),
        "reference_id": settlement['reference_id'],
        "amount": settlement['amount'],
        "status": settlement['status'],
        "bank_reference": settlement.get('bank_reference')
    }
    with open("settlements.jsonl", "a") as f:
        f.write(json.dumps(record) + "\n")

FAQ

Q: Does AgentPay VN hold my money? No. The QR code points directly to your merchant bank account. AgentPay VN is just a middleware layer—no money ever passes through it. Settlement is confirmed by your actual bank.

Q: What banks are supported? AgentPay VN supports any bank integrated with VietQR (which includes all major Vietnamese banks: MB, ACB, VCB, BID, etc.). Check your bank's VietQR provider.

Q: Can I run this locally for testing? Yes. Use a test/sandbox merchant ID from your bank. AgentPay VN works with both production and sandbox VietQR endpoints (configure via environment variables).

Q: What happens if the customer doesn't pay? The await_settlement() call times out. You can retry, ask for an alternative payment method, or cancel the transaction. Your agent remains in control.

Q: Is it really open-source? Yes, MIT license. Full source at GitHub. Review, fork, contribute.

Key Takeaways

Next Steps

Install AgentPay VN now:

pip install agentpay-vn

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

Explore the source: GitHub - AgentPay VN

Your AI agent is ready to sell. Let it collect VietQR payments with confidence.

Get started →

← All posts