AI Agent VietQR Payments in Python: Setup Guide

2026-06-23 · AgentPay VN

pythonvietqrai-agentspaymentsagentpay

The Problem: AI Agents Can't Collect Money (Yet)

You've built an impressive AI agent—maybe it handles customer support, sells digital products, or books appointments. It can talk, understand context, and make decisions. But the moment a customer says "I want to buy this," your agent hits a wall.

Traditional payment systems require: - Complex PCI compliance setups - Merchant accounts that take weeks - Managing customer card data (risky) - Heavy integration overhead

For Vietnamese merchants especially, accepting payments through AI agents meant either routing payments manually outside the agent or building custom integrations with banking APIs. Neither solution was elegant.

AgentPay VN changes that. It lets your Python AI agent generate a VietQR code, send it to the customer, and know instantly when payment lands—all without ever touching the money. The funds go straight to your merchant account.

Why VietQR + AI Agents = Perfect Pairing

VietQR is Vietnam's QR standard for bank-to-bank transfers. It's ubiquitous: customers scan with any banking app, pay directly from their account, and it's settled in seconds. For AI agents, this is ideal because:

  1. No token storage: Your agent never handles sensitive card data
  2. Instant confirmation: Bank feeds confirm settlement in real-time
  3. User familiar: Vietnamese users already know VietQR from daily transactions
  4. Stateless: Perfect for serverless and ephemeral agent environments

AgentPay VN abstracts the complexity into three methods: - create_payment_request() → generates a unique payment request - send_checkout_url() → delivers the QR to the user - await_settlement() → blocks until the bank confirms payment

Getting Started: Installation & Setup

Step 1: Install AgentPay VN SDK

pip install agentpay-vn

This gives you the core Python SDK (MIT licensed, open-source). Check out the GitHub repository to review the source.

Step 2: Set Environment Variables

You'll need merchant credentials from your Vietnamese bank (we support VietCombank, Techcombank, and others). Create a .env file:

BANK_CODE=970436  # VietCombank example
MERCHANT_ACCOUNT=0123456789  # Your bank account number
API_KEY=your_bank_api_key_here  # From your bank's developer portal
SECRET_KEY=your_secret_key_here

Step 3: Initialize AgentPay in Your Agent

If you're using Claude or another LLM agent, you'll want the MCP (Model Context Protocol) server to expose AgentPay as a tool:

pip install agentpay-mcp

Create mcp_config.json for Claude:

{
  "mcpServers": {
    "agentpay": {
      "command": "python",
      "args": ["-m", "agentpay.mcp.server"],
      "env": {
        "BANK_CODE": "970436",
        "MERCHANT_ACCOUNT": "0123456789",
        "API_KEY": "your_api_key",
        "SECRET_KEY": "your_secret_key"
      }
    }
  }
}

Now Claude (or your agent) can call AgentPay methods like any other MCP tool.

The 3-Line Payment Flow Explained

Understanding the Flow

Every payment in AgentPay VN follows this pattern:

  1. Request → Agent creates a unique payment request (amount, description, customer info)
  2. Checkout → Agent gets a URL with embedded VietQR code, sends it to customer
  3. Settlement → Agent polls bank API, detects when payment lands, resumes execution

Let's code this:

from agentpay_vn import AgentPayClient, PaymentRequest
import asyncio
import os

# Initialize the client
client = AgentPayClient(
    bank_code=os.getenv("BANK_CODE"),
    merchant_account=os.getenv("MERCHANT_ACCOUNT"),
    api_key=os.getenv("API_KEY"),
    secret_key=os.getenv("SECRET_KEY")
)

