Add VietQR Checkout to Your LLM Agent in 10 Minutes

2026-06-27 · AgentPay VN

ai-agentsvietqrpaymentsmcppython

The Pain Point Nobody Talks About

You've built an impressive AI agent—it chats naturally, solves problems, drafts contracts, even tutors your customers. But the moment someone says "I want to buy," your agent hits a wall.

You either: - Kick them to a separate payment page (breaking immersion, losing ~30% of conversions) - Build custom Stripe/PayPal integrations (weeks of work, PCI compliance headaches) - Hard-code a single bank account link (no tracking, no flexibility)

For Vietnamese businesses, the pain is sharper. VietQR is ubiquitous—every customer has a banking app—but no open-source toolkit lets agents handle it natively.

That's where AgentPay VN comes in. In this tutorial, you'll integrate real payment collection into your LLM agent in under 10 minutes, with zero merchant account gatekeeping.

What AgentPay VN Actually Does

AgentPay VN is an MIT-licensed Python SDK + MCP server that lets AI agents generate VietQR payment codes and confirm settlements. Here's the critical part: it never touches your money. The QR code points directly to your bank account. When payment arrives, a bank feed confirms it—your agent knows immediately.

This means: - Zero fraud risk (you control the receiving account) - No payment gateway fees - Instant settlement confirmation - Works with any Vietnamese bank - Runs locally or in production

Install It (30 Seconds)

pip install agentpay-vn

That's it. No API keys, no credential hunting. The library generates VietQR codes using your account details (which you provide at runtime).

The Three-Line Payment Flow

Before we build, understand the rhythm:

  1. create_payment_request — Agent generates a unique request with amount, description, expiry
  2. send checkout_url — Agent presents the QR code/URL to the user
  3. await_settlement — Agent waits for bank confirmation, then unlocks the good/service

Simple. No webhooks. No background jobs (though you can add them). Just async/await.

Step 1: Create Your First Payment Request

Here's a real-world example: a course-selling bot.

import asyncio
from agentpay_vn import PaymentClient, PaymentRequest

# Initialize with your bank details
client = PaymentClient(
    bank_account="0123456789",
    bank_code="970436",  # Vietcombank; get yours from agentpay docs
    account_name="Nguyen Van A",
    api_base="https://agentpay.servicesai.vn/v1"  # default
)

# Create a payment request for a course
payment_req = PaymentRequest(
    amount=299_000,  # 299k VND
    description="Python Async Masterclass",
    order_id="course_001_user_42",
    expiry_seconds=3600  # 1 hour window
)

# Generate the request
result = await client.create_payment_request(payment_req)
print(f"Checkout URL: {result.checkout_url}")
print(f"Request ID: {result.request_id}")

Line-by-line breakdown: - Lines 2–3: Import the core SDK - Lines 6–11: Initialize the client with your bank info (static, reused across all requests) - Lines 14–19: Define a payment request with amount, human-readable description, a unique order ID (tie it to your database), and a 1-hour expiry - Line 22: Async call to generate the QR. Returns a checkout URL your agent can share.

Step 2: Send the Checkout URL to Your Agent

Integrate into Claude, ChatGPT, or a custom agent framework:

import anthropic

defaults = {
    "bank_account": "0123456789",
    "bank_code": "970436",
    "account_name": "Your Name"
}

async def request_payment(amount: int, description: str, user_id: str) -> dict:
    """Tool callable by the agent."""
    client = PaymentClient(**defaults)
    req = PaymentRequest(
        amount=amount,
        description=description,
        order_id=f"user_{user_id}_{int(time.time())}",
        expiry_seconds=1800
    )
    result = await client.create_payment_request(req)
    return {
        "status": "pending",
        "checkout_url": result.checkout_url,
        "request_id": result.request_id,
        "expires_at": datetime.now() + timedelta(seconds=1800)
    }

# Claude tool definition (JSON schema)
payment_tool = {
    "name": "request_payment",
    "description": "Generate a VietQR payment request. User scans the code in their banking app.",
    "input_schema": {
        "type": "object",
        "properties": {
            "amount": {"type": "integer", "description": "VND amount"},
            "description": {"type": "string", "description": "What they're paying for"},
            "user_id": {"type": "string", "description": "Your internal user ID"}
        },
        "required": ["amount", "description", "user_id"]
    }
}

Key points: - Store bank credentials once; reuse per request - Generate unique order_id each time (tie to user, timestamp, or transaction) - Return the checkout_url for the agent to present to the user - Set realistic expiries (30 min–2 hours typical)

Step 3: Wait for Settlement Confirmation

After the user scans and pays, your agent waits:

async def await_payment(request_id: str, user_id: str, max_wait_seconds: int = 300) -> dict:
    """Poll for settlement (or hook into a bank feed for real-time)."""
    client = PaymentClient(**defaults)

    start = time.time()
    while (time.time() - start) < max_wait_seconds:
        status = await client.get_payment_status(request_id)

        if status.state == "settled":
            # Money arrived. Unlock the course, send the API key, whatever.
            print(f"✓ Payment confirmed for {user_id}")
            return {
                "success": True,
                "amount": status.amount,
                "confirmed_at": status.settled_at
            }

        # Not yet. Wait a bit and retry.
        await asyncio.sleep(5)

    # Timeout. Let the user retry or contact support.
    return {"success": False, "reason": "timeout", "hint": "Try again or email support"}

