AI Agent VietQR Payments in Python: Complete Guide

2026-09-28 · AgentPay VN

pythonvietqrai-agentspaymentsmcp

The Problem: AI Agents That Can't Actually Get Paid

You've built an impressive AI agent—maybe it sells online courses, manages a virtual café, or offers consulting services. Your Claude-powered chatbot charms customers, answers questions, and closes deals beautifully. But then comes the awkward moment: a customer says "I'm ready to pay," and your agent freezes. It has no way to collect money without you manually processing payments or routing users to a third-party checkout page.

This breaks the flow. The customer abandons the chat, opens a browser, and half the time, never comes back. Your agent becomes a glorified sales funnel instead of an autonomous business tool.

What if your AI agent could accept VietQR payments directly? No merchant account delays, no holding customer money, no third-party friction. Just a QR code that points straight to your bank account, settlement confirmation in seconds, and your agent continues the conversation.

That's exactly what AgentPay VN does.

What Is AgentPay VN?

AgentPay VN is an open-source (MIT license) Python SDK and MCP server that lets AI agents collect VietQR payments instantly. Here's what makes it different:

Whether you're in Hanoi, Ho Chi Minh City, or anywhere with Vietnamese banking, your AI agent can now be a full-service merchant.

Step 1: Install and Set Up AgentPay VN

Installation

Start with the Python package:

pip install agentpay-vn

This gives you the SDK. If you're also running the MCP server locally (for Claude Desktop or custom deployments), install that too:

pip install agentpay-mcp

Environment Setup

Your agent needs credentials to create payments. Create a .env file in your project:

AGENTPAY_MERCHANT_ID=your_merchant_id_here
AGENTPAY_BANK_ACCOUNT=your_vietqr_account
AGENTPAY_API_KEY=your_api_key_here

You'll get these from the AgentPay VN dashboard when you register. The merchant ID identifies your business; the bank account is your target VietQR-compatible account; the API key authenticates requests.

Load these in your Python code:

import os
from dotenv import load_dotenv

load_dotenv()

merchant_id = os.getenv('AGENTPAY_MERCHANT_ID')
bank_account = os.getenv('AGENTPAY_BANK_ACCOUNT')
api_key = os.getenv('AGENTPAY_API_KEY')

Step 2: Build Your First Payment Flow

Let's walk through the core 3-line pattern with a real example: a bot selling a Python course for 299,000 VND.

Code Example: Create Payment & Wait for Settlement

from agentpay_vn import PaymentClient, PaymentRequest, SettlementWatcher
import asyncio

# Initialize the payment client with your credentials
client = PaymentClient(
    merchant_id='your_merchant_id',
    api_key='your_api_key',
    bank_account='your_vietqr_account'
)

async def sell_course_to_customer(customer_name, customer_email, course_name):
    """
    Step 1: Create a payment request
    This generates a unique payment ID and amount tied to this customer.
    """
    payment_req = PaymentRequest(
        amount_vnd=299000,  # Course price
        description=f"Python Fundamentals Course - {customer_name}",
        customer_id=customer_email,  # Use email as unique ID
        metadata={'course': course_name, 'customer': customer_name}
    )

    # Create the payment on AgentPay's system
    payment = await client.create_payment_request(payment_req)

    """
    Step 2: Send the checkout URL to customer
    The payment object includes a checkout_url with the VietQR code.
    Your agent sends this to the customer via chat, email, or SMS.
    """
    checkout_url = payment.checkout_url
    qr_data = payment.qr_code  # Raw QR image data if needed

    agent_message = f"""
    Perfect! Your course is ready. Scan this QR code or click the link to pay:
    {checkout_url}

    Amount: 299,000 VND
    Course: {course_name}
    """
    print(agent_message)  # Agent sends this to customer

    """
    Step 3: Wait for settlement confirmation
    The bank feed integration watches your account. When the payment arrives,
    await_settlement resolves and returns the settlement confirmation.
    """
    settlement = await client.await_settlement(
        payment_id=payment.payment_id,
        timeout_seconds=600  # Wait up to 10 minutes
    )

    if settlement.confirmed:
        completion_message = f"""
        🎉 Payment received! Your course access is unlocked.
        Transaction ID: {settlement.transaction_id}
        Amount confirmed: {settlement.amount_vnd} VND

        Check your email for course materials.
        """
        print(completion_message)
        return {'status': 'success', 'course_access': True}
    else:
        print("Payment not received. Please try again.")
        return {'status': 'failed', 'course_access': False}

