Build a Paid MCP Server: Charge Users from Claude

2026-07-08 · AgentPay VN

mcp-serverai-agentspayment-integrationclaudepython-sdk

The Problem: Your AI Agent Delivers Value—But How Do You Get Paid?

Imagine you've built a brilliant Claude-powered MCP server that helps users generate marketing copy, analyze documents, or book consultations. It works flawlessly. Users love it. But when someone asks "how much does this cost?"—you freeze.

Traditional payment systems weren't designed for AI agent interactions. They require page redirects, modal popups, and friction. Your agent can't seamlessly collect money within the Claude conversation. You're forced into clunky choices: ask users to visit a separate website, implement complex webhook orchestration, or worse—offer your service free while hoping for sponsorship.

This is where AgentPay VN changes the game.

What AgentPay VN Does (In Plain English)

AgentPay VN is an open-source Python SDK and MCP server that lets your AI agents collect VietQR payments directly from within Claude—without ever touching the money.

Here's the magic:

  1. Your agent creates a payment request (e.g., "Process this 100,000 VND analysis")
  2. A QR code URL is generated pointing straight to the merchant's bank account
  3. The user scans and pays via their VietQR-compatible banking app
  4. Your system receives a settlement confirmation and delivers the service
  5. Money lands in your bank account—AgentPay never holds it

It's MIT-licensed, runs on your infrastructure, and requires just one line: pip install agentpay-vn.

Real-World Scenario: A Course-Selling Bot

Let's say you've deployed an MCP server that sells access to a premium AI training course. Here's what happens:

User: "Can I enroll in the advanced prompt engineering course?"

Claude (via AgentPay):

Sure! The 7-week course costs 499,000 VND.
Here's your payment link: [QR code image]
Scan with your banking app and I'll unlock your materials.

Behind the scenes: Your agent detects the payment confirmation and immediately generates a unique access token, enrolls the user in your LMS, and sends a welcome email. All within 5 seconds. No manual work. No merchant account complexity.

Setting Up: The 3-Step Installation

Step 1: Install the SDK

pip install agentpay-vn

This gives you the Python library. If you want to use AgentPay as an MCP server (recommended for Claude integration), also install:

pip install agentpay-mcp

Step 2: Configure Your MCP Server in Claude

Add this to your Claude desktop configuration file (typically ~/.claude_desktop_config.json):

{
  "mcpServers": {
    "agentpay": {
      "command": "agentpay-mcp",
      "args": [
        "--merchant-id",
        "YOUR_MERCHANT_ID",
        "--bank-account",
        "YOUR_BANK_ACCOUNT",
        "--bank-code",
        "BANK_CODE"
      ]
    }
  }
}

Replace placeholders with your VietQR merchant details. Your bank provides these when you set up QR billing.

Step 3: Restart Claude and Verify

Restart Claude Desktop. AgentPay tools are now available to your agents.

The Core Flow: Create → Send → Settle

Let's walk through the actual code your MCP server would execute:

Creating a Payment Request

from agentpay_vn import PaymentClient

# Initialize the client with your credentials
client = PaymentClient(
    merchant_id="YOUR_MERCHANT_ID",
    bank_account="0123456789",
    bank_code="970436",  # Example: Techcombank
    api_key="your_api_key_here"
)

# Create a payment request
payment = client.create_payment_request(
    amount=499000,  # VND
    description="Premium Course Access - 7 weeks",
    order_id="course_001_user123",
    currency="VND"
)

print(f"Payment URL: {payment['checkout_url']}")
print(f"Request ID: {payment['request_id']}")
# Output:
# Payment URL: https://qr.vietqr.io/...
# Request ID: req_abc123xyz

Line-by-line breakdown: - PaymentClient(): Initializes the SDK with your merchant credentials (never changes per session) - create_payment_request(): Generates a unique payment request. The order_id is crucial—it links this transaction to your internal system (e.g., user ID, course ID) - checkout_url: This is the QR code URL you display to the user - request_id: Store this—you'll poll for settlement status using it

Awaiting Settlement Confirmation

import asyncio
import time

async def wait_for_payment(request_id, timeout_seconds=300):
    """Poll for payment settlement (up to 5 minutes)"""
    start_time = time.time()

    while time.time() - start_time < timeout_seconds:
        # Check settlement status
        status = client.await_settlement(
            request_id=request_id,
            poll_interval=5  # Check every 5 seconds
        )

        if status['settled']:
            print(f"✓ Payment confirmed! Amount: {status['amount']} VND")
            print(f"Settlement ID: {status['settlement_id']}")
            return status

        print(f"Waiting for payment... ({int(time.time() - start_time)}s elapsed)")
        await asyncio.sleep(5)

    raise TimeoutError("Payment not received within 5 minutes")

# Usage in your MCP tool:
result = await wait_for_payment("req_abc123xyz")
if result['settled']:
    # Unlock course access, generate credentials, etc.
    unlock_course_for_user(user_id="user123")

