new-api 系列(十):接入国际支付——Stripe、PayPal 与海外收银

系列目录

  1. (一)认识 new-api——新一代 AI API 管理网关
  2. (二)安装部署——从零搭建 new-api
  3. (三)渠道配置——接入各类模型提供商
  4. (四)令牌与用户管理
  5. (五)计价模型与成本控制
  6. (六)监控、日志与告警
  7. (七)生产环境部署与安全加固
  8. (八)进阶——多实例、负载均衡与 SSO
  9. (九)接入国内支付——支付宝、微信与自动充值
  10. (十)接入国际支付——Stripe、PayPal 与海外收银(本文)

# 国际支付接入 ## 支付平台对比 - Stripe:开发体验最佳 - PayPal:用户基数最大 - Paddle:全球税务代缴 - LemonSqueezy:零门槛 MoR - 加密货币(可选) ## Stripe 接入 - Checkout Session 一键集成 - Webhook 签名验证 - 信用卡 / Apple Pay / Google Pay - 订阅制(月度/年度) ## PayPal 接入 - REST API v2 - 订单创建 + 捕获 - Webhook 异步通知 - 覆盖 200+ 国家 ## Paddle / LemonSqueezy - 不需要自己处理税务 - 全球 VAT/GST 自动计算 - MoR(Merchant of Record)模式 ## 安全要点 - Webhook 签名验证(stripe-signature) - 幂等处理防重复扣款 - 金额服务端校验 - HTTPS 必须

国际支付与国内支付的区别

国内支付(第九篇)的核心痛点是没有营业执照怎么收款——所以方案围绕 V免签、易支付这些绕过资质的渠道。国际支付则不同:

  • 没有营业执照也能接入——Stripe、PayPal 支持个人注册(部分国家),Paddle 和 LemonSqueezy 完全不需要公司资质
  • 手续费透明——Stripe ~2.9% + $0.30,PayPal ~3.49% + 固定费,Paddle 5% + $0.50(含税务)
  • 开发体验好——SDK 完善、文档清晰、Webhook 签名标准
  • 但多了税务问题——卖给欧盟用户要交 VAT,卖给美国用户各州税率不同

方案选型

平台 门槛 手续费 支持国家 税务处理 适合场景
Stripe 个人/企业 ~2.9% + $0.30 46 个国家 自己处理 开发能力强,需要灵活定制
PayPal 个人/企业 ~3.49% + 固定费 200+ 国家 自己处理 用户覆盖广,发展中国家友好
LemonSqueezy 零门槛 5% + $0.50 全球 平台代缴 不想碰税务,最快上线
Paddle 零门槛 5% + $0.50 全球 平台代缴 SaaS 订阅制,同 LemonSqueezy
加密货币 零门槛 极低 全球 技术向用户,隐私优先
flowchart LR U["选择支付平台"] --> Q{"有公司主体?"} Q -- 有 --> REGION{"主要用户地区?"} REGION -- 欧美为主 --> STRIPE["Stripe\n费率 2.9% + $0.30\n开发体验最佳"] REGION -- 全球覆盖 --> PAYPAL["PayPal\n费率 3.49%\n200+ 国家"] Q -- 没有/不想管税 --> MOR["MoR 平台\nPaddle / LemonSqueezy\n费率 5%\n平台代缴 VAT/GST"] Q -- 技术向用户 --> CRYPTO["加密货币\nUSDT / USDC\n极低费率"]

MoR(Merchant of Record):平台作为法律上的卖方,你不需要自己注册税号、申报 VAT。Paddle 和 LemonSqueezy 都属于此类,代价是更高费率。

方案一:Stripe(推荐)

Stripe 是国际支付的事实标准,SDK 覆盖 Python / Node.js / Go / PHP 等主流语言,文档质量业界标杆。支持信用卡、Apple Pay、Google Pay、银行转账等数十种支付方式。

