Build AI Chatbot Payment Flow Without Holding Funds

2026-06-24 · AgentPay VN

ai-agentsvietqr-paymentspython-sdkmcp-serverfintech

The Problem: Why Most AI Commerce Solutions Get Payment Wrong

You've built an AI chatbot that sells digital products—online courses, design templates, API credits. It's smart, responsive, and users love it. But there's a friction point nobody talks about: payment processing.

Most solutions force you into one of two uncomfortable positions:

  1. Integrate a traditional payment gateway (Stripe, PayPal) and deal with KYC requirements, settlement delays, and fees that stack up as your transaction volume grows.
  2. Build a custom bank integration and suddenly you're holding customer funds in escrow—a regulatory minefield that turns your side project into a fintech compliance nightmare.

In Vietnam, there's a third way that's been hiding in plain sight: VietQR—a standardized QR payment system that points directly at your merchant bank account. But until now, there's been no simple way to wire it into AI agents.

Enter AgentPay VN. It's a lightweight, open-source (MIT) Python SDK + MCP server that lets your AI agent generate payment requests, send checkout URLs, and confirm settlements—all without ever touching the money.


What AgentPay VN Actually Does (And Doesn't Do)

Let's be crystal clear about what this isn't: it's not a payment processor or a payment gateway. AgentPay VN is a bridge layer. Think of it like this:

No escrow. No fund custody. No regulatory gray area. The money goes straight into your business bank account, and AgentPay VN simply records the fact that it happened.

For AI agent developers, this means:


Installation: Get Up and Running in 60 Seconds

If you have Python 3.9+ and pip installed, you're ready:

pip install agentpay-vn

For Claude integration via the MCP server:

pip install agentpay-mcp

Verify the install:

import agentpay_vn
print(agentpay_vn.__version__)

If you see a version number, you're good. Now let's build.


The Three-Line Payment Flow Explained

AgentPay VN condenses the entire payment lifecycle into three operations:

1. Create a Payment Request

Your agent calls this when a user wants to buy. You specify the amount, product ID, and optional metadata.

from agentpay_vn import AgentPayClient

# Initialize the client with your merchant bank details
client = AgentPayClient(
    bank_account="1234567890",  # Your bank account number
    bank_code="MB",             # Bank code (MB, VCB, TCB, etc.)
    merchant_name="My AI Shop"   # How it shows on the QR
)

# Create a payment request for a digital product
payment = client.create_payment_request(
    amount=49000,                 # Amount in VND
    description="Python Mastery Course",
    order_id="order_12345",       # Unique ID for this transaction
    expiry_minutes=30             # QR expires in 30 minutes
)

print(f"Payment ID: {payment.id}")
print(f"Checkout URL: {payment.checkout_url}")
print(f"QR Code (as SVG): {payment.qr_svg}")

What happens here: - create_payment_request() generates a unique payment ID and a VietQR-formatted checkout URL. - The checkout_url contains all the bank routing info—the customer's phone or browser will recognize it as a bank transfer instruction. - The qr_svg is an SVG string you can embed directly in your chatbot's UI. - You're not holding any money. The QR just encodes your bank account and the amount.

2. Send the Checkout URL to Your Customer

Your AI agent now presents this to the user:

# In your chatbot/agent loop
def sell_digital_product(user_id, product_name, price_vnd):
    payment = client.create_payment_request(
        amount=price_vnd,
        description=f"Purchase: {product_name}",
        order_id=f"user_{user_id}_product_{product_name}",
        expiry_minutes=15
    )

    # Return the checkout URL to your agent
    return {
        "status": "payment_pending",
        "message": f"Scan this QR to pay {price_vnd:,} VND for {product_name}",
        "checkout_url": payment.checkout_url,
        "qr_svg": payment.qr_svg,
        "payment_id": payment.id
    }

Your UI (web, Telegram, Discord, etc.) renders this QR code. The user scans it with their banking app and completes the transfer. That's it—the money is now in your bank account.

3. Await Settlement Confirmation

Your agent polls for confirmation:

import time

def wait_for_payment(payment_id, timeout_seconds=300):
    """
    Poll for settlement. Returns True if money arrived, False if timeout.
    """
    start = time.time()

    while time.time() - start < timeout_seconds:
        settlement = client.await_settlement(payment_id)

        if settlement.confirmed:
            print(f"✓ Payment confirmed! Amount: {settlement.amount} VND")
            print(f"  Timestamp: {settlement.settled_at}")
            print(f"  Bank reference: {settlement.bank_reference}")
            return True

        # Not settled yet; check again in 2 seconds
        time.sleep(2)

    print("✗ Payment timed out (user didn't scan/pay within 5 minutes)")
    return False

Key insight: await_settlement() does not hold funds. It just checks your bank's webhook/feed to see if a matching transaction arrived. Once it returns confirmed=True, your agent can: - Grant access to the digital product (send download link, API key, course enrollment, etc.) - Log the transaction - Send a thank-you message


Real-World Example: An Online Course Seller Bot

Let's walk through a concrete scenario: a chatbot that sells Python courses.

Setup:

from agentpay_vn import AgentPayClient
import json

class CourseSellerBot:
    def __init__(self):
        self.client = AgentPayClient(
            bank_account="1029384756",
            bank_code="MB",
            merchant_name="Tech Learning Hub"
        )
        self.courses = {
            "python-101": {"name": "Python Basics", "price": 99000},
            "django-pro": {"name": "Django Mastery", "price": 299000},
            "ai-agents": {"name": "AI Agents in Python", "price": 499000}
        }

    def buy_course(self, user_id, course_key):
        if course_key not in self.courses:
            return {"error": "Course not found"}

        course = self.courses[course_key]
        payment = self.client.create_payment_request(
            amount=course["price"],
            description=f"Course: {course['name']}",
            order_id=f"{user_id}_{course_key}_{int(time.time())}",
            expiry_minutes=20
        )

        # Store this payment ID so we can track it later
        with open(f"pending_{payment.id}.json", "w") as f:
            json.dump({
                "user_id": user_id,
                "course_key": course_key,
                "payment_id": payment.id,
                "amount": course["price"]
            }, f)

        return {
            "status": "waiting_for_payment",
            "course": course["name"],
            "amount": f"{course['price']:,} VND",
            "qr": payment.qr_svg,
            "payment_id": payment.id
        }

    def confirm_purchase(self, payment_id):
        settlement = self.client.await_settlement(payment_id)

        if settlement.confirmed:
            # Load the pending payment info
            with open(f"pending_{payment_id}.json", "r") as f:
                order = json.load(f)

            user_id = order["user_id"]
            course_key = order["course_key"]
            course_name = self.courses[course_key]["name"]

            # Grant access (in real system, call your LMS/database)
            self.grant_course_access(user_id, course_key)

            return {
                "success": True,
                "message": f"Welcome! You now have access to {course_name}",
                "access_link": f"https://yoursite.com/learn/{course_key}?user={user_id}"
            }
        else:
            return {"success": False, "message": "Payment not yet confirmed"}

    def grant_course_access(self, user_id, course_key):
        # TODO: Call your database/LMS to enroll the user
        print(f"✓ Enrolled user {user_id} in {course_key}")

# Usage
bot = CourseSellerBot()

# User asks: "I want the Django course"
response = bot.buy_course("user_abc123", "django-pro")
print(response["qr"])  # Render this QR in the UI

# After user scans and pays, check settlement
time.sleep(5)  # In real code, use a queue/webhook
result = bot.confirm_purchase(response["payment_id"])
print(result["message"])  # "Welcome! You now have access to Django Mastery"

What's happening: 1. User says "I want the Django course." 2. Bot creates a payment request (₫299,000). 3. Bot shows the QR code in the chat. 4. User scans → their banking app shows "Transfer ₫299,000 to Tech Learning Hub" → they confirm → money lands in your account. 5. AgentPay VN detects the settlement. 6. Bot grants access and sends the course link.

Total time: ~30 seconds from QR to course access. No fund custody. No regulatory complexity.


Integrating with Claude via MCP Server

If you want Claude to orchestrate the entire payment flow autonomously, use the MCP server:

Claude Configuration (claude_desktop_config.json):

{
  "mcpServers": {
    "agentpay": {
      "command": "python",
      "args": ["-m", "agentpay_mcp"],
      "env": {
        "AGENTPAY_BANK_ACCOUNT": "1029384756",
        "AGENTPAY_BANK_CODE": "MB",
        "AGENTPAY_MERCHANT_NAME": "My AI Shop"
      }
    }
  }
}

Now Claude can call these tools directly in a conversation:

User: "I want to buy the API Pro plan for 199,000 VND"

Claude: I'll set up a payment for you. [calls create_payment_request]

→ Here's your payment QR:
[QR CODE]
Scanning this will transfer 199,000 VND to our account.

User: [scans and pays]

Claude: [polls await_settlement every 2 seconds]
→ Payment confirmed! Your API key is: sk_live_abc123...

Claude handles the entire flow without your code touching the money.


Advanced Tips & Best Practices

1. Webhook Integration (Better Than Polling)

Instead of time.sleep(2) loops, set up your bank to send webhooks when payments arrive:

from flask import Flask, request

app = Flask(__name__)
client = AgentPayClient(...)

@app.route("/webhooks/settlement", methods=["POST"])
def handle_settlement():
    data = request.json
    # Bank sends: {"bank_reference": "...", "amount": 199000}
    settlement = client.verify_settlement(data)

    if settlement.verified:
        grant_access(settlement.order_id)

    return {"status": "ok"}

This is faster and more reliable than polling.

2. Idempotency: Prevent Double-Granting

Always store order_id uniquely:

order_id = f"{user_id}_{product_id}_{timestamp}_{random_nonce}"
payment = client.create_payment_request(
    amount=price,
    order_id=order_id  # Globally unique
)

If a webhook arrives twice, your database sees the same order_id and skips re-granting.

3. Expiry Times for Abandoned Carts

Set expiry_minutes based on your use case: - High-friction (course sales): 30–60 minutes - Impulse buys (stickers, digital art): 5–10 minutes - API credits: 20 minutes

4. Testing Without Real Payments

Use sandbox mode (if available in your bank):

client = AgentPayClient(
    bank_account="1029384756",
    bank_code="MB",
    merchant_name="My Shop",
    sandbox=True  # Generates test QRs
)

Do's and Don'ts

✅ Do ❌ Don't
Create a new order_id for every request Reuse order_id across different transactions
Store payment_id in your database immediately Assume settlement happened without confirming
Set sensible expiry_minutes (5–60) Leave QRs active forever
Verify settlement.bank_reference matches your bank feed Trust only the SDK's response
Use MCP for Claude integration Implement custom payment parsing
Log all transactions for accounting Treat AgentPay as a "set and forget" tool

FAQ

Q: Does AgentPay VN hold my money? A: No, never. The QR points directly at your bank account. AgentPay VN only records that a settlement happened. Your money goes straight to your bank.

Q: Can I use this with Telegram bots, Discord bots, web apps? A: Yes. AgentPay VN generates a checkout URL and QR SVG—you can render those anywhere. No special payment UI needed.

Q: What if a customer scans the QR twice and pays twice? A: Each create_payment_request() call generates a unique order_id. Your code checks settlement.order_id before granting access. You can refund the duplicate payment manually (it's just a regular bank transfer).

Q: Which Vietnamese banks are supported? A: Any bank with VietQR support (VCB, MB, TCB, ACB, Techcombank, VIB, etc.). The bank_code parameter controls where your money lands.


Key Takeaways


Get Started Now

Your first payment-enabled AI agent is 15 minutes away:

pip install agentpay-vn

Then copy the course seller example above and adapt it to your product. Full docs and examples live at:

📖 AgentPay VN Docs 💻 GitHub Repository

Questions? Check the docs, review the examples, or open an issue on GitHub. The MIT license means you can modify, sell, and redistribute—go build something cool.

Get started →

← All posts