new-api 系列(九):接入国内支付——支付宝、微信与自动充值
系列目录
问题:new-api 有额度系统但没有支付
new-api 的令牌额度管理很完善——创建令牌时设额度、用完自动停、月额度自动重置。但它只提供了管理端手动加额度,没有面向终端用户的自助充值入口。
如果你在对外提供 API 服务,用户需要能自己付钱充值。本文讲解三种方案,从零搭建支付流程。
方案概览
| 方案 | 门槛 | 费时 | 手续费 | 适合场景 |
|---|---|---|---|---|
| 人工充值 | 零 | 每次手动 | 零 | 小团队内部、亲友圈 |
| 个人免签(V免签) | 一台安卓手机 | 一次搭建 | 零 | 个人站长、小规模商用 |
| 第三方聚合(易支付) | 注册账号 | 一次搭建 | 2%~5% | 不想折腾手机的 |
| 官方 API | 企业营业执照 | 开发对接 | 0.6% | 正规商业运营 |
方案一:人工充值(最简,零成本)
如果你只是给几个朋友或小团队用,直接管理员后台手动加额度最简单。
操作流程:
- 用户微信/支付宝转账给你
- 你登录 new-api 后台 →「令牌」→ 找到对应用户的令牌
- 编辑令牌,手动增加「剩余额度」
- 截图或口头告知用户已到账
优点:零门槛,不需要任何额外部署。 缺点:你人在的时候才能充值,不适合用户自主操作。
适用:团队内部使用、10 人以内的用户群。
方案二:个人免签支付(V免签 / 码支付)
这是个人站长最常用的方案——不需要营业执照,用一台闲置安卓手机监听支付宝/微信的到账通知,自动回调你的服务器完成充值。
V免签 原理
V免签 APP 运行在一台安卓手机上,保持支付宝/微信在后台运行。当有付款到账时,APP 读取通知栏的到账信息,回调你的服务器。
部署步骤
第一步:准备安卓手机
- 一台闲置安卓手机(Android 7.0+)
- 安装支付宝和微信,登录你的收款账号
- 开启通知栏权限
第二步:安装 V免签 APP
从 V免签 GitHub 下载 APK,安装到手机上。打开后配置:
| 配置项 | 说明 |
|---|---|
| 回调地址 | 你的服务器 URL,如 https://api.example.com/pay/notify |
| 通信密钥 | 随机字符串,用于签名验证 |
| 监听模式 | 通知栏监听(推荐) |
第三步:写充值页面和回调服务
下面是一个最小化的回调处理示例(Python Flask),部署在你的服务器上:
# callback_server.py
from flask import Flask, request, jsonify
import hashlib
import hmac
import requests
import sqlite3
from datetime import datetime
app = Flask(__name__)
# = 配置 =
VMQ_SECRET = "your-communication-key" # 与 V免签 APP 里一致
NEW_API_URL = "http://127.0.0.1:3000"
ADMIN_TOKEN = "sk-your-admin-token" # new-api 管理员令牌
DB_PATH = "orders.db" # 订单数据库
# = 订单数据库初始化 =
def init_db():
conn = sqlite3.connect(DB_PATH)
conn.execute('''CREATE TABLE IF NOT EXISTS orders (
order_id TEXT PRIMARY KEY,
token_name TEXT,
amount REAL,
status TEXT DEFAULT 'pending',
created_at TEXT
)''')
conn.commit()
conn.close()
init_db()
# = V免签 回调接收 =
@app.route('/pay/notify', methods=['POST'])
def vmq_callback():
data = request.form
# V免签回调字段: price, mark, sign, type
price = data.get('price')
mark = data.get('mark') # 订单号
sign = data.get('sign')
# 验签:price + mark + type 的 MD5 + secret
raw = f"{price}{mark}{data.get('type', '')}"
expected = hashlib.md5((raw + VMQ_SECRET).encode()).hexdigest()
if sign != expected:
return 'sign error', 403
# 查找订单
conn = sqlite3.connect(DB_PATH)
order = conn.execute('SELECT * FROM orders WHERE order_id=? AND status="pending"',
(mark,)).fetchone()
if not order:
conn.close()
return 'order not found', 404
# 校验金额(允许 0.01 误差)
if abs(float(price) - order[2]) > 0.01:
conn.close()
return 'amount mismatch', 400
# 标记订单已支付
conn.execute('UPDATE orders SET status="paid" WHERE order_id=?', (mark,))
conn.commit()
conn.close()
# 调用 new-api 给用户加额度
add_quota_to_token(order[1], order[2])
return 'success'
# = 给 new-api 用户加额度 =
def add_quota_to_token(token_name, amount_usd):
"""
通过 new-api 的管理 API 给令牌增加额度。
new-api 目前没有直接的"加额度"API,这里用两种变通方式:
1) 如果有管理 API,直接调用
2) 或者直接操作 SQLite 数据库(仅限 SQLite 部署)
"""
# 方式一:如果有暴露管理 API(取决于 new-api 版本)
# resp = requests.post(f"{NEW_API_URL}/api/token/quota",
# headers={"Authorization": f"Bearer {ADMIN_TOKEN}"},
# json={"token_name": token_name, "quota": amount_usd})
# return resp.ok
# 方式二:直接操作数据库(SQLite,简单粗暴)
# 注意:操作前停服或确保 new-api 不会同时写入
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) # new-api 内部以"分"存储
conn.execute('UPDATE tokens SET remain_quota=? WHERE id=?',
(new_quota, token[0]))
conn.commit()
conn.close()
# = 创建充值订单 =
@app.route('/pay/create', methods=['POST'])
def create_order():
data = request.json
token_name = data.get('token_name')
amount = data.get('amount') # 美元金额
import uuid
order_id = f"TOPUP{datetime.now().strftime('%Y%m%d%H%M%S')}{uuid.uuid4().hex[:6]}"
conn = sqlite3.connect(DB_PATH)
conn.execute(
'INSERT INTO orders (order_id, token_name, amount, created_at) VALUES (?,?,?,?)',
(order_id, token_name, amount, datetime.now().isoformat())
)
conn.commit()
conn.close()
# 返回收款信息(V免签通常不需要额外参数,用户直接扫你固定的收款码)
# 付款备注填订单号,V免签通过 mark 字段回传
return jsonify({
'order_id': order_id,
'amount': amount,
'qr_note': f'请转账 {amount} 元,备注:{order_id}'
})
if __name__ == '__main__':
app.run(host='127.0.0.1', port=5000)
第四步:用 Nginx/Caddy 反代回调服务
# 在已有的 Nginx 配置中追加
location /pay/ {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
提示:V免签的回调地址需要公网可达,不能用
127.0.0.1。确保你的服务器有域名 + HTTPS。
码支付(替代方案)
码支付 是另一个类似的方案,通过软件监听 PC 端支付宝/微信收款。不需要手机,但需要 PC 一直开机。接入流程类似。
方案二的小结
- 需要一台安卓手机 24 小时开机跑 V免签 APP
- 回调服务需要公网可达
- 零手续费,适合个人站长
- 注意手机端的通知权限不要被系统杀掉
方案三:第三方聚合支付(易支付)
如果你不想维护一台安卓手机,可以用第三方支付平台。易支付是国内站长圈最常用的个人可用的聚合支付,支持支付宝、微信扫码。
自建 vs 使用第三方
| 方式 | 说明 |
|---|---|
| 使用别人搭建的易支付 | 注册账号即可,但费率较高(3%~5%),且有跑路风险 |
| 自建易支付 | 在自己的服务器上搭建,需要对接官方 API(支付宝/微信)或上游易支付 |
自建易支付同样需要解决收款渠道的问题——如果你有企业资质,可以直接对接支付宝/微信官方 API;如果没有,自建易支付也需要找一个上游渠道。
对接流程(以使用第三方易支付为例)
第一步:注册易支付账号
找一个靠谱的易支付平台注册(注意甄别,行业内跑路的不少)。注册后获取:
- 商户 ID(
pid) - 商户密钥(
key) - 平台 API 地址
第二步:写充值页面
# 易支付集成示例
import requests
import hashlib
def create_epay_order(amount, order_id, notify_url, return_url):
"""创建易支付订单,返回支付链接"""
EP_API = "https://pay.example.com/submit.php"
pid = "your-merchant-id"
key = "your-merchant-key"
params = {
'pid': pid,
'type': 'alipay', # alipay / wxpay
'out_trade_no': order_id,
'notify_url': notify_url,
'return_url': return_url,
'name': f'API额度充值 ${amount}',
'money': str(amount),
}
# 生成签名(易支付的签名方式各平台可能不同,以文档为准)
sign_str = '&'.join(f'{k}={v}' for k, v in sorted(params.items())) + key
params['sign'] = hashlib.md5(sign_str.encode()).hexdigest()
params['sign_type'] = 'MD5'
# 构建支付 URL
pay_url = EP_API + '?' + '&'.join(f'{k}={v}' for k, v in params.items())
return pay_url
第三步:写回调服务
@app.route('/pay/epay/notify', methods=['GET', 'POST'])
def epay_notify():
"""易支付回调"""
data = request.args if request.method == 'GET' else request.form
pid = data.get('pid')
trade_no = data.get('trade_no')
out_trade_no = data.get('out_trade_no') # 你的订单号
money = data.get('money')
trade_status = data.get('trade_status')
sign = data.get('sign')
# 验签:排除 sign 和 sign_type 后排序拼接 + key
sign_params = {k: v for k, v in data.items()
if k not in ('sign', 'sign_type')}
sign_str = '&'.join(f'{k}={v}' for k, v in sorted(sign_params.items())) + KEY
expected = hashlib.md5(sign_str.encode()).hexdigest()
if sign != expected:
return 'sign error'
if trade_status != 'TRADE_SUCCESS':
return 'fail'
# 查找订单,加额度(与 V免签 示例相同逻辑)
process_topup(out_trade_no, float(money))
return 'success'
易支付的注意事项
- 验签必须做:回调不验签等于裸奔,任何人都可以伪造请求给自己加额度
- 订单唯一性:
out_trade_no必须在你的系统里唯一,防止重复充值 - 回调幂等:易支付可能多次回调同一个订单,需要先查订单状态,已处理的直接返回
success - HTTPS:回调地址必须 HTTPS,且能被公网访问
方案四:官方 API(企业资质)
如果你有企业营业执照,可以直接对接支付宝和微信支付的官方 API。这是最正规的方案,费率最低(0.6%),但开发和审核门槛最高。
| 步骤 | 说明 |
|---|---|
| 1. 注册支付宝商户 | 需要营业执照、对公账户 |
| 2. 开通「当面付」或「电脑网站支付」 | 获取 APPID、商户私钥、支付宝公钥 |
| 3. 接入支付宝 SDK | pip install alipay-sdk-python |
| 4. 微信支付同理 | 需要服务号或商户号 |
开发对接逻辑和上述易支付类似——创建订单 → 返回支付链接 → 接收异步通知。只是 SDK 和签名方式不同。
完整的充值系统架构
综合以上方案,一个完整的自助充值系统的推荐架构:
充值页面前端示例
<!-- topup.html - 最简充值页面 -->
<!DOCTYPE html>
<html>
<head><title>API 额度充值</title></head>
<body>
<h2>充值中心</h2>
<div>
<label>令牌名称:</label>
<input type="text" id="tokenName" placeholder="你的令牌名">
</div>
<div>
<label>充值金额(USD):</label>
<select id="amount">
<option value="5">$5 - 约 35 元</option>
<option value="10">$10 - 约 70 元</option>
<option value="20">$20 - 约 140 元</option>
<option value="50">$50 - 约 350 元</option>
</select>
</div>
<button onclick="createOrder()">去支付</button>
<script>
async function createOrder() {
const tokenName = document.getElementById('tokenName').value;
const amount = document.getElementById('amount').value;
if (!tokenName) return alert('请输入令牌名称');
const resp = await fetch('/pay/create', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({token_name: tokenName, amount: parseFloat(amount)})
});
const data = await resp.json();
// 跳转到支付页面(易支付模式)
// window.location.href = data.pay_url;
// 或者展示收款码(V免签模式)
alert(`订单号: ${data.order_id}\n请转账 ${amount} 元并备注订单号`);
}
</script>
</body>
</html>
安全注意事项
回调验签(最重要)
无论用哪种方案,回调接口必须验签。不验签的后果:攻击者直接 POST 你的回调地址 {"amount": 99999, "order_id": "fake"} 就能无限充值。
# 验签示例(通用模式)
def verify_sign(data, secret, received_sign):
# 1. 排除 sign 字段,按 key 排序
sign_params = {k: v for k, v in data.items() if k != 'sign'}
# 2. 拼接 key=value&key=value + secret
raw = '&'.join(f'{k}={v}' for k, v in sorted(sign_params.items()))
raw += secret
# 3. MD5 或 HMAC
expected = hashlib.md5(raw.encode()).hexdigest()
return hmac.compare_digest(expected, received_sign)
防止重复回调
支付平台可能多次回调同一个订单。处理前先检查订单状态:
order = db.get_order(order_id)
if order['status'] == 'paid':
return 'success' # 已处理,直接返回成功
金额校验
回调金额必须与创建订单时的金额一致:
if abs(callback_amount - order_amount) > 0.01:
return 'amount mismatch', 400
订单号规则
订单号必须全局唯一,建议格式:TOPUP-{日期}-{随机串}。
IP 白名单
如果支付平台提供回调 IP 白名单,加上它:
EPAY_IPS = ['1.2.3.4', '5.6.7.8']
if request.remote_addr not in EPAY_IPS:
return 'unauthorized ip', 403
与 new-api 用户系统的衔接
new-api 的额度是绑定在令牌上的。充值回调里需要做的是:
- 根据订单里的
token_name找到令牌 - 更新
remain_quota字段 - 记一条充值日志(可选)
如果你的 new-api 使用 SQLite,最简单的做法是直接操作数据库:
def topup_token(token_name, amount_usd):
conn = sqlite3.connect(NEW_API_DB)
token = conn.execute(
'SELECT id, remain_quota FROM tokens WHERE name=?', (token_name,)
).fetchone()
if not token:
raise ValueError(f'Token {token_name} not found')
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()
如果是 MySQL,替换为对应的 SQL 即可。
注意:直接操作数据库时要确保 new-api 不会同时写入同一条记录。SQLite 的写锁是进程级的,建议在 new-api 低峰期操作,或者通过管理 API 间接更新(如果 new-api 版本支持)。
不同场景的推荐方案
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 自用 / 家人 | 人工充值 | 零成本,没几次操作 |
| 小团队(<20人) | 人工充值 | 每月收一次钱,手动加额度 |
| 对外服务(<100用户) | V免签 | 零手续费,一台旧手机搞定 |
| 对外服务(>100用户) | 易支付 / 官方 API | 稳定性更重要,值得付费 |
| 正规商业运营 | 支付宝/微信官方 API | 最低费率,最可靠 |
系列导航
系列导航
- (一)认识 new-api——新一代 AI API 管理网关
- (二)安装部署——从零搭建 new-api
- (三)渠道配置——接入各类模型提供商
- (四)令牌与用户管理
- (五)计价模型与成本控制
- (六)监控、日志与告警
- (七)生产环境部署与安全加固
- (八)进阶——多实例、负载均衡与 SSO
- (九)接入国内支付——支付宝、微信与自动充值(本文)
- (十)接入国际支付——Stripe、PayPal 与海外收银
实操清单
- 确定支付方案(人工 / V免签 / 易支付 / 官方API)
- 如用 V免签:准备安卓手机,安装 APP,配置回调地址
- 如用易支付:注册账号,获取商户 ID 和密钥
- 部署回调服务(Python Flask 或其他)
- 配置 Nginx/Caddy 反代回调地址,确保公网可达 + HTTPS
- 创建订单数据库(SQLite 记录订单状态)
- 编写充值页面(HTML + JS,调用 /pay/create)
- 实现 new-api 加额度逻辑(数据库操作或 API 调用)
- 全额测试:扫码支付 → 确认回调 → 确认额度到账
- 测试防重复回调(模拟多次 POST 同一订单)
- 测试验签失败场景(确认返回错误而非加额度)
- 配置回调 IP 白名单(如有)