注册准备

  1. 访问 stripe.com 注册账号
  2. 验证邮箱,填写基本信息(个人或企业均可)
  3. 获取 API Keys:Dashboard → Developers → API keys
    • Publishable keypk_test_xxx / pk_live_xxx):前端用,可公开
    • Secret keysk_test_xxx / sk_live_xxx):后端用,必须保密
  4. 获取 Webhook Signing Secret:Dashboard → Developers → Webhooks → 添加端点
sequenceDiagram participant U as 用户 participant FE as 充值页面 participant BE as 你的后端 participant ST as Stripe API participant WH as Stripe Webhook participant NA as new-api U->>FE: 选择套餐,点击支付 FE->>BE: POST /pay/create-stripe-session BE->>ST: 创建 Checkout Session ST-->>BE: 返回 session URL BE-->>FE: 返回重定向 URL FE->>ST: 跳转 Stripe 支付页 U->>ST: 填写卡信息,确认支付 ST->>WH: POST Webhook(checkout.session.completed) WH->>BE: 转发到你的回调端点 BE->>BE: 验签 stripe-signature BE->>NA: 给令牌加额度 BE->>U: 显示支付成功

Stripe Checkout 集成(Python)

Stripe Checkout 是预构建的支付页面,不需要自己写前端支付表单。

第一步:安装 Stripe SDK

pip install stripe

第二步:后端创建 Checkout Session

# checkout_server.py
import stripe
import sqlite3
import uuid
import json
from datetime import datetime
from flask import Flask, request, jsonify, redirect

app = Flask(__name__)

stripe.api_key = "sk_live_xxxxxxxxxxxxxxxxxxxx"
STRIPE_WEBHOOK_SECRET = "whsec_xxxxxxxxxxxxxxxxxxxx"
DOMAIN = "https://api.example.com"

def get_db():
    conn = sqlite3.connect("orders.db")
    conn.row_factory = sqlite3.Row
    return conn

# = 创建支付会话 =
@app.route('/pay/create-stripe-session', methods=['POST'])
def create_stripe_session():
    data = request.json
    token_name = data['token_name']
    amount_usd = data['amount']  # 美元金额

    # 生成订单号
    order_id = f"STRIPE-{datetime.now().strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex[:8]}"

    # 记录订单
    conn = get_db()
    conn.execute(
        'INSERT INTO orders (order_id, token_name, amount, status, created_at) VALUES (?,?,?,?,?)',
        (order_id, token_name, amount_usd, 'pending', datetime.now().isoformat())
    )
    conn.commit()
    conn.close()

    # 创建 Stripe Checkout Session
    session = stripe.checkout.Session.create(
        payment_method_types=['card'],
        line_items=[{
            'price_data': {
                'currency': 'usd',
                'product_data': {
                    'name': f'API Credits - ${amount_usd}',
                    'description': f'Top-up for token: {token_name}',
                },
                'unit_amount': int(amount_usd * 100),  # Stripe 以"分"为单位
            },
            'quantity': 1,
        }],
        mode='payment',
        success_url=f'{DOMAIN}/pay/success?session_id={{CHECKOUT_SESSION_ID}}',
        cancel_url=f'{DOMAIN}/pay/cancel',
        metadata={
            'order_id': order_id,
            'token_name': token_name,
            'amount': str(amount_usd),
        }
    )

    return jsonify({'url': session.url, 'order_id': order_id})


