new-api 系列(十):接入国际支付——Stripe、PayPal 与海外收银
系列目录
- (一)认识 new-api——新一代 AI API 管理网关
- (二)安装部署——从零搭建 new-api
- (三)渠道配置——接入各类模型提供商
- (四)令牌与用户管理
- (五)计价模型与成本控制
- (六)监控、日志与告警
- (七)生产环境部署与安全加固
- (八)进阶——多实例、负载均衡与 SSO
- (九)接入国内支付——支付宝、微信与自动充值
- (十)接入国际支付——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、银行转账等数十种支付方式。
注册准备
- 访问 stripe.com 注册账号
- 验证邮箱,填写基本信息(个人或企业均可)
- 获取 API Keys:Dashboard → Developers → API keys
- Publishable key(
pk_test_xxx/pk_live_xxx):前端用,可公开 - Secret key(
sk_test_xxx/sk_live_xxx):后端用,必须保密
- Publishable key(
- 获取 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)平台是最省心的选择。
Paddle 和 LemonSqueezy 的运作模式:平台作为法律上的卖方,你作为供应商。用户付款给平台,平台扣除手续费和税后结算给你。税务申报、发票开具全由平台负责。
对比
| 特性 | 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 分流 |
系列导航
系列导航
- (一)认识 new-api——新一代 AI API 管理网关
- (二)安装部署——从零搭建 new-api
- (三)渠道配置——接入各类模型提供商
- (四)令牌与用户管理
- (五)计价模型与成本控制
- (六)监控、日志与告警
- (七)生产环境部署与安全加固
- (八)进阶——多实例、负载均衡与 SSO
- (九)接入国内支付——支付宝、微信与自动充值
- (十)接入国际支付——Stripe、PayPal 与海外收银(本文)
实操清单
- 确定目标用户地区,选择合适的支付平台
- 注册 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,做一笔真实小额支付验证