AI Agent VietQR Payments in Python: AgentPay VN

2026-09-05 · AgentPay VN

pythonai-agentspaymentsvietqrmcp-server

The Pain: Your AI Agent Needs Money, But Can't Hold It

Imagine you've built a brilliant AI chatbot that tutors Vietnamese students in math. It works beautifully—students love it, engagement is high, and you're ready to charge $5 per session. But here's the problem: your bot has no way to collect payment that feels native to Vietnam, doesn't require you to integrate a heavy payment processor, and doesn't involve your agent holding customer funds (a legal and security nightmare).

You search for solutions. Stripe requires bank account verification that takes weeks. PayPal charges international fees that erode your margins. Building a custom payment system means wrestling with bank APIs, PCI compliance, and settlement schedules.

Then there's VietQR—Vietnam's beloved interbank QR code standard, already built into every major Vietnamese bank's mobile app. It's instant, it's trusted, and customers scan it daily. The missing piece? A way for your AI agent to generate and manage VietQR payment requests without holding money or building payment infrastructure from scratch.

That's exactly what AgentPay VN solves.

What Is AgentPay VN? The 30-Second Version

AgentPay VN is an open-source Python SDK (MIT license) paired with an MCP server that lets your AI agents create VietQR payment requests in three lines of code. Here's what makes it radical:

Installation is one line: pip install agentpay-vn. The MCP server runs as agentpay-mcp. The workflow is: create request → send checkout URL → await settlement.

The Three-Line Payment Flow

Let's walk through the actual mechanics before we build anything.

The Conceptual Flow

1. Your agent calls → create_payment_request(amount, description, timeout)
   ↓
2. AgentPay returns a checkout URL with embedded VietQR
   ↓
3. Customer scans QR, pays from their bank app
   ↓
4. Bank feeds confirm settlement → your agent unlocks the service

No intermediate wallet. No escrow. No agency relationship. The QR code is cryptographically tied to your merchant bank account, and settlement is a bank-to-bank transfer you can verify in real time.

Setting Up Your First Payment Request in Python

Step 1: Install the SDK

pip install agentpay-vn

That's it. No additional dependencies, no configuration files yet.

Step 2: Create Your First Payment Request

Here's a real, working example:

from agentpay_vn import AgentPayClient
from datetime import datetime, timedelta

# Initialize the client with your merchant credentials
# (You'll get these from AgentPay VN dashboard after bank verification)
client = AgentPayClient(
    merchant_id="YOUR_MERCHANT_ID",
    merchant_secret="YOUR_MERCHANT_SECRET"
)

# Create a payment request for a tutoring session
payment_request = client.create_payment_request(
    amount=50000,  # VND (roughly $2 USD)
    description="Math tutoring session - Algebra Level 2",
    customer_id="student_12345",
    order_id="session_20250115_001",
    expires_in=300  # 5 minutes to pay
)

# Payment request now contains:
# - checkout_url: Send this to the student
# - qr_code: The actual VietQR string (for display)
# - request_id: Track this payment internally
# - expires_at: When the QR becomes invalid

print(f"Checkout URL: {payment_request['checkout_url']}")
print(f"Request ID: {payment_request['request_id']}")

Line-by-line breakdown:

Step 3: Wait for Settlement

Now your agent needs to know when the customer has paid. This is where bank feeds come in:

import asyncio
from agentpay_vn import AgentPayClient

client = AgentPayClient(
    merchant_id="YOUR_MERCHANT_ID",
    merchant_secret="YOUR_MERCHANT_SECRET"
)

async def wait_for_payment(request_id, timeout_seconds=300):
    """
    Poll the AgentPay settlement service until payment confirms.
    In production, you'd also listen to webhooks for real-time updates.
    """
    start = asyncio.get_event_loop().time()

    while (asyncio.get_event_loop().time() - start) < timeout_seconds:
        # Check settlement status via bank feed
        settlement = await client.await_settlement(request_id)

        if settlement['status'] == 'confirmed':
            # Payment landed in your bank account
            print(f"✓ Payment confirmed: {settlement['amount']} VND")
            print(f"  Settlement ID: {settlement['settlement_id']}")
            return settlement

        elif settlement['status'] == 'failed':
            # QR expired or transaction reversed
            print("✗ Payment failed or expired.")
            raise Exception("Payment not completed")

        # Wait 2 seconds before checking again
        await asyncio.sleep(2)

    raise TimeoutError("Payment confirmation timeout")

# Usage in your agent logic
async def handle_tutoring_session(student_id, topic):
    # Create payment
    payment = client.create_payment_request(
        amount=50000,
        description=f"Tutoring: {topic}",
        customer_id=student_id
    )

    # Send checkout URL to student (via chat, email, SMS, etc.)
    await send_to_student(payment['checkout_url'])

    # Wait for settlement
    settlement = await wait_for_payment(payment['request_id'])

    # Unlock tutoring content
    await unlock_course_materials(student_id, topic)
    return settlement