# = Stripe Webhook 回调 =
@app.route('/pay/stripe/webhook', methods=['POST'])
def stripe_webhook():
    payload = request.get_data(as_text=True)
    sig_header = request.headers.get('Stripe-Signature')

    # 验签:这是关键安全步骤
    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, STRIPE_WEBHOOK_SECRET
        )
    except stripe.error.SignatureVerificationError:
        return 'Invalid signature', 400

    # 只处理支付完成事件
    if event['type'] != 'checkout.session.completed':
        return 'ok'

    session = event['data']['object']
    metadata = session['metadata']
    order_id = metadata['order_id']
    token_name = metadata['token_name']
    amount = float(metadata['amount'])

    # 幂等检查:防止 Stripe 多次发送同一事件
    conn = get_db()
    order = conn.execute(
        'SELECT status FROM orders WHERE order_id=?', (order_id,)
    ).fetchone()

    if not order:
        conn.close()
        return 'Order not found', 404

    if order['status'] == 'paid':
        conn.close()
        return 'Already processed'  # 幂等

    # 金额二次校验
    db_amount = conn.execute(
        'SELECT amount FROM orders WHERE order_id=?', (order_id,)
    ).fetchone()['amount']
    if abs(amount - db_amount) > 0.01:
        conn.close()
        return 'Amount mismatch', 400

    # 标记已支付
    conn.execute(
        'UPDATE orders SET status=? WHERE order_id=?', ('paid', order_id)
    )
    conn.commit()
    conn.close()

    # 给 new-api 加额度
    add_quota_to_token(token_name, amount)

    return 'ok'


# = 给令牌加额度(与国内支付篇相同逻辑)=
def add_quota_to_token(token_name, amount_usd):
    conn = sqlite3.connect('/home/user/new-api/new-api.db')
    token = conn.execute(
        'SELECT id, remain_quota FROM tokens WHERE name=?', (token_name,)
    ).fetchone()
    if token:
        new_quota = token[1] + int(amount_usd * 100)
        conn.execute('UPDATE tokens SET remain_quota=? WHERE id=?',
                     (new_quota, token[0]))
        conn.commit()
    conn.close()


if __name__ == '__main__':
    app.run(host='127.0.0.1', port=5000)

第三步:前端充值页面

<!-- topup-stripe.html -->
<!DOCTYPE html>
<html>
<head><title>API Credits Top-Up</title></head>
<body>
  <h2>Top-Up Your API Credits</h2>
  <div>
    <label>Token Name:</label>
    <input type="text" id="tokenName" placeholder="Your token name">
  </div>
  <div>
    <label>Amount (USD):</label>
    <select id="amount">
      <option value="5">$5</option>
      <option value="10">$10</option>
      <option value="20">$20</option>
      <option value="50">$50</option>
    </select>
  </div>
  <button onclick="checkout()">Pay with Card</button>

  <script src="https://js.stripe.com/v3/"></script>
  <script>
  async function checkout() {
    const tokenName = document.getElementById('tokenName').value;
    const amount = document.getElementById('amount').value;
    if (!tokenName) return alert('Please enter your token name');

    const resp = await fetch('/pay/create-stripe-session', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({token_name: tokenName, amount: parseFloat(amount)})
    });
    const data = await resp.json();

    // 跳转到 Stripe Checkout 页面
    window.location.href = data.url;
  }
  </script>
</body>
</html>

Stripe 订阅制(月度/年度)

如果你的 API 服务是订阅制而非按量充值,用 Stripe Billing:

# 创建订阅(在 Stripe Dashboard 先建好 Product + Price)
subscription = stripe.Subscription.create(
    customer=customer_id,
    items=[{'price': 'price_monthly_10usd'}],
    metadata={'token_name': token_name}
)

# Webhook 监听 invoice.paid 事件
# 每次自动扣款成功 → 重置令牌月度额度
flowchart LR U["用户订阅\n$10/月"] --> ST["Stripe Billing\n自动扣款"] ST --> WH["invoice.paid\nWebhook"] WH --> BE["后端处理"] BE --> NA["重置令牌额度\nset remain_quota = 10"] BE --> U2["用户继续使用"]

Stripe 的关键安全实践

  • Webhook 验签是必须的stripe.Webhook.construct_event() 验证签名,防止伪造回调
  • 幂等处理:Stripe 可能重试发送 Webhook,先查订单状态再处理
  • 金额服务端校验:不要信任 Webhook payload 里的金额,和数据库订单记录比对
  • HTTPS 必须:Stripe 要求 Webhook 端点是 HTTPS