Flow: - Loop with 5-second intervals (or tune per your UX) - Call get_payment_status() each time - On settled, unlock the good/service immediately - On timeout, gracefully tell the user (don't ghost them)

Using the MCP Server for Seamless Claude Integration

If you're using Claude in a tool-use loop and want zero custom glue code, run the MCP server:

pip install agentpay-vn[mcp]
agentpay-mcp --bank-account 0123456789 --bank-code 970436 --account-name "Your Name"

Then configure Claude (in Claude Desktop or your framework):

{
  "mcpServers": {
    "agentpay": {
      "command": "agentpay-mcp",
      "args": [
        "--bank-account", "0123456789",
        "--bank-code", "970436",
        "--account-name", "Your Name"
      ]
    }
  }
}

Claude now has native tools: - create_vietqr_request(amount, description, order_id, expiry_seconds) - check_payment_status(request_id) - list_recent_payments(limit)

No Python wrapper code needed. Claude orchestrates payments autonomously.

Real-World Walkthrough: A Café Loyalty Bot

Imagine a Vietnamese coffee shop selling digital gift cards via a Telegram bot (powered by Claude):

User: "I want a 500k gift card for my friend."

Bot: "Perfect! Let me create a payment request..." [Generates QR]

Bot: "Scan this with your banking app. I'll wait."

User: [Scans, pays 500k from their Vietcombank account to the café's account]

Bot: [Polls every 5 seconds, detects settlement in ~10 seconds]

Bot: "✓ Payment received! Your gift card code is CAFE_GIFTCARD_XYZ123. Share it with your friend, or use it yourself."

Backend: The bot logs the transaction, credits the user's account, and the café's bank balance updates in real-time (no payment gateway waiting period).

Total merchant fees: $0. Settlement time: immediate.

Common Patterns & Best Practices

Do Don't
Store order_id in your database for reconciliation Reuse order IDs or generate them randomly
Set expiries per context (course = 2h, snack = 30m) Use a one-size-fits-all expiry
Log all request_ids for refund/support audits Discard IDs after settlement
Validate amount & description server-side Trust user input directly
Retry status checks with exponential backoff Poll aggressively (100+ req/sec)
Use a production bank account in production Test with a personal account

Advanced Tips

1. Batch Payments If your agent needs to split a purchase (e.g., course + certification + consultation):

payments = [
    PaymentRequest(amount=100_000, description="Course", order_id="..._1"),
    PaymentRequest(amount=50_000, description="Cert", order_id="..._2"),
    PaymentRequest(amount=30_000, description="1-on-1", order_id="..._3"),
]
results = await asyncio.gather(*[client.create_payment_request(p) for p in payments])

2. Bank Feed Integration For production, don't poll. Connect to your bank's API (ACB, Vietcombank, MB all support this) and listen for incoming transfers. AgentPay parses the transfer description—include your request_id in the memo.

# Pseudo-code: listen to bank webhook
@app.post("/bank-webhook")
async def handle_bank_notification(payload: dict):
    request_id = extract_from_memo(payload["memo"])
    amount = payload["amount"]
    await client.mark_settled(request_id, amount)  # Update internal state
    # Agent now sees it immediately on next status check

3. Multi-Agent Orchestration Run multiple agents (support, sales, logistics) sharing the same PaymentClient:

shared_client = PaymentClient(**defaults)  # Singleton or dependency injection
# All agents reuse it; requests pile up in your bank account

Pricing & Hosting

AgentPay VN is open-source (MIT license). Host it yourself or use the free SaaS docs server. No limits on request volume. Your only cost: the VietQR transfer (standard bank fees, usually ≈0%).

FAQ

Q: What if someone scans the QR but doesn't complete payment? A: The request expires (you set the window). Status checks return "expired." Your agent can offer to regenerate a fresh QR or suggest an alternative payment method.

Q: Can I refund a payment? A: AgentPay doesn't hold funds, so it doesn't refund. You refund by transferring money back from your bank account (a separate transaction). Log the refund in your database and mark the request_id as refunded.

Q: Does this work with all Vietnamese banks? A: Yes. Provide any bank account + bank code (found in the docs). The QR standard is identical across all banks.

Q: What's the difference between SDK and MCP server? A: SDK = you write Python code + call the library. MCP = Claude calls the tools automatically (less boilerplate). Both give the same functionality.

Key Takeaways

Next Steps

  1. Install: pip install agentpay-vn
  2. Grab your bank code: Check the official docs
  3. Read the full examples: GitHub repo
  4. Deploy: Use the MCP server config above, or wrap the SDK in your agent framework
  5. Share: If you build something cool (loyalty bot, course platform, tipping jar), open an issue on GitHub—we'd love to feature it

Your AI agent is now a payment machine. Go collect those VietQR transactions.

Get started →

← All posts