VietQR Payment Automation for AI Agents: Stripe Alternative

2026-07-15 · AgentPay VN

vietqrpayment automationai agentspython sdkvietnam payments

Why Vietnamese AI Projects Fail at Payment Collection

You've just built an intelligent chatbot for your client—a language tutoring platform targeting Ho Chi Minh City professionals. The bot onboards students, tracks progress, and recommends premium lessons. One problem: when a student wants to enroll in a 4-week intensive course (₫2,000,000), your agent has no way to collect payment.

Stripe doesn't support VietQR in Vietnam without complex workarounds. PayPal's unpopular locally. International gateways add 3-5% fees. You're left manually processing bank transfers or building fragile custom integrations with Vietnamese banks—risky, slow, and unmaintainable.

This is where AgentPay VN changes the game. It's a lightweight, open-source Python SDK that lets your AI agents accept VietQR payments directly into the merchant's bank account—no middle-man, no escrow, no API keys juggling.


What Is AgentPay VN? The Core Concept

AgentPay VN is an MIT-licensed Python SDK + Model Context Protocol (MCP) server that enables AI agents to generate VietQR payment requests and await settlement confirmations. Here's the mental model:

  1. Agent initiates → Creates a unique payment request tied to a transaction ID
  2. Customer scans → VietQR code points directly to your merchant bank account
  3. Bank confirms → Webhook or polling tells your agent payment arrived

Crucially: AgentPay never touches your money. You're not running a payment processor. The funds go straight from customer to your bank account, and AgentPay just orchestrates the handshake.

Why this matters: - ✅ Compliant with Vietnam's VietQR standard - ✅ Instant settlement (no 2-3 day holds) - ✅ Works with any Vietnamese bank - ✅ Fits seamlessly into AI agent workflows (Claude, GPT, custom) - ✅ Zero monthly fees (open-source)


Installation & Quick Setup

Step 1: Install the SDK

pip install agentpay-vn

This adds the core Python module to your environment. Verify:

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

Step 2: Set Environment Variables

Create a .env file in your project root:

BANK_ACCOUNT_NUMBER=0123456789
BANK_CODE=970400  # MB = 970400, Vietcombank = 970436, etc.
MERCHANT_NAME=My Shop
OTP_SECRET=your_otp_secret_if_using_2fa

Bank codes for major Vietnamese banks: - MB (MBBank): 970400 - Vietcombank: 970436 - Techcombank: 970407 - ACB: 970005

Full bank code list

Step 3: Initialize the Client

from agentpay_vn import AgentPayClient
import os

client = AgentPayClient(
    bank_account=os.getenv("BANK_ACCOUNT_NUMBER"),
    bank_code=os.getenv("BANK_CODE"),
    merchant_name=os.getenv("MERCHANT_NAME")
)

You're ready to accept payments.


The 3-Step Payment Flow Explained

Step 1: Create a Payment Request

When a customer decides to purchase, your agent creates a payment request. This generates a unique VietQR code tied to the transaction.

from agentpay_vn import AgentPayClient
import os
from datetime import datetime, timedelta

client = AgentPayClient(
    bank_account="0123456789",
    bank_code="970400",
    merchant_name="TutorBot Academy"
)

# Customer enrolls in course
transaction_id = f"course_001_{int(datetime.now().timestamp())}"
amount_vnd = 2_000_000  # ₫2 million
user_email = "student@example.com"

# Create the payment request
payment_req = client.create_payment_request(
    transaction_id=transaction_id,
    amount=amount_vnd,
    description="4-Week Intensive English Course",
    customer_email=user_email,
    expires_in_minutes=30  # QR expires in 30 mins
)

print(f"VietQR Code URL: {payment_req.qr_url}")
print(f"Checkout Link: {payment_req.checkout_url}")
print(f"Transaction ID: {payment_req.transaction_id}")

Line-by-line breakdown: - transaction_id: Unique identifier for this sale. Use timestamps or UUIDs to avoid collisions. - amount: Always in VND (Vietnamese Dong). - description: Shown to customer on their bank statement. - expires_in_minutes: Prevents stale QR codes. 30 minutes is standard. - qr_url: Image URL of the VietQR code (PNG/SVG). - checkout_url: Full-page checkout link (optional backup).

Step 2: Send Checkout URL to Customer

Your agent now delivers the QR code and payment link. In a web context:

# In your FastAPI/Flask endpoint
from fastapi import FastAPI
from fastapi.responses import JSONResponse

app = FastAPI()

@app.post("/enroll")
async def enroll_student(user_id: str, course_id: str):
    # Create payment
    payment_req = client.create_payment_request(
        transaction_id=f"{user_id}_{course_id}",
        amount=2_000_000,
        description=f"Course {course_id}"
    )

    # Return QR for frontend to display
    return JSONResponse({
        "status": "awaiting_payment",
        "qr_url": payment_req.qr_url,
        "checkout_url": payment_req.checkout_url,
        "amount_vnd": 2_000_000,
        "expires_in_seconds": 30 * 60
    })