方案二:PayPal

PayPal 用户基数全球最大,覆盖 200+ 国家和地区,在发展中国家尤其普及。如果目标用户不全是欧美,PayPal 是必须接的渠道。

PayPal REST API 集成

# paypal_server.py
import paypalrestsdk
import sqlite3
from flask import Flask, request, jsonify

app = Flask(__name__)

paypalrestsdk.configure({
    'mode': 'live',  # sandbox / live
    'client_id': 'your-paypal-client-id',
    'client_secret': 'your-paypal-client-secret',
})

# = 创建 PayPal 订单 =
@app.route('/pay/create-paypal-order', methods=['POST'])
def create_paypal_order():
    data = request.json
    amount = data['amount']
    order_id = data['order_id']

    payment = paypalrestsdk.Payment({
        'intent': 'sale',
        'payer': {'payment_method': 'paypal'},
        'redirect_urls': {
            'return_url': f'{DOMAIN}/pay/paypal/success',
            'cancel_url': f'{DOMAIN}/pay/cancel',
        },
        'transactions': [{
            'amount': {'total': str(amount), 'currency': 'USD'},
            'description': f'API Credits Top-Up',
            'invoice_number': order_id,
        }]
    })

    if payment.create():
        # 获取用户跳转的支付链接
        for link in payment.links:
            if link.rel == 'approval_url':
                return jsonify({'url': link.href})
    else:
        return jsonify({'error': payment.error}), 500


# = PayPal 支付完成回调 =
@app.route('/pay/paypal/success', methods=['GET'])
def paypal_success():
    payment_id = request.args.get('paymentId')
    payer_id = request.args.get('PayerID')

    payment = paypalrestsdk.Payment.find(payment_id)
    if payment.execute({'payer_id': payer_id}):
        order_id = payment.transactions[0].invoice_number
        amount = float(payment.transactions[0].amount.total)
        # 加额度(需要从订单数据库获取 token_name)
        process_topup(order_id, amount)
        return 'Payment successful!'
    else:
        return 'Payment failed', 500


# = PayPal Webhook(异步通知)=
@app.route('/pay/paypal/webhook', methods=['POST'])
def paypal_webhook():
    headers = {
        'PayPal-Auth-Algo': request.headers.get('PAYPAL-AUTH-ALGO'),
        'PayPal-Cert-Url': request.headers.get('PAYPAL-CERT-URL'),
        'PayPal-Transmission-Id': request.headers.get('PAYPAL-TRANSMISSION-ID'),
        'PayPal-Transmission-Sig': request.headers.get('PAYPAL-TRANSMISSION-SIG'),
        'PayPal-Transmission-Time': request.headers.get('PAYPAL-TRANSMISSION-TIME'),
    }

    body = request.get_data(as_text=True)
    webhook_id = 'your-webhook-id'

    # 验签
    verified = paypalrestsdk.WebhookEvent.verify(
        headers, body, webhook_id
    )

    if not verified:
        return 'Invalid signature', 400

    event = request.json
    if event['event_type'] == 'PAYMENT.SALE.COMPLETED':
        # 处理到账
        order_id = event['resource']['invoice_number']
        amount = float(event['resource']['amount']['total'])
        process_topup(order_id, amount)

    return 'ok'

PayPal 注意事项

  • 两种模式sandbox(测试)和 live(生产),开发时用 sandbox 创建测试买家账号
  • IPN vs Webhook:PayPal 有两套回调机制,优先用新版 Webhook(REST API),老 IPN 逐渐淘汰
  • 货币转换费:如果用户支付币种不是 USD,PayPal 会加收货币转换费,定价时需考虑
  • 争议处理:PayPal 买家保护机制很强,卖家在争议中常处于劣势,建议做好服务条款

方案三:Paddle / LemonSqueezy(MoR 平台)