async def process_payment(customer_id: str, amount: int, description: str):
    """
    Process a VietQR payment for a customer.

    Args:
        customer_id: Unique customer identifier
        amount: Payment amount in VND
        description: Payment description (e.g., "Course Purchase")
    """

    # Step 1: Create the payment request
    # This generates a unique request ID and prepares the payment
    payment_req = PaymentRequest(
        amount=amount,
        description=description,
        customer_id=customer_id,
        reference_code=f"ORDER_{customer_id}_{int(time.time())}"
    )

    payment = await client.create_payment_request(payment_req)
    payment_id = payment["id"]

    print(f"✓ Payment request created: {payment_id}")
    print(f"  Amount: {amount:,} VND")

    # Step 2: Get the checkout URL with embedded VietQR code
    # You'd send this link/QR to your customer (via message, email, chat)
    checkout_url = await client.get_checkout_url(payment_id)
    print(f"✓ Send this to customer: {checkout_url}")

    # Step 3: Wait for settlement
    # This polls the bank API every 2 seconds until payment is confirmed
    # Timeout is 5 minutes by default
    try:
        settlement = await client.await_settlement(
            payment_id=payment_id,
            timeout_seconds=300
        )
        print(f"✓ Payment confirmed!")
        print(f"  Transaction ID: {settlement['transaction_id']}")
        print(f"  Settled at: {settlement['timestamp']}")
        return True
    except asyncio.TimeoutError:
        print(f"✗ Payment not received within 5 minutes")
        return False

# Example usage
if __name__ == "__main__":
    success = asyncio.run(
        process_payment(
            customer_id="CUST_789",
            amount=299000,  # ~$13 USD
            description="Python Course: Advanced Async Patterns"
        )
    )

    if success:
        print("\n→ Next step: Deliver course access to customer")

Line-by-line breakdown: - PaymentRequest object: Encapsulates payment metadata. The reference_code is crucial—it's your link to orders in your own database. - create_payment_request(): Contacts AgentPay backend, assigns a unique ID, validates credentials. Returns payment details. - get_checkout_url(): Generates a one-time URL containing the VietQR code (as a rendered SVG or PNG). This is what you show the customer. - await_settlement(): Enters a polling loop. Every 2 seconds, it queries your bank's API to check if the exact amount arrived. Once confirmed, returns transaction metadata.

Real-World Example: AI Course Selling Agent

Imagine you've built a chatbot that sells online courses. Here's how you'd wire AgentPay:

import json
from agentpay_vn import AgentPayClient
from datetime import datetime

class CourseSellingAgent:
    def __init__(self):
        self.client = AgentPayClient(
            bank_code="970436",
            merchant_account="1234567890",
            api_key=os.getenv("AGENTPAY_API_KEY"),
            secret_key=os.getenv("AGENTPAY_SECRET_KEY")
        )
        self.courses = {
            "python-async": {"name": "Advanced Python Async", "price": 299000},
            "langchain-pro": {"name": "LangChain + Claude", "price": 499000},
        }

    async def sell_course(self, user_id: str, course_key: str):
        """
        Handle course purchase flow.
        Called when user says 'I want to buy Advanced Python Async'
        """
        if course_key not in self.courses:
            return {"status": "error", "message": "Course not found"}

        course = self.courses[course_key]

        # Create payment request
        payment_req = PaymentRequest(
            amount=course["price"],
            description=f"Course: {course['name']}",
            customer_id=user_id,
            reference_code=f"{course_key}_{user_id}_{datetime.now().timestamp()}"
        )

        payment = await self.client.create_payment_request(payment_req)
        payment_id = payment["id"]

        # Get QR code URL
        checkout_url = await self.client.get_checkout_url(payment_id)

        # In a real agent, you'd send this via Telegram/Discord/WhatsApp:
        # - Display QR code image
        # - Provide checkout_url as clickable link
        # - Message: "Scan to complete purchase"

        return {
            "status": "awaiting_payment",
            "course": course["name"],
            "amount": course["price"],
            "qr_url": checkout_url,
            "payment_id": payment_id
        }

    async def confirm_and_deliver(self, payment_id: str, user_id: str):
        """
        Called after user scans QR. Waits for payment, then delivers course.
        """
        try:
            # Wait up to 10 minutes for payment
            settlement = await self.client.await_settlement(
                payment_id=payment_id,
                timeout_seconds=600
            )

            # Payment confirmed! Deliver course
            course_link = self._generate_course_access(user_id, payment_id)

            return {
                "status": "success",
                "message": "Payment received! Course access below:",
                "course_link": course_link,
                "transaction_id": settlement["transaction_id"]
            }
        except asyncio.TimeoutError:
            return {
                "status": "timeout",
                "message": "Payment not received. Please try again."
            }

    def _generate_course_access(self, user_id: str, payment_id: str) -> str:
        """Generate unique course access token."""
        # In production: create database record, generate JWT, etc.
        return f"https://courses.example.com/access?user={user_id}&payment={payment_id}"