Or for a ChatBot context, your Claude-powered agent might respond:

"Perfect! I've generated your enrollment link. 
Scan this QR code with your banking app:
[QR IMAGE]

Or tap here for web checkout: [LINK]
Payment expires in 30 minutes."

Step 3: Await Settlement

Once the customer scans and pays, your agent must confirm receipt before granting access. AgentPay supports two modes:

Mode A: Polling (Simpler)

import time
from agentpay_vn.exceptions import PaymentNotFoundError, PaymentTimeoutError

transaction_id = "course_001_1704067200"
max_wait_seconds = 60  # Poll for up to 1 minute

try:
    settlement = client.await_settlement(
        transaction_id=transaction_id,
        timeout_seconds=max_wait_seconds
    )

    print(f"✅ Payment settled!")
    print(f"Amount received: ₫{settlement.amount_received}")
    print(f"Bank reference: {settlement.bank_reference_id}")
    print(f"Settled at: {settlement.settlement_timestamp}")

    # Safely grant course access
    db.update_student(user_id, status="enrolled", course_id=course_id)

except PaymentTimeoutError:
    print("❌ Payment not received within 60 seconds")
    # Don't grant access, let customer retry

except PaymentNotFoundError:
    print("❌ Transaction not found (invalid ID?)")

Mode B: Webhook (Production)

For higher-traffic apps, use bank webhooks instead of polling:

from fastapi import FastAPI, Request
import hmac
import hashlib

app = FastAPI()