如果你不想处理 VAT、GST 等全球税务问题,MoR(Merchant of Record)平台是最省心的选择。

PaddleLemonSqueezy 的运作模式:平台作为法律上的卖方,你作为供应商。用户付款给平台,平台扣除手续费和税后结算给你。税务申报、发票开具全由平台负责。

对比

特性 Paddle LemonSqueezy
费率 5% + $0.50 5% + $0.50
支付方式 信用卡、PayPal 信用卡、PayPal、Apple Pay
门槛 需要审核(较严) 即开即用
结算周期 月结 周结 / 月结
API 质量 简洁(类似 Stripe)
适合 成熟的 SaaS 产品 个人开发者、快速上线

LemonSqueezy 集成(最简单)

LemonSqueezy 的 API 设计几乎照搬 Stripe,学习成本极低。

# lemonsqueezy 集成
import requests

LEMONSQUEEZY_API_KEY = "your-api-key"
LEMONSQUEEZY_STORE_ID = "your-store-id"
LEMONSQUEEZY_WEBHOOK_SECRET = "your-webhook-secret"

def create_checkout(variant_id, order_id, token_name):
    """创建 LemonSqueezy 支付链接"""
    resp = requests.post(
        'https://api.lemonsqueezy.com/v1/checkouts',
        headers={
            'Authorization': f'Bearer {LEMONSQUEEZY_API_KEY}',
            'Accept': 'application/json',
            'Content-Type': 'application/json',
        },
        json={
            'data': {
                'type': 'checkouts',
                'attributes': {
                    'checkout_data': {
                        'custom': {
                            'order_id': order_id,
                            'token_name': token_name,
                        }
                    }
                },
                'relationships': {
                    'store': {
                        'data': {'type': 'stores', 'id': LEMONSQUEEZY_STORE_ID}
                    },
                    'variant': {
                        'data': {'type': 'variants', 'id': variant_id}
                    }
                }
            }
        }
    )
    return resp.json()['data']['attributes']['url']


# Webhook 验签(LemonSqueezy 使用 HMAC-SHA256)
import hmac
import hashlib

