ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

代付、卡密、聚合支付源码实战:从环境搭建到对账上线

代付、卡密、聚合支付源码实战:从环境搭建到对账上线 简介这套源码面向电商支付场景的开发者与商家聚焦淘宝天猫虚拟卡密代付、京东中石油充值及聚合支付三类业务可支持卡密店铺协议回调实现支付后自动发货。包内共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, statusUNUSED return card_no, card_hash def redeem_card(card_no: str, db): 核销卡密原子操作 card_hash hashlib.sha256(card_no.encode()).hexdigest() # 用UPDATE ... WHERE statusUNUSED保证原子性 affected db.execute( UPDATE cards SET statusUSED, used_atNOW() WHERE card_hash%s AND statusUNUSED, (card_hash,) ) if affected 0: raise ValueError(卡密无效或已使用) return True参数说明batch_no是批次号建议用日期加渠道码比如20250101JDface_value是面额单位分。核销时用UPDATE ... WHERE statusUNUSED这一条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, encodingutf-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里显式指定utf8mb4Redis用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应用IDwx1234567890各通道不同别混用merchant_id商户号1900000109跟app_id配对api_key接口密钥32位字符串不要提交到Gitnotify_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 fkey{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 statusPAID WHERE order_no%s AND statusPENDING, (order_no,) ) return {code: SUCCESS, msg: OK}逻辑说明先验签防止伪造再用pay_notify表的唯一索引做幂等重复回调直接返回成功最后更新订单状态WHERE statusPENDING保证只更新一次。参数说明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_typestatus和created_at上方便按业务和日期查询。灰度上线时我习惯先把新系统跟老系统并行跑一段时间用影子流量验证。具体做法是新系统接收真实请求但不真正扣款只记录日志和比对结果。跑一周后看差异率低于万分之一再切正式流量。切的时候按用户ID哈希分批切先切1%观察24小时没问题再切10%、50%、100%。最后说一个具体技巧回调日志一定要存原始报文。我吃过亏通道说回调成功了我说没收到双方扯皮。后来在回调接口第一行就把request.get_data()原样存到日志表包括请求头和请求体。再遇到争议直接拿日志说话。这个习惯帮我省了无数扯皮时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表