简介:这套源码面向电商支付场景的开发者与商家,聚焦淘宝天猫虚拟卡密代付、京东中石油充值及聚合支付三类业务,可支持卡密店铺协议回调,实现支付后自动发货。包内共2000个文件,以1412个js脚本、209个html页面、117个css样式、111个json配置为主,另含131个md说明、2个sql建表脚本及少量docx、pptx文档,压缩包约75.33MB,前后端结构完整,便于二次开发与部署调试。目前已有157人学习下载。需注意系统仅完成天猫代付与京东中石油模块,京东中石化、比心、快手小店仅有名称未开发;天猫模块附带ck软件与使用教程,中石油模块需自行研究。整体适合具备一定支付系统基础、希望快速搭建卡密代付与聚合支付平台的开发者参考。
1. 代付、卡密、聚合支付:三套源码到底在解决什么生意问题
你在淘宝天猫下单,结算时选了“找人代付”,朋友点开链接用微信付了钱,订单状态实时变成“已付款”——这背后跑的就是 淘宝天猫代付系统 。你在京东买了张加油卡,收到一串卡密,去加油站圈存时系统核销成功——这是 京东油卡卡密系统 。你的小商城同时接了微信、支付宝、云闪付,用户选哪个都能付,对账时统一在一个后台看——这是 聚合支付系统 。三套源码,三个场景,但底层逻辑高度重合:都是围绕“订单—支付—回调—对账”这条链路做文章。热搜里“聚合支付”“源码”“淘宝天猫”“京东”这几个词反复出现,说明需求真实存在,但大多数人卡在同一个地方:拿到源码跑不起来,或者跑起来了不敢上生产。这篇把我自己踩过的路讲清楚,从环境搭建到回调验签到对账文件解析,每一步都给可复现的命令和参数。
2. 三套系统的技术底座:订单、支付通道、回调与对账
2.1 代付系统的核心状态机
代付系统跟普通支付最大的区别在于:付款人和下单人不是同一个。淘宝天猫代付的流程是——买家A创建订单,选择“找人代付”,系统生成一个代付链接和代付单号;被委托人B打开链接,用自己的支付账户完成付款;支付成功后,平台通过异步回调通知订单系统,订单状态从“待代付”变为“已付款”。
这里面最关键的是 状态机设计 。我一般会把代付单的状态定义为这几个: INIT (已创建)、 PENDING (等待付款)、 PAID (已付款)、 EXPIRED (已过期)、 REFUNDED (已退款)。状态流转必须是单向的,不允许从 PAID 回到 PENDING 。很多源码翻车就翻在这里——回调重复触发时没有做幂等,导致订单状态被反复改写。
# 代付单状态机核心逻辑(简化版)
from enum import Enum
class PayOrderStatus(Enum):
INIT = "INIT"
PENDING = "PENDING"
PAID = "PAID"
EXPIRED = "EXPIRED"
REFUNDED = "REFUNDED"
# 合法状态流转表
VALID_TRANSITIONS = {
PayOrderStatus.INIT: [PayOrderStatus.PENDING],
PayOrderStatus.PENDING: [PayOrderStatus.PAID, PayOrderStatus.EXPIRED],
PayOrderStatus.PAID: [PayOrderStatus.REFUNDED],
PayOrderStatus.EXPIRED: [],
PayOrderStatus.REFUNDED: [],
}
def transition(current, target):
if target not in VALID_TRANSITIONS.get(current, []):
raise ValueError(f"非法状态流转: {current} -> {target}")
return target
这段代码的逻辑很直白:用枚举锁死所有可能的状态,用字典定义合法流转路径。参数说明—— current 是当前状态, target 是目标状态,任何不在白名单里的跳转直接抛异常。实际项目中我会再加一层数据库乐观锁,用 version 字段防止并发更新。
2.2 卡密系统的加密与核销
京东油卡卡密系统的核心是两件事: 生成时加密存储 , 核销时原子扣减 。卡密本质上是一串有面额的兑换码,生成时用AES加密后入库,核销时解密比对并标记已使用。
常见做法是卡密分两段:前8位是批次号,后16位是随机串。批次号用于批量管理和对账,随机串用于唯一性校验。存储时只存哈希值(比如SHA256),不存明文——这样即使数据库泄露,卡密也不会被直接盗用。
import hashlib
import secrets
def generate_card(batch_no: str, face_value: int):
"""生成一张卡密,返回明文和哈希"""
random_part = secrets.token_hex(8) # 16位随机串
card_no = f"{batch_no}{random_part}"
card_hash = hashlib.sha256(card_no.encode()).hexdigest()
# 入库: card_hash, face_value, status='UNUSED'
return card_no, card_hash
def redeem_card(card_no: str, db):
"""核销卡密,原子操作"""
card_hash = hashlib.sha256(card_no.encode()).hexdigest()
# 用UPDATE ... WHERE status='UNUSED'保证原子性
affected = db.execute(
"UPDATE cards SET status='USED', used_at=NOW() "
"WHERE card_hash=%s AND status='UNUSED'",
(card_hash,)
)
if affected == 0:
raise ValueError("卡密无效或已使用")
return True
参数说明: batch_no 是批次号,建议用日期加渠道码,比如 20250101JD ; face_value 是面额,单位分。核销时用 UPDATE ... WHERE status='UNUSED' 这一条SQL完成原子扣减,返回影响行数为0就说明卡密已经被用过或者不存在。这个设计比“先查再改”安全得多,高并发下不会出现同一张卡被核销两次的情况。
2.3 聚合支付的路由与对账
聚合支付系统要解决的核心问题是: 一笔订单来了,走哪个通道 。常见策略有几种——按费率优先(选费率最低的)、按成功率优先(选近期成功率最高的)、按权重轮询(按配置比例分流)。我一般会做一个简单的路由引擎,把通道配置放在数据库里,支持热更新。
对账是聚合支付最容易被忽视的环节。每天凌晨,系统需要下载各通道的对账文件,跟本地订单逐笔比对,找出“本地成功但通道失败”“通道成功但本地失败”“金额不一致”这三类差异。对账文件格式各通道不同,有的是CSV,有的是定长文本,解析时要特别注意编码和分隔符。
import csv
from decimal import Decimal
def reconcile(local_orders: dict, channel_file: str):
"""对账核心逻辑"""
diffs = []
with open(channel_file, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
order_no = row['order_no']
channel_amount = Decimal(row['amount'])
local = local_orders.get(order_no)
if not local:
diffs.append(('CHANNEL_ONLY', order_no, channel_amount))
elif local['amount'] != channel_amount:
diffs.append(('AMOUNT_MISMATCH', order_no,
local['amount'], channel_amount))
else:
local_orders[order_no]['reconciled'] = True
# 剩下的就是本地有但通道没有的
for order_no, order in local_orders.items():
if not order.get('reconciled'):
diffs.append(('LOCAL_ONLY', order_no, order['amount']))
return diffs
这段代码用 Decimal 处理金额,避免浮点误差——这是血泪经验,用float对账迟早出问题。 local_orders 是以订单号为key的字典, channel_file 是通道对账文件路径。返回的 diffs 列表包含三类差异,后续可以入库告警或自动冲正。
3. 从零跑通一套聚合支付源码:环境、配置与联调
3.1 环境准备与依赖安装
拿到一套聚合支付源码,第一步不是急着改代码,而是把环境跑起来。我一般用Docker Compose编排,把MySQL、Redis、应用服务放在一个网络里,避免“在我机器上能跑”的玄学问题。
# docker-compose.yml 核心片段
version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: pay123456
MYSQL_DATABASE: payment
ports:
- "3306:3306"
volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
redis:
image: redis:7-alpine
ports:
- "6379:6379"
app:
build: .
ports:
- "8080:8080"
depends_on:
- mysql
- redis
environment:
DB_HOST: mysql
REDIS_HOST: redis
参数说明:MySQL用8.0版本,字符集建议在 init.sql 里显式指定 utf8mb4 ;Redis用7-alpine够轻量;应用服务的 depends_on 只保证启动顺序,不保证服务就绪,生产环境要加健康检查。 init.sql 里放建表语句和初始通道配置。
启动命令就一行: docker-compose up -d 。起来之后用 docker-compose logs -f app 看日志,如果看到“Started Application”就说明服务起来了。常见翻车点是MySQL连接超时——应用启动太快,MySQL还没初始化完。解决办法是在应用里加重试逻辑,或者用 wait-for-it.sh 脚本。
3.2 支付通道配置的关键参数
聚合支付源码里,通道配置是最容易配错的地方。我整理了一张必调参数表:
| 参数名 | 说明 | 典型值 | 坑点 |
|---|---|---|---|
| channel_code | 通道标识 | WXPAY_01 | 必须与代码里枚举一致 |
| app_id | 应用ID | wx1234567890 | 各通道不同,别混用 |
| merchant_id | 商户号 | 1900000109 | 跟app_id配对 |
| api_key | 接口密钥 | 32位字符串 | 不要提交到Git |
| notify_url | 异步回调地址 | https://your.domain/notify/wx | 必须公网可达 |
| sign_type | 签名类型 | MD5/RSA2 | 与通道要求一致 |
配置写完后,先用通道提供的沙箱环境测一笔。微信支付有沙箱,支付宝也有。测试时重点看三件事:下单是否返回支付链接、回调是否收到、验签是否通过。验签失败最常见的原因是 api_key 配错或者签名串拼接顺序不对——每个通道的签名规则都不一样,必须对着文档逐字核对。
3.3 回调验签与幂等处理
回调是支付系统里最脆弱的一环。通道会重复推送回调,网络抖动会导致回调丢失,恶意用户可能伪造回调。所以回调接口必须做三件事: 验签 、 幂等 、 返回正确响应 。
from flask import Flask, request
import hashlib
app = Flask(__name__)
@app.route('/notify/wx', methods=['POST'])
def wx_notify():
data = request.json
# 1. 验签
sign = data.pop('sign')
raw = '&'.join(f'{k}={v}' for k, v in sorted(data.items()))
raw += f'&key={WX_API_KEY}'
expected = hashlib.md5(raw.encode()).hexdigest().upper()
if sign != expected:
return {'code': 'FAIL', 'msg': 'sign error'}
# 2. 幂等:用订单号做唯一索引,重复插入会失败
order_no = data['out_trade_no']
try:
db.execute(
"INSERT INTO pay_notify (order_no, raw) VALUES (%s, %s)",
(order_no, str(data))
)
except DuplicateKeyError:
return {'code': 'SUCCESS', 'msg': 'OK'} # 已处理过,直接返回成功
# 3. 更新订单状态
db.execute(
"UPDATE orders SET status='PAID' WHERE order_no=%s AND status='PENDING'",
(order_no,)
)
return {'code': 'SUCCESS', 'msg': 'OK'}
逻辑说明:先验签,防止伪造;再用 pay_notify 表的唯一索引做幂等,重复回调直接返回成功;最后更新订单状态, WHERE status='PENDING' 保证只更新一次。参数说明: WX_API_KEY 是微信商户平台的API密钥, out_trade_no 是商户订单号。返回给通道的响应必须是通道要求的格式,否则通道会一直重推。
4. 代付与卡密系统落地时最容易翻车的五个地方
4.1 回调地址配了内网IP,通道推不过来
现象 :本地测试回调正常,部署到服务器后订单一直显示“待付款”,通道后台显示“回调失败”。
原因 : notify_url 配的是 http://192.168.x.x/notify ,通道服务器在公网,根本访问不到内网地址。
解决 :回调地址必须是公网可达的域名或IP,并且用HTTPS。开发阶段可以用内网穿透工具临时映射,但生产环境必须用正式域名。另外检查防火墙是否放行了回调端口。
4.2 卡密生成用了随机数但没做唯一性校验
现象 :批量生成10万张卡密,导入时发现有几张重复,导致核销时出现“一码多用”。
原因 :用了 random 模块而不是 secrets ,随机性不够;或者生成后没有对数据库做唯一索引。
解决 :用 secrets.token_hex() 生成随机串,数据库对 card_hash 字段加唯一索引。批量生成时用 INSERT IGNORE 或 ON DUPLICATE KEY UPDATE 跳过重复。
4.3 代付链接没有设置过期时间
现象 :用户创建代付单后忘了付款,三个月后朋友点开链接还能付,但商品早就下架了。
原因 :代付单没有过期机制,状态一直是 PENDING 。
解决 :创建代付单时设置 expire_at 字段,比如30分钟。用一个定时任务扫描过期订单,把状态改为 EXPIRED 。付款时先检查 expire_at ,过期直接拒绝。
4.4 对账文件解析时编码搞错导致金额错乱
现象 :对账时发现大量金额不一致,但逐笔核对又没问题。
原因 :通道对账文件是GBK编码,代码用UTF-8读取,中文乱码导致字段错位。
解决 :先确认通道对账文件的编码格式,用 chardet 检测或直接问通道技术支持。读取时显式指定编码,解析后用 Decimal 转换金额。
4.5 聚合支付路由没有降级策略
现象 :某个通道故障,所有走该通道的订单全部失败,但系统没有自动切换。
原因 :路由引擎只按配置分流,没有健康检查。
解决 :给每个通道加健康检查,连续失败N次后自动降级,把流量切到备用通道。降级阈值我一般设5次失败或成功率低于80%持续1分钟。
5. 把三套系统串起来:统一订单中心与灰度上线技巧
三套系统单独跑通之后,下一步是串起来。我一般会做一个 统一订单中心 ,所有代付单、卡密单、聚合支付单都往这里写,用一个 biz_type 字段区分业务类型。这样做的好处是对账统一、报表统一、风控统一。
-- 统一订单表核心字段
CREATE TABLE unified_order (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(64) NOT NULL UNIQUE,
biz_type ENUM('DAIFU', 'KAMI', 'AGGREGATE') NOT NULL,
channel_code VARCHAR(32),
amount DECIMAL(12,2) NOT NULL,
status VARCHAR(16) NOT NULL,
ext JSON,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_biz_status (biz_type, status),
INDEX idx_created (created_at)
);
ext 字段用JSON存各业务的扩展信息,比如代付单存代付人ID,卡密单存批次号。索引建在 biz_type+status 和 created_at 上,方便按业务和日期查询。
灰度上线时,我习惯先把新系统跟老系统并行跑一段时间,用 影子流量 验证。具体做法是:新系统接收真实请求但不真正扣款,只记录日志和比对结果。跑一周后看差异率,低于万分之一再切正式流量。切的时候按用户ID哈希分批切,先切1%,观察24小时,没问题再切10%、50%、100%。
最后说一个具体技巧: 回调日志一定要存原始报文 。我吃过亏,通道说回调成功了,我说没收到,双方扯皮。后来在回调接口第一行就把 request.get_data() 原样存到日志表,包括请求头和请求体。再遇到争议,直接拿日志说话。这个习惯帮我省了无数扯皮时间。
希望帮到你。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/weixin_30846347/article/details/166797078