# Run the flow
result = asyncio.run(sell_course_to_customer(
    customer_name='Nguyen Van A',
    customer_email='a@example.com',
    course_name='Python Fundamentals'
))

Line-by-line breakdown:

  1. PaymentClient: Initializes with your merchant credentials. This is your agent's payment wallet.
  2. PaymentRequest: Defines what you're selling—amount, description, customer ID, and metadata (useful for tracking which product/course this is).
  3. create_payment_request: Sends the request to AgentPay's servers, gets back a payment object with a checkout_url (the VietQR link) and unique payment_id.
  4. Checkout URL: This is what your agent shows the customer. It's a QR code that points to the merchant's bank account (your account), not AgentPay's.
  5. await_settlement: Polls the bank feed and waits for confirmation that money landed. Once it does, your agent unlocks the course, sends credentials, or triggers next steps.

Step 3: Integrate with Claude via MCP

If you're using Claude (via Claude Desktop, API, or a custom client), the MCP server exposes AgentPay functions as tools Claude can call directly.

MCP Configuration

Add this to your Claude client's MCP config file (usually ~/.claude/mcp.json or the config in your client):

{
  "mcpServers": {
    "agentpay": {
      "command": "python",
      "args": ["-m", "agentpay_mcp"],
      "env": {
        "AGENTPAY_MERCHANT_ID": "your_merchant_id",
        "AGENTPAY_API_KEY": "your_api_key",
        "AGENTPAY_BANK_ACCOUNT": "your_vietqr_account"
      }
    }
  }
}

Once this is configured, Claude automatically sees three tools:

Claude Prompt Example

Tell Claude what you want:

You are a course sales agent. When a customer wants to buy the "Advanced Python" 
course (4,990,000 VND), use the agentpay create_payment_request tool to generate 
a payment link. Send the link to the customer. Then use await_settlement to wait 
for payment. Once settled, send them the course access code.

Now Claude can autonomously handle the entire sales transaction, from pitch to delivery, without leaving the chat.

Real-World Walkthrough: Virtual Café Bot

Imagine a WhatsApp chatbot for a Hanoi coffee shop. Customers order coffee, pay with VietQR, and get a pickup code.

from agentpay_vn import PaymentClient
import asyncio

client = PaymentClient(
    merchant_id='cafe_hanoi_001',
    api_key='sk_live_xxxxx',
    bank_account='cafe_hanoi_vietqr'
)

async def process_coffee_order(phone, order_items):
    """
    Customer: "I want 2 cappuccinos and 1 iced tea."
    Bot processes order and requests payment.
    """

    # Calculate total
    prices = {'cappuccino': 45000, 'iced_tea': 35000}
    total = sum(prices.get(item, 0) for item in order_items)
    order_id = f"order_{phone}_{int(time.time())}"

    # Create payment
    payment = await client.create_payment_request({
        'amount_vnd': total,
        'description': f"Coffee order: {', '.join(order_items)}",
        'customer_id': phone,
        'metadata': {'order_id': order_id, 'items': order_items}
    })

    # Send to customer
    print(f"Pay here: {payment.checkout_url}")
    print(f"Total: {total:,} VND")

    # Wait for payment
    settlement = await client.await_settlement(payment.payment_id, timeout_seconds=300)

    if settlement.confirmed:
        pickup_code = f"CAFE{order_id[-6:]}"
        print(f"✓ Payment confirmed! Your pickup code: {pickup_code}")
        print("Your order will be ready in 5 minutes.")
    else:
        print("Payment not received. Order cancelled.")

# Usage
asyncio.run(process_coffee_order(
    phone='+84901234567',
    order_items=['cappuccino', 'cappuccino', 'iced_tea']
))

Flow: Customer scans QR → pays from their bank app → 5 seconds later, bot confirms payment and gives them a pickup code. No cash, no manual approval, fully autonomous.

Common Patterns & Best Practices

DO ✅

Pattern Reason
Store payment_id in your database You need it to check settlement status later or handle timeouts
Set realistic timeouts (5–10 minutes) Customers take time to complete mobile payments
Include order/course info in metadata Track which product caused which payment for analytics
Use customer email or ID in customer_id Prevents duplicate payments from the same person
Log settlement confirmations For accounting and debugging

DON'T ❌