# Run it
asyncio.run(handle_tutoring_session("student_12345", "Calculus Basics"))

What's happening:

In production, replace polling with webhooks for true real-time updates (details in AgentPay VN docs).

Integrating with Claude via MCP Server

If you're building an agent that runs on Claude (or another LLM via MCP), you don't call the Python SDK directly—you configure the MCP server and Claude handles the function calls.

MCP Server Configuration

Add this to your Claude configuration file (e.g., claude_desktop_config.json):

{
  "mcpServers": {
    "agentpay": {
      "command": "agentpay-mcp",
      "args": [
        "--merchant-id", "YOUR_MERCHANT_ID",
        "--merchant-secret", "YOUR_MERCHANT_SECRET",
        "--bank-name", "vietcombank"  // or "techcombank", "acb", etc.
      ],
      "env": {
        "AGENTPAY_WEBHOOK_URL": "https://yourdomain.com/webhooks/agentpay"
      }
    }
  }
}

Now Claude automatically has access to these tools: - create_payment_request – Generate VietQR - await_settlement – Check payment status - get_settlement_history – Retrieve past transactions

When Claude detects that a user should pay, it calls these tools directly without you writing integration code.

Real-World Example: An AI Tutoring Bot

Let's build a complete scenario: a Vietnamese language tutoring bot that charges per lesson.

The Scenario

You're running a platform where Claude tutors students in Vietnamese grammar. Each lesson costs 100,000 VND (~$4 USD). When a student asks to start a lesson:

  1. Claude asks for payment.
  2. Claude generates a VietQR checkout.
  3. Student scans and pays from their bank app.
  4. Claude confirms settlement and delivers the lesson.

The Code

from agentpay_vn import AgentPayClient
import asyncio

client = AgentPayClient(
    merchant_id="tutor_bot_001",
    merchant_secret="your_secret_key"
)

class TutoringBot:
    def __init__(self):
        self.client = client
        self.lessons = {}

    async def start_lesson(self, student_name, student_id, grammar_topic):
        """
        Student initiates a tutoring session. Payment required upfront.
        """
        lesson_id = f"lesson_{student_id}_{int(asyncio.get_event_loop().time())}"

        # Create payment request
        payment = self.client.create_payment_request(
            amount=100000,
            description=f"Vietnamese Grammar: {grammar_topic}",
            customer_id=student_id,
            order_id=lesson_id,
            expires_in=600  # 10 minutes to pay
        )

        # Store lesson metadata
        self.lessons[lesson_id] = {
            "student_name": student_name,
            "topic": grammar_topic,
            "request_id": payment['request_id'],
            "status": "awaiting_payment"
        }

        # Respond to student with checkout link
        checkout_message = f"""
        Hello {student_name}! 👋

        To start your lesson on {grammar_topic}, please pay 100,000 VND.

        🔗 Click to pay: {payment['checkout_url']}
        ⏱️  Payment link expires in 10 minutes.

        I'll confirm payment and start your lesson immediately!
        """

        print(checkout_message)

        # Wait for settlement (non-blocking)
        try:
            settlement = await self.client.await_settlement(
                payment['request_id'],
                timeout=600
            )

            self.lessons[lesson_id]["status"] = "paid"
            self.lessons[lesson_id]["settlement_id"] = settlement['settlement_id']

            # Lesson content delivered
            await self.deliver_lesson(student_name, grammar_topic)

        except Exception as e:
            self.lessons[lesson_id]["status"] = "failed"
            print(f"Payment failed: {e}")

    async def deliver_lesson(self, student_name, topic):
        """
        Content delivery after payment confirmed.
        """
        lesson_content = f"""
        ✅ Payment confirmed! Let's start your lesson.

        📚 Topic: {topic}

        **Lesson 1: Basic Tones**
        Vietnamese has 6 tones. Here's how to practice:
        - Level tone: "ma" (ghost)
        - Rising tone: "má" (mother)
        - Question tone: "mả" (tomb)
        ... [full lesson content]

        Let's practice! Try saying these words after me...
        """
        print(lesson_content)

# Usage
bot = TutoringBot()
await bot.start_lesson("Hung", "student_789", "Vietnamese Tones")

What Happens Behind the Scenes

  1. Payment creation: create_payment_request() generates a unique VietQR QR code pointing to your bank account.
  2. Student action: Hung opens the checkout link on their phone, scans the QR in their bank app (e.g., Vietcombank, Techcombank, ACB), and pays 100,000 VND.
  3. Bank settlement: The bank confirms the transfer to your merchant account via ACH/real-time transfer.
  4. Settlement polling: await_settlement() detects the bank feed confirmation and returns status='confirmed'.
  5. Lesson unlock: Your bot delivers lesson content immediately.