Key points: - await_settlement() polls the bank feed (not AgentPay's database—directly from your bank) - poll_interval=5 means we check every 5 seconds; adjust based on your UX needs - Settlement is final and irreversible once confirmed - The timeout prevents infinite loops; adjust based on your use case

Integrating with Your MCP Server

Here's how to expose AgentPay as a Claude tool:

from typing import Any
from mcp.server.models import Tool
from mcp.types import TextContent

# Define the MCP tool
def create_payment_tool() -> Tool:
    return Tool(
        name="create_vietqr_payment",
        description="Generate a VietQR payment request for user charges",
        inputSchema={
            "type": "object",
            "properties": {
                "amount": {
                    "type": "integer",
                    "description": "Amount in VND (e.g., 499000)"
                },
                "description": {
                    "type": "string",
                    "description": "Payment description shown to user"
                },
                "order_id": {
                    "type": "string",
                    "description": "Unique order ID (e.g., 'course_001_user123')"
                }
            },
            "required": ["amount", "description", "order_id"]
        }
    )

# Handle the tool call
async def handle_create_payment(amount: int, description: str, order_id: str) -> str:
    payment = client.create_payment_request(
        amount=amount,
        description=description,
        order_id=order_id
    )

    return f"Payment link: {payment['checkout_url']}\nRequest ID: {payment['request_id']}"

Now Claude can call this tool naturally:

Claude: "I'll create a payment request for 499,000 VND for your course access."

[Calling create_vietqr_payment with amount=499000, description="Premium Course Access", order_id="course_001_user456"]

Advanced Tips: Production Best Practices

1. Store Request IDs in Your Database

Map request_id to user/order records so you can reconcile settlements:

import uuid
from datetime import datetime

# When creating a payment:
order_id = f"course_{course_id}_{user_id}_{int(datetime.now().timestamp())}"
payment = client.create_payment_request(
    amount=499000,
    description="Course Access",
    order_id=order_id
)

# Save to your DB:
db.insert('pending_payments', {
    'request_id': payment['request_id'],
    'order_id': order_id,
    'user_id': user_id,
    'amount': 499000,
    'created_at': datetime.now(),
    'status': 'pending'
})

2. Implement Webhook Listeners (Optional)

Instead of polling, listen for bank-triggered webhooks (if your bank supports it):

# Your FastAPI/Flask endpoint
@app.post("/webhooks/settlement")
async def handle_settlement_webhook(payload: dict):
    request_id = payload.get('request_id')
    amount = payload.get('amount')

    # Mark as settled in your DB
    db.update('pending_payments', 
              {'request_id': request_id},
              {'status': 'settled', 'settled_at': datetime.now()})

    # Unlock access immediately
    unlock_course_for_order(request_id)
    return {"status": "ok"}

3. Set Reasonable Timeouts

VietQR users have varying response times. A 5-minute timeout is standard, but for high-friction scenarios (enterprise purchases), extend to 15 minutes.

4. Display the QR Code Correctly

AgentPay returns a QR URL. Render it as an image in Claude:

from PIL import Image
import qrcode

# If you need to generate the QR locally:
qr = qrcode.QRCode(version=1, box_size=10, border=4)
qr.add_data(payment['checkout_url'])
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
img.save("payment_qr.png")

Do's and Don'ts

Do ✓ Don't ✗
Store request_id for reconciliation Assume settlement happened without confirmation
Set a reasonable timeout (5-15 min) Wait indefinitely for payment
Include order_id in every request Reuse request_ids across different transactions
Test with small amounts first Go live with production amounts immediately
Log all payment events Ignore failed/expired requests
Use unique order_id per transaction Create multiple requests for same purchase

FAQ

Q: Does AgentPay hold my money? No. The QR code points directly to your bank account. AgentPay is purely a request/settlement bridge—never a custodian.

Q: What if the user doesn't pay within 5 minutes? The request expires. Generate a new one. You can offer a "resend payment link" option in Claude.

Q: Can I use this outside Vietnam? VietQR is Vietnam-specific. AgentPay VN is optimized for Vietnamese merchants and banking infrastructure. For international payments, you'd need a different provider (Stripe, etc.).

Q: What banks support VietQR? Most Vietnamese banks do (Techcombank, VietcomBank, BIDV, Vietinbank, etc.). Check your bank's documentation for merchant QR setup.

Q: Is there a transaction fee? Your bank determines fees (typically 0.5–1.5% per transaction). AgentPay itself is free and open-source.

Key Takeaways

Getting Started Now

You're 3 minutes away from your first payment integration:

pip install agentpay-vn

Then visit the full documentation for detailed API reference, webhook examples, and troubleshooting.

Explore the code and contribute on GitHub.

Start building agents that earn their keep. Your users are ready to pay for real value—AgentPay makes it frictionless.

Get started →

← All posts