Anti-Pattern Problem
Block the chat indefinitely waiting for payment Timeout and let customer retry
Create multiple payment requests for the same order Customer might pay twice
Assume the user will scan immediately Always offer a fallback (email the link, SMS, etc.)
Skip error handling on await_settlement Network glitches happen; always catch exceptions
Reuse payment IDs across different transactions Each sale needs a unique payment ID

Advanced Tips

Handling Timeouts & Retries

Not all customers pay within 10 minutes. Handle gracefully:

async def sell_with_retry(amount, description, customer_id, max_attempts=3):
    for attempt in range(max_attempts):
        try:
            payment = await client.create_payment_request({
                'amount_vnd': amount,
                'description': description,
                'customer_id': customer_id
            })

            settlement = await client.await_settlement(
                payment.payment_id,
                timeout_seconds=300  # 5 minutes per attempt
            )

            if settlement.confirmed:
                return {'success': True, 'payment_id': payment.payment_id}
        except asyncio.TimeoutError:
            print(f"Attempt {attempt + 1} timed out. Retrying...")
            continue

    return {'success': False, 'message': 'Payment not received after 3 attempts'}

Webhooks (Advanced)

For high-volume scenarios, instead of blocking on await_settlement, register a webhook. AgentPay will POST settlement data to your server:

# Configure webhook in your agent code or dashboard
client.register_webhook(
    url='https://yourdomain.com/agentpay-webhook',
    events=['payment.settled', 'payment.failed']
)

Then handle the POST:

from flask import Flask, request

app = Flask(__name__)

@app.route('/agentpay-webhook', methods=['POST'])
def settlement_webhook():
    data = request.json

    if data['event'] == 'payment.settled':
        payment_id = data['payment_id']
        amount = data['amount_vnd']
        # Unlock course, send email, etc.
        print(f"Payment {payment_id} settled for {amount:,} VND")

    return {'ok': True}, 200

Multi-Currency (Future-Proofing)

Currently, AgentPay VN handles VND. For USD or international customers, consider converting at purchase time:

exchange_rate = 25000  # 1 USD = 25,000 VND (example)
usd_price = 12
vnd_price = int(usd_price * exchange_rate)

payment = await client.create_payment_request({
    'amount_vnd': vnd_price,
    'description': f'Purchase (${usd_price} USD)',
    'customer_id': customer_email,
    'metadata': {'original_currency': 'USD', 'original_amount': usd_price}
})

FAQ

Q: Does AgentPay hold my money?

A: No. The QR code points directly to your merchant's bank account. AgentPay VN is a facilitator only—it never touches customer funds. Settlement happens between the customer's bank and your bank.

Q: How quickly does settlement confirm?

A: Settlement typically confirms within 10–30 seconds via the bank feed. await_settlement polls and resolves the moment confirmation arrives. If no confirmation within your timeout, it returns confirmed=False and you can retry or escalate to manual review.

Q: Can I use this with Claude Desktop?

A: Yes. Configure the MCP server in your mcp.json, restart Claude Desktop, and Claude will see the AgentPay tools. You can then instruct Claude to handle payments autonomously.

Q: What if the customer's bank app doesn't support VietQR?

A: VietQR is supported by all major Vietnamese banks (Vietcombank, BIDV, Techcombank, etc.). If a customer's bank doesn't support it, they can't pay via this method—provide an alternative (traditional transfer, cash). This is rare in Vietnam as of 2024.

Q: How do I handle currency conversion for international customers?

A: Convert to VND at the time of payment using a reliable exchange rate API. Store both the original amount (USD, EUR, etc.) and VND amount in metadata for accounting.

Key Takeaways

Get Started Now

Your AI agent is ready to get paid. Here's what to do:

  1. Install AgentPay VN: pip install agentpay-vn
  2. Get credentials: Register at https://agentpay.servicesai.vn/v1/docs and grab your merchant ID and API key.
  3. Copy the payment flow code above into your agent.
  4. For Claude integration: Set up the MCP server following the config example.
  5. Test with a small amount: 10,000 VND to confirm everything works.
  6. Deploy and scale: Your agent is now a fully autonomous merchant.

Full documentation and examples are at https://agentpay.servicesai.vn/v1/docs. Source code is on GitHub: https://github.com/phuocdu/agentpay-vn.

Your agent has the ability to serve customers, close sales, and collect payment—all without you lifting a finger. Make it happen.

Get started →

← All posts