In action: 1. User: "Sell me the Python Async course" 2. Agent calls sell_course() → receives QR URL 3. Agent displays QR + text: "Scan to pay 299,000 VND" 4. User scans, transfers money 5. confirm_and_deliver() detects payment in 2-3 seconds 6. Agent: "✓ Payment confirmed! Here's your course link..."

The entire flow is automatic. Your agent never saw the money.

Advanced Tips & Patterns

Handling Multiple Concurrent Payments

If you're running a high-traffic agent, you might have 50 payment requests pending. Don't block the main loop:

import asyncio
from typing import Dict, List

class PaymentTracker:
    def __init__(self, client: AgentPayClient):
        self.client = client
        self.pending: Dict[str, asyncio.Task] = {}

    async def track_payment(self, payment_id: str, callback):
        """
        Fire-and-forget settlement tracking.
        Calls callback(result) when payment lands.
        """
        async def _wait():
            try:
                result = await self.client.await_settlement(
                    payment_id=payment_id,
                    timeout_seconds=600
                )
                await callback(payment_id, result)
            except asyncio.TimeoutError:
                await callback(payment_id, {"status": "timeout"})

        task = asyncio.create_task(_wait())
        self.pending[payment_id] = task
        return task

    async def cleanup_completed(self):
        """Remove completed tasks from tracking."""
        completed = [
            pid for pid, task in self.pending.items()
            if task.done()
        ]
        for pid in completed:
            del self.pending[pid]

Error Handling & Retries

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 resilient_settlement_check(client, payment_id):
    """
    Retry up to 3 times with exponential backoff.
    Handles network glitches gracefully.
    """
    return await client.await_settlement(payment_id)

Webhook Confirmation (Alternative to Polling)

For production systems, avoid polling:

# Instead of await_settlement polling, your bank sends you a webhook:
from fastapi import FastAPI, Request

app = FastAPI()

@app.post("/webhooks/payment-confirmed")
async def handle_settlement_webhook(request: Request):
    """
    Bank POST's here when payment lands.
    Much more efficient than polling.
    """
    payload = await request.json()
    payment_id = payload["payment_id"]
    amount = payload["amount"]

    # Verify signature (prevent spoofing)
    signature = request.headers.get("X-AgentPay-Signature")
    if not verify_signature(payload, signature):
        return {"error": "Invalid signature"}, 401

    # Trigger fulfillment (deliver course, etc.)
    await fulfill_order(payment_id)

    return {"status": "received"}

Check the docs for webhook configuration.

Do's and Don'ts

Do Don't
Store reference_code in your database to link payments to orders Store VietQR codes—they're one-time use
Handle asyncio.TimeoutError gracefully (offer to retry) Assume payment is confirmed before await_settlement() returns
Use MCP server for Claude/LLM agents—it handles OAuth/secrets Hardcode API keys in your agent prompt
Rotate API keys every 90 days Reuse payment IDs across different transactions
Test in sandbox mode first (contact your bank) Call await_settlement() more than once per payment
Log transaction IDs for reconciliation Expose customer bank account numbers in logs

FAQ

Q: Does AgentPay VN hold my money?

No. AgentPay VN never touches your funds. The VietQR points directly at your merchant bank account. We're a payment orchestration layer—you're always in control.

Q: How fast is settlement?

2-3 seconds on average from scan to confirmation. VietQR is faster than card networks because it's bank-to-bank within Vietnam. By contrast, card processors often take 24-48 hours.

Q: What if my AI agent crashes during payment?

The payment still goes to your bank account—that's the beauty of QR codes. Your agent can recover by checking await_settlement() when it restarts. Store payment_id in your database so you can resume.

Q: Can I use AgentPay VN outside Vietnam?

Currently no. VietQR and the underlying bank APIs are Vietnam-specific. We're exploring Asia expansion—watch the GitHub.

Key Takeaways

Next Steps

  1. Install now: pip install agentpay-vn
  2. Read the full docs: https://agentpay.servicesai.vn/v1/docs
  3. Star the repo: https://github.com/phuocdu/agentpay-vn (open-source, MIT license)
  4. Get merchant credentials: Contact your Vietnamese bank's API team
  5. Build your agent: Use the examples above as templates

Your AI agent just became a payment-accepting system. Welcome to the future of conversational commerce in Vietnam.

Get started →

← All posts