Add VietQR Checkout to Your LLM Agent in 10 Minutes

2026-09-01 · AgentPay VN

pythonai-agentspayment-integrationvietqrllm

The Problem: Your AI Agent Can't Close Sales

You've built an intelligent agent—maybe it's a course-selling chatbot, a customer support bot that upsells, or a café ordering assistant. It can negotiate, recommend products, and build rapport. But when it comes time to actually collect payment, the conversation hits a dead end. You paste a Stripe link, hope the customer clicks it, and lose half of them to friction.

Worse, integrating traditional payment APIs into agent workflows is messy: you need webhooks, session management, database polling. Your agent becomes a payment infrastructure engineer instead of a sales closer.

What if payment collection was as simple as three function calls in your agent's toolkit?

Enter AgentPay VN: Payments for AI Agents

AgentPay VN is an open-source (MIT license) Python SDK + MCP server purpose-built for AI agents to collect payments via VietQR—Vietnam's instant bank transfer standard. Here's what makes it different:

By the end of this guide, you'll have a working agent that generates personalized checkout URLs and confirms payments in real time.

Why This Matters: The Economics of Friction

Each step in a payment flow costs you conversions. Traditional integrations require:

  1. User leaves chat → clicks external link
  2. User fills payment form (again)
  3. User waits for confirmation
  4. Webhook arrives (eventually)
  5. Agent resumes conversation

With AgentPay VN, your agent stays in control:

  1. Agent generates checkout URL
  2. User scans QR with banking app (already trusted)
  3. Transfer completes instantly
  4. Agent confirms and fulfills (within seconds)

On a typical conversion funnel, removing friction at step 1 increases completion by 15–30%.

Step 1: Install and Configure

Install the SDK:

pip install agentpay-vn

That's it for the core library. If you're running Claude or another LLM via MCP (Model Context Protocol), also install the server:

pip install agentpay-mcp

For MCP integration with Claude, add this to your Claude config file (e.g., claude_desktop_config.json):

{
  "mcpServers": {
    "agentpay-vn": {
      "command": "python",
      "args": ["-m", "agentpay_mcp.server"]
    }
  }
}

Restart Claude, and it will auto-discover AgentPay's tools. No environment variables needed—it's zero-config by design.

Step 2: Create Your First Payment Request (5 minutes)

Here's the core workflow in Python. Let's build a simple course-selling bot:

from agentpay_vn import create_payment_request, await_settlement
import uuid
from datetime import datetime, timedelta

# 1. Define your course offering
course_name = "Python for AI Agents"
price_vnd = 299000  # ~$12 USD
merchant_account = "0123456789@vietcombank"  # Your VietQR account

# 2. Generate a unique request ID (prevents duplicates)
request_id = str(uuid.uuid4())[:8]

# 3. Create the payment request
payment = create_payment_request(
    merchant_account=merchant_account,
    amount_vnd=price_vnd,
    description=f"Course: {course_name}",
    request_id=request_id,
    expires_at=datetime.now() + timedelta(minutes=15)  # 15-min expiry
)

# 4. Send to your agent's user
print(f"Checkout URL: {payment.checkout_url}")
print(f"QR Code: {payment.qr_code_url}")
print(f"Payment ID: {payment.payment_id}")

Line-by-line breakdown: - merchant_account: Your bank account in VietQR format (bank ID + account number). - amount_vnd: Price in Vietnamese Dong. 1 USD ≈ 24,500 VND. - description: Shown in the user's banking app (e.g., "Course: Python for AI Agents"). - request_id: A unique identifier; AgentPay uses this to prevent duplicate charges if your agent retries. - expires_at: Payment link validity. After this, new scans won't work (fraud protection).

The function returns a payment object with three key fields: - checkout_url: Share this with the user or embed it in your chat. - qr_code_url: Direct image URL of the VietQR code (scannable). - payment_id: Track this payment in your database.

Step 3: Await Settlement and Fulfill (3 minutes)

Once the user scans and transfers, your agent needs to confirm. Here's the waiting side:

from agentpay_vn import await_settlement
import asyncio

async def confirm_course_access(payment_id, user_email):
    """
    Wait for payment settlement, then grant course access.
    """
    # 4. Wait for the bank to confirm the transfer
    # Timeout after 5 minutes (user might abandon)
    settlement = await_settlement(
        payment_id=payment_id,
        timeout_seconds=300  # 5 minutes
    )

    # 5. Settlement confirmed by bank feed
    if settlement.confirmed:
        print(f"✓ Payment received: {settlement.amount_vnd} VND")
        print(f"  From: {settlement.sender_account}")
        print(f"  Confirmation ID: {settlement.txn_ref}")

        # 6. Fulfill the order
        send_course_link(user_email)
        send_slack_notification(f"Sale: {settlement.amount_vnd} VND from {user_email}")

        return True
    else:
        print(f"✗ Payment did not settle within 5 minutes")
        return False

# Use this in your agent's workflow
# asyncio.run(confirm_course_access(payment.payment_id, "student@example.com"))

Key points: - await_settlement() blocks until the bank confirms the transfer or timeout occurs. No polling, no webhooks. - settlement.confirmed is True only after the bank feed confirms money arrived in your account. - settlement.txn_ref is the bank's official reference number—log this for reconciliation. - Never grant access until confirmed=True. The VietQR transfer is instant, but the bank confirmation is your guarantee.

Real-World Walkthrough: AI Café Ordering Bot

Let's see this in action with a concrete example: a café that uses an AI agent to take orders via messaging.

Scenario: A customer messages the café bot: "I'd like a cappuccino and a croissant for pickup tomorrow at 9 AM."