@app.post("/webhooks/agentpay")
async def handle_payment_webhook(request: Request):
    """
    Bank sends settlement confirmation here.
    Verify HMAC signature to prevent spoofing.
    """
    payload = await request.json()

    # Verify signature
    signature = request.headers.get("X-AgentPay-Signature")
    expected_sig = hmac.new(
        key=os.getenv("WEBHOOK_SECRET").encode(),
        msg=str(payload).encode(),
        digestmod=hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(signature, expected_sig):
        return {"status": "unauthorized"}

    # Process settlement
    transaction_id = payload["transaction_id"]
    amount = payload["amount"]
    bank_ref = payload["bank_reference_id"]

    # Update database immediately
    db.update_student(
        user_id=extract_user_from_txn(transaction_id),
        status="enrolled",
        bank_reference=bank_ref
    )

    # Send confirmation email
    send_email(user_email, "Welcome to your course!")

    return {"status": "processed"}

Real-World Example: Online Café Product Shop

Let's trace a complete flow for a coffee roastery selling beans online.

Scenario: Linh's Coffee Roastery has built an AI agent that recommends blends based on customer taste profiles, then automates orders and payment.

from agentpay_vn import AgentPayClient
from datetime import datetime
import json

client = AgentPayClient(
    bank_account="1234567890",
    bank_code="970400",  # MBBank
    merchant_name="Linh's Coffee Roastery"
)

class CoffeeShopAgent:
    def recommend_and_checkout(self, customer_id, profile):
        """
        Step 1: AI recommends blend based on profile
        """
        if profile["roast_preference"] == "dark":
            recommendation = {
                "blend": "Robusta Dalat Dark Roast",
                "price_vnd": 450_000,  # ₫450k per 1kg bag
                "quantity": 2
            }

        total = recommendation["price_vnd"] * recommendation["quantity"]

        # Step 2: Create payment request
        txn_id = f"cafe_{customer_id}_{int(datetime.now().timestamp())}"

        payment = client.create_payment_request(
            transaction_id=txn_id,
            amount=total,
            description=f"{recommendation['quantity']}kg {recommendation['blend']}",
            customer_email=customer_id,
            expires_in_minutes=15
        )

        # Step 3: Return checkout to agent/UI
        return {
            "recommendation": recommendation,
            "total_vnd": total,
            "qr_code_url": payment.qr_url,
            "payment_link": payment.checkout_url,
            "txn_id": txn_id
        }

    def confirm_and_ship(self, txn_id, customer_id):
        """
        Step 4: Await payment, then trigger shipment
        """
        try:
            settlement = client.await_settlement(
                transaction_id=txn_id,
                timeout_seconds=120  # 2 min timeout
            )

            # Payment confirmed → create shipment
            print(f"🎉 Order {txn_id} paid!")
            print(f"Amount: ₫{settlement.amount_received}")
            print(f"Bank ref: {settlement.bank_reference_id}")

            # Trigger warehouse system
            warehouse_api.create_shipment(txn_id, customer_id)

            # Send SMS to customer
            send_sms(customer_id, f"Your coffee order is being roasted! Shipping in 2 days.")

            return {"status": "processing_shipment", "txn_id": txn_id}

        except Exception as e:
            print(f"❌ Payment failed: {e}")
            return {"status": "payment_failed", "retry_url": payment.checkout_url}

# Usage
agent = CoffeeShopAgent()

# Customer conversation
profile = {"roast_preference": "dark", "origin": "vietnam", "grind": "whole_bean"}
checkout_info = agent.recommend_and_checkout("customer_123", profile)

print(f"Agent says: 'I recommend {checkout_info['recommendation']['blend']}'")
print(f"Show QR: {checkout_info['qr_code_url']}")
print(f"Total: ₫{checkout_info['total_vnd']:,}")

# After customer pays...
result = agent.confirm_and_ship(checkout_info['txn_id'], "customer_123")

This entire flow—recommendation, payment creation, awaiting settlement, and shipping trigger—happens autonomously.


Using AgentPay with Claude / MCP Server

To give Claude (or other AI models) direct payment capabilities, run the MCP server:

agentpay-mcp --bank-account "1234567890" --bank-code "970400" --merchant-name "My Business"

Then configure your Claude client (e.g., via Anthropic API):

{
  "mcpServers": {
    "agentpay": {
      "command": "agentpay-mcp",
      "args": [
        "--bank-account", "1234567890",
        "--bank-code", "970400",
        "--merchant-name", "My Business"
      ],
      "env": {
        "BANK_ACCOUNT_NUMBER": "1234567890",
        "BANK_CODE": "970400",
        "MERCHANT_NAME": "My Business"
      }
    }
  }
}

Now Claude can call tools like:

Claude: "The customer wants to buy a ₫5M subscription. Let me create a payment request."
[Claude calls: create_payment_request(amount=5000000, description="Monthly subscription")]
Claude: "Here's your QR code: [image]. Payment expires in 30 minutes."

AgentPay vs. Stripe: A Comparison

Feature AgentPay VN Stripe
VietQR Support ✅ Native ❌ Workarounds only
Monthly Fees ✅ None (open-source) ❌ $20-300+
Settlement Speed ✅ Instant to bank ❌ 2-3 days
Transaction Fee ✅ None ❌ 2.9% + ₫3k
Integration Effort ✅ 10 lines of code ❌ Complex OAuth
Works in Vietnam ✅ Yes ⚠️ Limited
AI Agent Native ✅ MCP built-in ❌ Requires custom wrappers
PCI Compliance ✅ Bank-to-bank (no card data) ✅ But adds overhead

TL;DR: For Vietnamese merchants accepting local payments from local customers, AgentPay wins on cost, speed, and simplicity.


Do's and Don'ts

✅ DO:

❌ DON'T:


Advanced Tips

Idempotency

If your network fails mid-request, retry safely:

idempotency_key = "order_12345_attempt_1"
payment = client.create_payment_request(
    transaction_id="order_12345",
    amount=1_000_000,
    idempotency_key=idempotency_key  # Same key = same result
)

Handling Failed Payments

def handle_failed_payment(txn_id, customer_email):
    # Generate fresh QR with extended deadline
    payment = client.create_payment_request(
        transaction_id=f"{txn_id}_retry_1",
        amount=amount,
        expires_in_minutes=60,  # Give more time
        description=f"Retry for {txn_id}"
    )

    # Email customer
    send_email(customer_email, f"""Your payment didn't go through.
    New QR: [image]
    Or pay here: {payment.checkout_url}
    """)

Multi-Currency (VND only, but convert externally)

def create_payment_usd_to_vnd(amount_usd, exchange_rate=24500):
    amount_vnd = int(amount_usd * exchange_rate)
    return client.create_payment_request(
        transaction_id=...,
        amount=amount_vnd,
        description=f"${amount_usd} USD = ₫{amount_vnd:,} VND"
    )

FAQ

Q: Does AgentPay hold my money?
A: No. AgentPay is a transaction orchestrator only. Funds go directly from customer's bank → your bank account. AgentPay just confirms the settlement.

Q: What if a customer disputes the payment?
A: Disputes are handled by your bank directly. AgentPay provides the bank reference ID for easy reconciliation.

Q: Can I use AgentPay without internet?
A: The Python SDK is offline-first for QR generation, but settlement confirmation requires bank API connectivity. Use polling during brief outages.

Q: How do I test locally?
A: AgentPay provides a sandbox mode (coming in v1.1). Currently, use test amounts (₫10,000) with a test bank account.


Key Takeaways


Get Started Now

Install AgentPay VN today:

pip install agentpay-vn

Read the full documentation:
https://agentpay.servicesai.vn/v1/docs

Explore the source code:
https://github.com/phuocdu/agentpay-vn

Questions? Open an issue on GitHub or reach out to the maintainers.

Your AI agents can now accept VietQR payments. Build something amazing! 🚀

Get started →

← All posts