No escrow, no delays, no middleman holding funds.

Do's and Don'ts: Common Pitfalls

Do Don't
Use await_settlement() with a timeout to prevent infinite waiting Don't poll settlement more than once per 2 seconds (wastes API calls)
Store the request_id in your database for reconciliation Don't forget to validate merchant_id and merchant_secret in production
Send checkout URL via multiple channels (SMS, email, chat) for better UX Don't hardcode credentials; use environment variables
Listen to webhooks for real-time settlement updates Don't assume QR codes are valid indefinitely; always set expires_in
Test with your actual bank account in sandbox mode first Don't charge customers before status='confirmed'
Keep your merchant_secret private; rotate it every 90 days Don't display settlement IDs to customers (for security)

Advanced Tips for Production

1. Webhook Integration for Real-Time Updates

Instead of polling, register a webhook to get instant payment notifications:

from agentpay_vn import AgentPayClient
from flask import Flask, request

app = Flask(__name__)
client = AgentPayClient(merchant_id="...", merchant_secret="...")

@app.route('/webhooks/agentpay', methods=['POST'])
def handle_settlement_webhook():
    """
    AgentPay sends a POST here when payment confirms.
    """
    payload = request.json

    # Verify webhook signature (prevents spoofing)
    if not client.verify_webhook_signature(payload, request.headers.get('X-AgentPay-Signature')):
        return {'error': 'Invalid signature'}, 403

    request_id = payload['request_id']
    status = payload['status']  # 'confirmed' or 'failed'
    amount = payload['amount']

    if status == 'confirmed':
        # Unlock immediately (no polling needed)
        unlock_lesson(request_id, amount)

    return {'ok': True}, 200

2. Batch Reconciliation

Daily reconciliation with your bank to catch any discrepancies:

from agentpay_vn import AgentPayClient
from datetime import datetime, timedelta

client = AgentPayClient(merchant_id="...", merchant_secret="...")

async def reconcile_daily():
    """
    Check settlements from past 24 hours against your bank statement.
    """
    yesterday = (datetime.now() - timedelta(days=1)).isoformat()

    settlements = await client.get_settlement_history(
        start_date=yesterday,
        status='confirmed'
    )

    total_received = sum(s['amount'] for s in settlements)
    print(f"Verified {len(settlements)} payments, total: {total_received} VND")

    # Compare with your bank statement
    # Alert if there's a mismatch

3. Retry Logic for Transient Failures

import asyncio
from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=2, max=10))
async def create_payment_with_retry(amount, description, customer_id):
    """
    Retry payment creation if the API is temporarily down.
    """
    return client.create_payment_request(
        amount=amount,
        description=description,
        customer_id=customer_id,
        expires_in=300
    )

Frequently Asked Questions

Q1: Does AgentPay hold my money?

A: No, never. AgentPay is a payment orchestration layer—the VietQR points directly to your merchant bank account. Settlement happens bank-to-bank. AgentPay's role is to generate the QR, confirm the transfer via bank feeds, and notify your agent. You own 100% of the funds immediately.

Q2: Which Vietnamese banks are supported?

A: AgentPay supports all major Vietnamese banks that participate in the VietQR interbank network, including Vietcombank, Techcombank, ACB, MB Bank, Agribank, Sacombank, and 25+ others. Customers can pay from any Vietnamese bank app.

Q3: How long does settlement take?

A: VietQR transfers are typically settled in real-time (within 30 seconds to 2 minutes). AgentPay confirms via bank feeds, so await_settlement() usually returns within that window. The API has a default timeout of 5 minutes.

Q4: What if a customer's payment fails?

A: await_settlement() will return status='failed' if the QR expires (default 10 minutes) or the customer cancels. You can automatically regenerate a new checkout URL and ask them to retry. The original request_id is logged for audit purposes.

Q5: Is there a minimum or maximum payment amount?

A: Amounts must be >= 1,000 VND and <= 50,000,000 VND per transaction (per Vietnamese banking regulations). For larger amounts, you can process multiple payments in sequence.

Key Takeaways

Get Started Now

  1. Install the SDK: pip install agentpay-vn
  2. Register your merchant account: AgentPay VN Dashboard
  3. Verify your bank account (takes 24–48 hours)
  4. Copy your merchant credentials to your .env file
  5. Run the examples above in a Python script or Jupyter notebook
  6. Read the full API docs: https://agentpay.servicesai.vn/v1/docs
  7. Join the community: GitHub https://github.com/phuocdu/agentpay-vn

Your AI agent can now accept Vietnamese payments. Stop waiting for payment processors. Start collecting money in three lines of code.

Get started →

← All posts