VietQR Payment Automation for AI Agents: Stripe Alternative

2026-08-07 · AgentPay VN

vietqrai-agentspayment-automationpython-sdkvietnam

The Problem: AI Agents Can't Accept Vietnamese Payments Today

You've built an AI agent that sells something valuable—Vietnamese language courses, consulting hours, or SaaS subscriptions. Your agent can chat, reason, and persuade users to buy. But the moment someone says "I want to pay," your agent hits a wall.

Stripe doesn't fully support VietQR bank transfers in Vietnam. International payment gateways charge 3–5% fees, eat into margins, and require USD bank accounts. Momo and ZaloPay APIs are fragmented and SDK-poor. Your agent sits there, unable to collect money directly into your Vietnamese merchant bank account.

This is the gap AgentPay VN fills. It's a lightweight, open-source Python SDK + MCP server that lets AI agents collect payments via VietQR—Vietnam's native QR code standard—with zero money-holding. The QR points straight at your bank. A webhook confirms settlement. Done.

In this tutorial, you'll learn to integrate VietQR payments into an AI agent in under 30 minutes.

Why VietQR Beats Traditional Solutions for AI Agents

VietQR is Vietnam's interbank QR code standard, adopted by all major banks (Vietcombank, Techcombank, MB, ACB, etc.). It's instant, free to the merchant, and trusted by 99% of Vietnamese smartphone users.

Why AgentPay VN is better than rolling your own:

  1. Agent-native design: Built specifically for AI workflows (Claude, GPT, local LLMs). The MCP server exposes payment tools to Claude as structured functions.
  2. No money-holding: Funds go directly to your bank account. No escrow, no platform risk.
  3. Bank-confirmed settlement: Payment webhooks verify the actual transfer completed (not just a pending order).
  4. MIT open-source: Inspect the code, self-host, no vendor lock-in.
  5. Vietnam-optimized: Handles VietQR quirks (bank codes, transfer limits, consumer behavior).

Compare to Stripe (5% fee, USD settlement), Momo (2% fee, delayed settlement), or building raw VietQR yourself (regulatory nightmare, 2–3 months of work).

Installation & Setup: 3 Commands

Step 1: Install the SDK

pip install agentpay-vn

Verify the install:

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

Step 2: Configure Your Merchant Account

You'll need: - Bank account in Vietnam (any major bank). - Bank code (e.g., 970401 for Techcombank, 970010 for Vietcombank). Full list here. - Account number and account holder name.

Set environment variables:

export AGENTPAY_BANK_CODE="970401"
export AGENTPAY_ACCOUNT_NUMBER="1234567890"
export AGENTPAY_ACCOUNT_NAME="Nguyen Van A"
export AGENTPAY_WEBHOOK_SECRET="your-secret-key"

Step 3: Run the MCP Server (for Claude Integration)

agentpay-mcp

The server runs on http://localhost:3000 by default. (Optional if using the SDK directly; required for Claude integration.)

Core Payment Flow: 3 Lines

AgentPay VN abstracts payment collection into three methods:

# 1. Create a payment request
request = create_payment_request(amount=500000, description="Online Course")

# 2. Send checkout URL to user
user.send_message(f"Pay here: {request.checkout_url}")

# 3. Wait for settlement confirmation
await_settlement(request_id=request.id, timeout_seconds=300)

Let's expand this into a real example.

Code Example: Building a Course Bot with AgentPay VN

Imagine a simple AI agent that sells a Python course. Here's the payment integration:

from agentpay_vn import (
    create_payment_request,
    await_settlement,
    PaymentStatus
)
import time

class CourseBot:
    def __init__(self):
        self.course_price = 500000  # 500,000 VND
        self.webhook_timeout = 300  # 5 minutes

    def sell_course(self, user_id: str, user_phone: str) -> dict:
        """
        Main payment flow: create request → send URL → await settlement.
        """
        # Step 1: Create payment request
        # amount: price in VND
        # description: shown to user in their banking app
        # reference: your internal order ID
        payment = create_payment_request(
            amount=self.course_price,
            description="Python Mastery Course - 12 weeks",
            reference=f"course_{user_id}_{int(time.time())}"
        )

        # Step 2: Compose checkout message
        checkout_msg = f"""
        📚 Course Ready to Buy!
        Price: {self.course_price:,} VND

        Scan this QR code to pay:
        {payment.qr_image_url}

        Or click: {payment.checkout_url}

        ⏰ Valid for 5 minutes.
        """

        # Send to user (via Telegram, email, web, etc.)
        print(checkout_msg)

        # Step 3: Wait for settlement webhook
        # The bank confirms the transfer → webhook fires → we resume
        try:
            result = await_settlement(
                request_id=payment.id,
                timeout_seconds=self.webhook_timeout
            )

            if result.status == PaymentStatus.SETTLED:
                return {
                    "success": True,
                    "message": "Payment confirmed! Course access granted.",
                    "settlement_amount": result.amount,
                    "transaction_id": result.bank_transaction_id
                }
            else:
                return {"success": False, "message": "Payment failed or timed out."}

        except TimeoutError:
            return {"success": False, "message": "Payment took too long."}