@app.route('/pay/lemonsqueezy/webhook', methods=['POST'])
def lemonsqueezy_webhook():
    payload = request.get_data(as_text=True)
    signature = request.headers.get('X-Signature')

    expected = hmac.new(
        LEMONSQUEEZY_WEBHOOK_SECRET.encode(),
        payload.encode(),
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(signature, expected):
        return 'Invalid signature', 400

    event = request.json
    if event['meta']['event_name'] == 'order_created':
        custom = event['data']['attributes']['first_order_item']['checkout_data']['custom']
        order_id = custom['order_id']
        token_name = custom['token_name']
        # 加额度
        add_quota_to_token(token_name, amount_from_variant(event))

    return 'ok'

MoR 平台的优势

  • 零税务负担:平台自动计算和代缴 VAT/GST/Sales Tax,你不需要任何税号
  • 合规即服务:GDPR、PCI-DSS 全部由平台负责
  • 快速上线:注册即用,不需要等审核(LemonSqueezy)
  • 但费率更高:5% 比 Stripe 的 2.9% 贵了近一倍,月流水大时值得自己处理税务

方案四:加密货币(可选补充)

如果你的用户群体偏技术向,加密货币是一个低成本、无退款、全球通的收款方式。

USDT / USDC 收款

# 最简单的方案:生成固定收款地址,用户转账后手动确认
# 或使用 Coinbase Commerce / NOWPayments 等平台的 API

import requests

def create_crypto_invoice(amount_usd, order_id):
    """通过 Coinbase Commerce 创建加密币支付"""
    resp = requests.post(
        'https://api.commerce.coinbase.com/charges',
        headers={
            'X-CC-Api-Key': 'your-api-key',
            'X-CC-Version': '2018-03-22',
        },
        json={
            'name': 'API Credits',
            'description': f'Top-Up Order #{order_id}',
            'local_price': {'amount': str(amount_usd), 'currency': 'USD'},
            'pricing_type': 'fixed_price',
            'metadata': {'order_id': order_id},
        }
    )
    return resp.json()['data']['hosted_url']


# Webhook 处理
@app.route('/pay/crypto/webhook', methods=['POST'])
def crypto_webhook():
    event = request.json
    # Coinbase Commerce Webhook 验签(SHA256 HMAC)
    # ...

    if event['type'] == 'charge:confirmed':
        order_id = event['data']['metadata']['order_id']
        # 加额度
        process_topup(order_id, amount)
    return 'ok'

加密货币的取舍

优点 缺点
零手续费(或极低) 用户门槛高
不可退款(无 chargeback) 到账需要区块确认(分钟级)
全球无差别 币价波动

建议作为补充渠道而非主力。偏技术的 Indie Hacker 圈子里加密币接受度较高。

多支付渠道统一架构

同时接入多个支付渠道时,推荐用统一的订单服务抽象:

flowchart LR U["用户"] --> FE["充值页面\n选择金额 + 支付方式"] FE --> ORD["订单服务\n生成 order_id\n记录渠道 + 金额 + token"] ORD --> GW{"支付渠道?"} GW -- Stripe --> ST["Stripe Checkout"] GW -- PayPal --> PP["PayPal REST"] GW -- Paddle/LSQ --> MO["MoR Checkout"] GW -- Crypto --> CR["Coinbase Commerce"] ST --> CB["统一回调入口\n/pay/webhook/"] PP --> CB MO --> CB CR --> CB CB --> VERIFY["验签 + 匹配订单"] VERIFY --> TOPUP["加额度\nnew-api DB"] VERIFY --> LOG["充值日志"]

统一回调入口示例

@app.route('/pay/webhook/<provider>', methods=['POST'])
def unified_webhook(provider):
    handlers = {
        'stripe': handle_stripe_webhook,
        'paypal': handle_paypal_webhook,
        'lemonsqueezy': handle_lemonsqueezy_webhook,
        'coinbase': handle_coinbase_webhook,
    }

    handler = handlers.get(provider)
    if not handler:
        return 'Unknown provider', 404

    try:
        result = handler(request)
        return result
    except SignatureError:
        return 'Invalid signature', 400
    except OrderNotFoundError:
        return 'Order not found', 404
    except DuplicateOrderError:
        return 'Already processed'

推荐选型指南

你的情况 推荐平台 原因
有美国/欧洲公司,开发能力强 Stripe 费率低,可定制性最强
用户遍布发展中国家 Stripe + PayPal PayPal 覆盖 Stripe 不支持的地区
个人开发者,不想碰税务 LemonSqueezy 零门槛,5 分钟上线
成熟 SaaS,月流水 >$10K Paddle 对大额有费率折扣
API 服务有技术门槛 Stripe + 加密货币 加密币用户和 API 用户画像重合
同时服务国内 + 海外 第九篇方案 + 本文方案 国内外分开,支付页面按 IP 分流

系列导航

系列导航

实操清单

  • 确定目标用户地区,选择合适的支付平台
  • 注册 Stripe(或 PayPal / LemonSqueezy),获取 API Key
  • 在平台 Dashboard 配置 Webhook 端点(必须 HTTPS)
  • 部署后端创建订单 + Webhook 回调服务
  • 实现 Webhook 验签(stripe-signature / HMAC)
  • 实现幂等处理(检查订单状态防重复加额度)
  • 实现金额服务端校验(对比数据库记录)
  • 编写充值页面前端(跳转 Checkout 或内嵌支付表单)
  • 用 Stripe test mode / PayPal sandbox 完成全流程测试
  • 测试验签失败场景(确认返回错误而非加额度)
  • 测试重复 Webhook(确认第二次返回 Already processed)
  • 如用 MoR 平台,确认 VAT/GST 发票自动生成
  • 切换到 live mode,做一笔真实小额支付验证