The bot: 1. Confirms the order ("Cappuccino, croissant, 9 AM tomorrow. Total: ₫65,000") 2. Creates a payment request: python order_payment = create_payment_request( merchant_account="0987654321@vietinbank", amount_vnd=65000, description="Café order #ORD-2024-001", request_id="ord-001" ) 3. Sends the checkout URL to the customer. 4. Waits for settlement: python if await_settlement(order_payment.payment_id).confirmed: bot.send_message("Payment received! Your order is confirmed for 9 AM tomorrow.") bot.notify_kitchen("Order confirmed: cappuccino + croissant") 5. The customer scans the QR with their phone's banking app, approves the ₫65,000 transfer to the café's account. 6. The bot detects settlement within 2–3 seconds and sends a confirmation. 7. Kitchen starts preparing the order.

Result: Frictionless ordering, zero payment failures, instant kitchen notification.

Integrating with Claude via MCP

If you're using Claude as your agent, AgentPay VN exposes tools via MCP. Claude automatically discovers them:

{
  "tools": [
    {
      "name": "create_payment_request",
      "description": "Generate a VietQR payment link for the user. Returns checkout_url and qr_code_url.",
      "input_schema": {
        "type": "object",
        "properties": {
          "merchant_account": {
            "type": "string",
            "description": "Bank account in VietQR format (e.g., 0123456789@vietcombank)"
          },
          "amount_vnd": {
            "type": "integer",
            "description": "Amount in Vietnamese Dong"
          },
          "description": {
            "type": "string",
            "description": "Order/product description shown to user"
          },
          "request_id": {
            "type": "string",
            "description": "Unique identifier for idempotency"
          }
        },
        "required": ["merchant_account", "amount_vnd", "description", "request_id"]
      }
    },
    {
      "name": "await_settlement",
      "description": "Wait for bank confirmation of a VietQR payment (up to 5 minutes).",
      "input_schema": {
        "type": "object",
        "properties": {
          "payment_id": {
            "type": "string",
            "description": "Payment ID returned by create_payment_request"
          },
          "timeout_seconds": {
            "type": "integer",
            "description": "Maximum wait time (default: 300)",
            "default": 300
          }
        },
        "required": ["payment_id"]
      }
    }
  ]
}

When Claude calls create_payment_request, you respond with the checkout URL. Claude can then say to the user: "Here's your checkout link: [URL]. Please scan the QR code with your banking app." And when you call await_settlement, Claude will pause and resume once confirmed.

Do's and Don'ts: Payment Best Practices

Do Don't
✓ Always store payment_id in your database ✗ Don't forget to log the txn_ref for reconciliation
✓ Set expires_at to 5–15 minutes ✗ Don't keep links alive indefinitely (fraud risk)
✓ Use a unique request_id per order ✗ Don't reuse request IDs across different orders
✓ Wait for settlement.confirmed == True before fulfilling ✗ Don't fulfill on QR generation alone
✓ Display the QR code and the checkout URL ✗ Don't rely only on link-clicking (QR is faster)
✓ Implement a timeout (your agent should move on if payment stalls) ✗ Don't block the agent indefinitely on awaiting settlement
✓ Log all payment attempts for debugging ✗ Don't silently ignore settlement timeouts

Advanced Tips: Scaling Your Payment Agent

1. Async Workflows for Multiple Payments

If your agent handles multiple concurrent orders, use asyncio to wait on several payments in parallel:

import asyncio

async def process_bulk_orders(orders):
    tasks = [
        await_settlement(order['payment_id'], timeout_seconds=300)
        for order in orders
    ]
    results = await asyncio.gather(*tasks)
    return results

2. Retry Logic with Exponential Backoff

If your agent retries a failed payment creation, use the same request_id to prevent double-charging:

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=2, max=10)
)
def create_payment_with_retry(merchant_account, amount_vnd, request_id):
    return create_payment_request(
        merchant_account=merchant_account,
        amount_vnd=amount_vnd,
        description="Retryable payment",
        request_id=request_id  # Same ID = AgentPay deduplicates
    )

3. Webhook Fallback for Offline Agents

If your agent can't stay alive for 5 minutes, register a webhook:

from agentpay_vn import register_webhook

register_webhook(
    payment_id=payment.payment_id,
    webhook_url="https://your-app.com/webhooks/agentpay",
    secret="your-webhook-signing-key"
)
# AgentPay will POST settlement confirmation to your webhook
# Your app can then notify the agent via your messaging queue

FAQ

Q: Does AgentPay hold my money? No. The VietQR code points directly to your merchant bank account. AgentPay is a payment tool, not a wallet. Funds transfer bank-to-bank; AgentPay only provides confirmation.

Q: How long does settlement take? VietQR transfers are instant (1–3 seconds in most cases). AgentPay's bank feed confirms within the same window. If you set timeout_seconds=300, your agent will typically confirm within 5 seconds.

Q: Can I use this outside Vietnam? VietQR is Vietnam-specific. AgentPay VN is optimized for Vietnam's banking ecosystem. International customers can't scan VietQR codes.

Q: What if the user closes the payment app mid-transaction? The payment request expires after your expires_at time (e.g., 15 minutes). If settlement isn't confirmed by then, await_settlement() times out, and your agent can retry with a new payment.

Key Takeaways

Get Started Now

You now have everything needed to add VietQR checkout to your LLM agent in under 10 minutes:

  1. Install the SDK: pip install agentpay-vn
  2. Read the full docs: https://agentpay.servicesai.vn/v1/docs
  3. Explore the code: https://github.com/phuocdu/agentpay-vn
  4. Try the walkthrough: Use the café ordering example above and adapt it to your use case.

Your AI agent is already smart enough to sell. Now it can collect payment without friction. Start building. 🚀

Get started →

← All posts