# Usage
bot = CourseBot()
result = bot.sell_course(user_id="user_123", user_phone="0912345678")
print(result)

Line-by-line breakdown:

Integrating AgentPay VN with Claude via MCP

If you want Claude (or another LLM) to call payment functions autonomously, use the MCP server.

MCP Configuration (Claude Desktop)

Add this to your Claude Desktop claude_desktop_config.json (typically ~/.claude_desktop_config.json on macOS/Linux):

{
  "mcpServers": {
    "agentpay-vn": {
      "command": "agentpay-mcp",
      "args": [],
      "env": {
        "AGENTPAY_BANK_CODE": "970401",
        "AGENTPAY_ACCOUNT_NUMBER": "1234567890",
        "AGENTPAY_ACCOUNT_NAME": "Nguyen Van A",
        "AGENTPAY_WEBHOOK_SECRET": "your-secret-key"
      }
    }
  }
}

Restart Claude Desktop. Claude will now see tools like: - create_payment_request(amount, description, reference) - await_settlement(request_id, timeout_seconds) - get_payment_status(request_id)

Claud can call these in natural conversation:

User: "I want to buy your course for 500,000 VND."

Claude: I'll create a payment request for you.

[Claude calls create_payment_request(amount=500000, description="Python Mastery Course")]

Claude: Here's your payment QR: [image]. Valid for 5 minutes.

[User scans and pays]

[Bank webhook fires, await_settlement() returns SETTLED]

Claude: ✅ Payment confirmed! Sending course access link...

Real-World Walkthrough: Café Order Bot

Let's build a concrete example: a café that sells coffee subscriptions via an AI bot.

Scenario: - Café owner wants to sell monthly coffee boxes (300,000 VND/month). - AI bot (Claude) takes orders on Facebook Messenger. - Bot collects payment, confirms order, sends tracking number.

Implementation:

from agentpay_vn import create_payment_request, await_settlement
import asyncio
from datetime import datetime

class CafeSubscriptionBot:
    def __init__(self):
        self.subscription_price = 300000
        self.orders = {}

    async def handle_order(self, customer_name: str, email: str):
        """
        Full order flow: payment → confirmation → fulfillment.
        """
        # Create payment
        payment = create_payment_request(
            amount=self.subscription_price,
            description=f"Monthly Coffee Box - {datetime.now().strftime('%B %Y')}",
            reference=f"cafe_sub_{customer_name}_{int(datetime.now().timestamp())}"
        )

        # Store order metadata
        self.orders[payment.id] = {
            "customer_name": customer_name,
            "email": email,
            "status": "pending_payment"
        }

        # Send checkout URL
        checkout_msg = f"""Hi {customer_name},

Thank you for subscribing! 🎉

Pay 300,000 VND to confirm your monthly box:
        {payment.checkout_url}

After payment, we'll ship within 24 hours.
        """
        print(checkout_msg)  # In production, send via SMS/email/Messenger

        # Wait for payment
        try:
            settlement = await_settlement(
                request_id=payment.id,
                timeout_seconds=600  # 10 minutes
            )

            if settlement.status.value == "settled":
                # Mark order as paid
                self.orders[payment.id]["status"] = "paid"
                self.orders[payment.id]["transaction_id"] = settlement.bank_transaction_id

                # Trigger fulfillment
                tracking_number = self.ship_order(customer_name, email)

                confirmation = f"""✅ Payment received!

Tracking: {tracking_number}
Ships today, arrives in 2-3 days.
                """
                print(confirmation)
                return {"success": True, "tracking": tracking_number}

        except TimeoutError:
            self.orders[payment.id]["status"] = "abandoned"
            print(f"❌ Payment expired for {customer_name}")
            return {"success": False, "error": "timeout"}

    def ship_order(self, customer_name: str, email: str) -> str:
        """Dummy fulfillment (in production: call shipping API)."""
        return f"VN{datetime.now().strftime('%Y%m%d%H%M%S')}"

# Run
async def main():
    bot = CafeSubscriptionBot()
    await bot.handle_order("Tran Quang", "tran@example.com")

if __name__ == "__main__":
    asyncio.run(main())

This bot: 1. Creates a payment request tied to the order. 2. Sends the checkout URL to the customer. 3. Blocks until the bank confirms payment. 4. Automatically ships the order.

No manual reconciliation. No payment verification headaches.

Do's and Don'ts

✅ Do ❌ Don't
Use await_settlement() to confirm payment before fulfilling Assume payment is done until await_settlement() returns SETTLED
Set a reasonable timeout (5–10 min for quick purchases, 1h for large orders) Timeout immediately; give users time to complete payment
Store reference ID in your database for reconciliation Forget to log transaction IDs; you'll have no audit trail
Test with a small amount (50,000 VND) first Deploy to production before testing end-to-end
Expire checkout URLs after 5–10 minutes Keep URLs valid indefinitely; users might pay old invoices
Webhook to automatically confirm orders Manually check bank account; you'll miss payments
Handle timeout exceptions gracefully Crash if payment takes > 5 seconds

Advanced Tips

1. Batch Payments (B2B Orders)

If you're selling to businesses, you might need to split a 10M VND order into multiple 3M VND payments (per-transaction limits):

def split_large_payment(total_amount: int, per_split_limit: int = 3000000):
    num_splits = (total_amount + per_split_limit - 1) // per_split_limit
    return [per_split_limit] * (num_splits - 1) + [total_amount % per_split_limit or per_split_limit]

# Example: 10M splits into [3M, 3M, 3M, 1M]
splits = split_large_payment(10000000)
for amount in splits:
    payment = create_payment_request(amount=amount, description=f"Part {splits.index(amount)+1}")

2. Webhook Verification

Always verify the webhook signature to prevent forged settlement notifications:

from agentpay_vn import verify_webhook_signature

def handle_webhook(headers: dict, body: str):
    signature = headers.get("X-AgentPay-Signature")
    secret = os.getenv("AGENTPAY_WEBHOOK_SECRET")

    if not verify_webhook_signature(body, signature, secret):
        raise ValueError("Invalid webhook signature")

    # Process settlement safely
    settlement_data = json.loads(body)
    print(f"Payment settled: {settlement_data['request_id']}")

3. Retry Logic for Flaky Networks

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 robust_await_settlement(request_id: str):
    return await_settlement(request_id=request_id, timeout_seconds=300)

# Automatically retries up to 3 times with backoff

4. Store QR Codes as Images

The qr_image_url is a base64 PNG. Save it for offline use:

import base64
from PIL import Image
from io import BytesIO

payment = create_payment_request(amount=500000, description="Course")
qr_base64 = payment.qr_image_url.split(",")[1]  # Strip data:image/png;base64, prefix
qr_png = base64.b64decode(qr_base64)

img = Image.open(BytesIO(qr_png))
img.save(f"payment_{payment.id}.png")

FAQ

Q: Does AgentPay VN hold my money? A: No. The QR code and checkout URL point directly to your bank account. AgentPay VN never touches the funds—it only provides the payment infrastructure. Settlement goes straight to your bank within seconds.

Q: What are transaction limits? A: Per-transaction limits vary by bank (typically 3–10M VND per transfer). Use the batch splitting logic above for large orders.

Q: How do I handle currency (should prices be in VND)? A: All prices are in VND (Vietnamese Dong). If you serve international customers, convert to VND before calling create_payment_request(). AgentPay VN doesn't handle multi-currency.

Q: Is my merchant account verified/KYC required? A: Your bank account must be real and verified (which it is if you use it normally). AgentPay VN doesn't require additional KYC—it just forwards payments to your existing bank account.

Q: Can I use AgentPay VN with Telegram/Discord bots? A: Yes. Use the SDK directly (no MCP needed). Call create_payment_request(), send the checkout_url to the user, then await_settlement() when they confirm payment.

Key Takeaways

Next Steps

  1. Install AgentPay VN: pip install agentpay-vn
  2. Read the full docs: https://agentpay.servicesai.vn/v1/docs
  3. Clone the GitHub repo for examples: https://github.com/phuocdu/agentpay-vn
  4. Test with a small order (50,000 VND) in your agent.
  5. Deploy to production once you've verified the full flow.

Your AI agent is now a payment-collecting machine. Happy selling! 🚀

Get started →

← All posts