☰
Flask订单支付系统实战:支付宝微信接入与回调状态机设计
2026/9/30 9:27:25 网站建设 项目流程

简介:基于Python和Flask框架搭建的订单支付系统,完整集成了支付宝支付与微信支付两大主流支付渠道,面向计算机相关专业学生,适用于毕业设计、课程设计及项目初期演示,解决支付流程对接、订单状态管理与项目部署等典型问题。压缩包内共28个文件,以14个Python源码文件为核心,覆盖Flask应用配置、支付路由与订单业务逻辑;3个pem证书文件用于支付平台密钥通信,3个HTML页面提供前端交互,另有Markdown部署文档与说明文档,整体包体仅34KB,结构紧凑、便于快速查阅。目前已有122人学习下载,项目代码经测试运行成功,并获得导师指导认可,答辩评审分达到95分。从部署文档入手可快速搭建运行环境,对照源码即可理解支付宝与微信支付的接入流程,也可在此基础上扩展订单管理、退款对账等功能,适合作为高分毕设参考或Python进阶学习。

1. 从毕设到生产:订单支付系统的选型与落地边界

很多同学拿到"Python+Flask的订单支付系统"这类毕设项目,第一反应是跑通代码、截图答辩,但很少有人关心一件事:支付宝和微信支付的接入方式完全是两套体系,Flast 作为轻量框架,如何同时兼容异步回调、签名验签和订单状态机?这个项目给我的启发是,它不只是一个 demo,而是一个“麻雀虽小五脏俱全”的支付中台雏形——用户下单、选择支付渠道、发起预支付、接收异步通知、修改订单状态、处理退款,每个环节都有真实业务场景。适合正在做课设、毕设的人,也适合想快速了解支付对接流程的企业开发人员。下面我根据项目源码和部署文档,拆解订单支付系统从数据表设计到线上部署的完整链路,给出可直接套用的代码和参数。

2. 订单与支付记录的数据模型:先想清楚状态机,再写表

支付系统的核心不是搞一个好看的支付按钮,而是订单状态的一致性。这个项目里与支付直接相关的表是否合理?我打开数据库脚本,最值得讲的是orders和payment_records两张表之间的联动关系。订单表保存业务层面的信息,支付表保存渠道交互的流水,两者通过order_sn关联。

2.1 订单表字段设计思路

订单表至少包含:order_sn(业务单号,自己生成,不要用数据库自增 ID,因为支付回调和第三方接口对单号长度和格式有要求)、user_id、total_amount(单位是分,避免浮点误差)、pay_type(1=支付宝,2=微信)、status(状态机)、created_at、paid_at等。total_amount必须用整数分存储,这是做支付项目的基本常识。如果沿用 Decimal 或 Float 算金额,等到退款对账时一定会后悔。

CREATE TABLE `orders` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_sn` varchar(32) NOT NULL COMMENT '业务订单号', `user_id` int(11) NOT NULL, `total_amount` int(11) NOT NULL COMMENT '总金额,单位分', `pay_type` tinyint(4) DEFAULT NULL COMMENT '1-支付宝 2-微信', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0-待支付 1-已支付 2-已取消 3-已退款', `created_at` datetime NOT NULL, `paid_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_sn` (`order_sn`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里的关键是status用整型而不是字符串。因为支付回调可能并发到达,虽然我们不控并发,但状态字段越简单越好判断。order_sn唯一索引是必须的,否则回调重复时容易产生脏数据。我一般会生成订单号 = 时间戳 + 用户ID + 随机数,长度保持在 32 字符内,因为支付宝要求商户订单号最长 64 字符,微信要求 32 以内的数字或字母。

2.2 支付流水表:记录每一次渠道交互

支付记录表需要记录设备信息、第三方交易号、回调报文、异步通知处理状态。这个项目里的payment_records表设计得比较聪明:它把支付宝和微信统一到一张表中,通过channel区分,而不是给每个支付方式建一张表。字段包括:id、order_sn、channel(alipay/wechat)、transaction_id(第三方支付单号)、raw_data(回调原始报文 JSON)、notify_status(0=未处理 1=处理成功 2=处理失败)、created_at。

CREATE TABLE `payment_records` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_sn` varchar(32) NOT NULL, `channel` varchar(16) NOT NULL, `transaction_id` varchar(64) DEFAULT NULL, `raw_data` json DEFAULT NULL COMMENT '回调原始请求体或报文', `notify_status` tinyint(4) DEFAULT '0', `created_at` datetime NOT NULL, PRIMARY KEY (`id`), KEY `idx_order_sn` (`order_sn`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

JSON 类型的raw_data字段很实用——排查回调问题时可以直接把支付宝的原始通知存进去,不用翻日志。在旧版本 MySQL 5.7 以下不支持 JSON,可以改用 TEXT 字段,但查询性能会差些。课程设计阶段可以放宽,生产环境建议用独立的回调日志表。

2.3 订单状态机:防止重复回调把订单打回待支付

状态机的定义要显式,不能直接在业务代码里写 if status == 1。我建议在配置里维护一个状态流转表:

当前状态允许的下一状态触发条件
0 待支付1 已支付 / 2 已取消用户支付成功 / 超时未支付
0 待支付3 已退款管理员强制退款(无支付记录时)
1 已支付3 已退款用户申请退款并审核通过

在回调处理函数里,第一件事是查订单,第二件事就是判断当前状态是否等于待支付。如果订单已经是已支付,说明回调重复推送,直接返回成功标志给支付平台,不要再改数据库。Flask 里用装饰器封装一个状态校验函数比较优雅:

def update_order_status(order_sn, new_status): order = Order.query.filter_by(order_sn=order_sn).first() if not order: return False allowed = ORDER_STATUS_TRANSITIONS.get(order.status, []) if new_status not in allowed: # 重复回调或非法跳转,这里只记录日志,不抛异常 return False order.status = new_status if new_status == 1: order.paid_at = datetime.now() db.session.commit() return True

ORDER_STATUS_TRANSITIONS是一个字典,比如{0: [1,2], 1: [3]}。这样改状态时就能快速看出非法流转。项目的实际代码里可能没有这么封装,但你在做毕设时加入这个类,答辩时就是加分项。

3. Flask 项目结构与配置管理:把敏感信息挡在代码之外

这个项目的目录结构里有config.py、manage.py、pay_flask包、utils、keys等目录,看得出来是用 Flask 应用工厂模式组织的。很多毕设项目把数据库连接串、支付密钥直接写死在视图文件里,这个项目没有,值得按生产标准来看。

3.1 应用工厂与 Blueprint 拆分

pay_flask/__init__.py里多半是创建 app 和注册蓝图。建议把路由按业务拆成pay和order两个 Blueprint,而不是所有视图堆在一个文件里。下面是常见的工厂函数写法:

# pay_flask/__init__.py from flask import Flask from config import Config def create_app(config_class=Config): app = Flask(__name__) app.config.from_object(config_class) from pay_flask.views import order_bp, pay_bp app.register_blueprint(order_bp, url_prefix='/order') app.register_blueprint(pay_bp, url_prefix='/pay') return app

manage.py则负责启动入口,一般会加一个 CLI 命令初始化数据库:

# manage.py from flask.cli import FlaskGroup from pay_flask import create_app cli = FlaskGroup(create_app=create_app) if __name__ == '__main__': cli()

然后命令行python manage.py runserver就能跑起来。这里的FlaskGroup可以让你直接用 Flask 命令行插件,比如flask shell、flask db upgrade。注意:

  • create_app是 Flask 官方推荐的工厂函数写法,测试时只需传入不同配置类,比如TestingConfig。
  • Project 里的Pay_Flask-master目录名带版本号,部署时可以直接改名为Pay_Flask,避免路径里出现连字符导致 import 时找不到包。

3.2 配置分离与环境变量

config.py是配置的集中地。我建议把所有环境相关的键做成变量,而不是硬编码。下面这段配置适用于大多数 Flask 支付项目:

import os class Config: SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-key' SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \ 'mysql+pymysql://root:123456@localhost/pay_flask?charset=utf8mb4' SQLALCHEMY_TRACK_MODIFICATIONS = False # 支付宝配置 ALIPAY_APP_ID = os.environ.get('ALIPAY_APP_ID') ALIPAY_PRIVATE_KEY = os.environ.get('ALIPAY_PRIVATE_KEY') # 应用私钥 ALIPAY_PUBLIC_KEY = os.environ.get('ALIPAY_PUBLIC_KEY') # 支付宝公钥 ALIPAY_GATEWAY = 'https://openapi.alipay.com/gateway.do' # 微信支付配置 WECHAT_APP_ID = os.environ.get('WECHAT_APP_ID') WECHAT_MCH_ID = os.environ.get('WECHAT_MCH_ID') WECHAT_API_KEY = os.environ.get('WECHAT_API_KEY') WECHAT_NOTIFY_URL = os.environ.get('WECHAT_NOTIFY_URL')

有人可能觉得直接写在 config.py 里更方便,但毕设答辩时老师很容易问“如果代码上传 GitHub 怎么办”。README 里写.env,然后把.env加到.gitignore,这才是标准做法。这个项目里有.gitignore,但没有.env.example,建议你补一个示例文件,把变量名列清楚,不写真实值。

3.3 密钥文件管理

项目里有个keys目录,里面存放密钥文件,但注意密钥文件不能直接传进 Docker 镜像或推到公共仓库。常见做法是用utils里的封装函数读取文件内容:

# utils/crypto.py import os def read_key(name): key_path = os.path.join(os.path.dirname(__file__), '..', 'keys', name) with open(key_path, 'r') as f: return f.read().strip()

然后配置文件里写ALIPAY_PRIVATE_KEY = read_key('alipay_private.key')。这里有个细节:支付宝的 Java 常使用 PKCS8 格式密钥,Python 的 SDK 可以直接读取不带BEGIN RSA PRIVATE KEY头的纯字符串,也可以整段读取。如果报错,检查read_key()是否把换行符去掉了,有些 SDK 要求密钥字符串里必须包含换行符,去掉反而报错。

4. 支付宝支付接入:电脑网站支付与异步验签

支付宝支付在 Flask 中的对接,核心是统一下单、构造表单、处理异步通知、验签和回调返回。这个项目用的是支付宝电脑网站支付(alipay.trade.page.pay),用户在网页上点击支付跳转到支付宝收银台,Android 和 iOS 也能适用,只是 Uniapp 集成需要改用手机网站支付或者 App 支付,但后端签名逻辑一致。

4.1 生成支付表单的接口

下单接口接收订单号,构造支付宝参数,这里推荐使用python-alipay-sdk这个三方库。如果项目里没有,可以在requirements.txt里加上python-alipay-sdk>=3.3。关键代码如下:

# pay_flask/views/pay.py from flask import Blueprint, request, jsonify, render_template from alipay import AliPay from pay_flask import models from utils.crypto import read_key pay_bp = Blueprint('pay', __name__) def get_alipay(): return AliPay( appid=app.config['ALIPAY_APP_ID'], app_notify_url=None, # 浏览器跳转后展示的页面 app_private_key_string=read_key('alipay_private.key'), alipay_public_key_string=read_key('alipay_public.key'), sign_type='RSA2', # RSA2 对应 SHA256withRSA debug=False ) @pay_bp.route('/alipay/create', methods=['POST']) def alipay_create(): data = request.get_json() order_sn = data.get('order_sn') order = models.Order.query.filter_by(order_sn=order_sn).first() if not order: return jsonify(code=400, msg='订单不存在') alipay = get_alipay() order_string = alipay.api_alipay_trade_page_pay( out_trade_no=order_sn, total_amount=order.total_amount / 100, # 元,支付宝用元,金额转回 float subject='订单支付-{}'.format(order_sn), return_url='http://localhost:5000/pay/alipay/return', notify_url='http://your-domain.com/pay/alipay/notify' ) pay_url = 'https://openapi.alipay.com/gateway.do?' + order_string return jsonify(code=200, data={'pay_url': pay_url})

total_amount从分转元时一定要除以 100,且保留两位小数,否则支付宝会报“订单金额无效”。out_trade_no必须唯一,如果同一订单下单多次,建议相同订单号重新生成支付链接,覆盖旧的未支付订单。这样用户刷新页面不会生成多个未支付单。

4.2 异步通知验签与幂等处理

支付宝的异步通知是 POST 请求,带sign参数。验签后还需要判断trade_status,只有TRADE_SUCCESS或TRADE_FINISHED才是真正的支付成功。下面的代码是完整的回调处理:

@pay_bp.route('/alipay/notify', methods=['POST']) def alipay_notify(): data = request.form.to_dict() alipay = get_alipay() success = alipay.verify(data, data.pop('sign')) if not success: return 'failure' trade_status = data.get('trade_status') out_trade_no = data.get('out_trade_no') trade_no = data.get('trade_no') if trade_status in ('TRADE_SUCCESS', 'TRADE_FINISHED'): # 保存原始回调到支付记录表 record = models.PaymentRecord( order_sn=out_trade_no, channel='alipay', transaction_id=trade_no, raw_data=json.dumps(data), ) db.session.add(record) # 更新订单状态,注意幂等 if update_order_status(out_trade_no, 1): db.session.commit() # 这里可发送短信或通知用户 return jsonify({'code': 'SUCCESS'}) # 支付宝要求返回纯文本 success return 'failure'

注意两个细节:

  • verify方法内部会把sign_type、sign参数剔除,只拿剩余参数验签。自己手写验签时也记得不要包含sign本身。
  • 返回值必须是纯文本success(小写),不能是 JSON,不能带引号,否则支付宝会认为回调失败并不断重试,每 30 秒重试一次,最多 8 次,直接打爆你的业务日志。

调试阶段最容易犯的错是把return_url当成回调处理业务逻辑。return_url是用户付款后浏览器跳转的页面,它不一定能保证到达,不能在里面更新订单状态,只能用来展示“支付成功”。真正的状态更新必须在notify_url里。

4.3 支付宝沙箱环境

快速体验联调时使用支付宝开放平台沙箱账号。在沙箱里,网关地址是https://openapi.alipaydev.com/gateway.do,可以在Config里加一个ALIPAY_DEBUG=True,然后动态拼网关地址。沙箱应用不需要真实营业执照,下载支付宝开放平台密钥工具生成 RSA2 密钥即可。注意:新注册的沙箱应用的公钥模式,一定要把你的应用公钥填写到沙箱应用后台,再用支付宝公钥进行验证,很多同学做完下单后,回调一直验签失败,就是因为把应用公钥写成了支付宝公钥。

5. 微信支付接入:Native 与 JSAPI 双模式实现

微信支付的逻辑和支付宝不同,它采用 API v3 版本后支持证书和密钥两种方案,小程序支付走 JSAPI 模式,PC 网站走 Native 模式。本项目里同时支持支付宝和微信,那么微信支付建议兼容两种场景。

5.1 微信支付的统一下单与请求签名

微信支付 API v3 要求每个请求都带Authorization头,使用商户私钥对请求签名。项目keys目录里通常有apiclient_key.pem,这是微信支付商户 API 私钥,注意不要泄露。下面是用requests手动发请求的代码,避免引入太重的 SDK:

import json import time import requests from cryptography.hazmat.primitives import serialization def wechat_prepay(order_sn, total_fee, openid=None, trade_type='NATIVE'): url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/native' if trade_type == 'JSAPI': url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi' timestamp = str(int(time.time())) nonce_str = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789', k=32)) body = { 'appid': app.config['WECHAT_APP_ID'], 'mchid': app.config['WECHAT_MCH_ID'], 'description': '支付订单-{}'.format(order_sn), 'out_trade_no': order_sn, 'notify_url': app.config['WECHAT_NOTIFY_URL'], 'amount': {'total': total_fee, 'currency': 'CNY'} } if openid: body['payer'] = {'openid': openid} body_json = json.dumps(body, ensure_ascii=False) message = 'POST\n{}\n{}\n{}\n{}\n'.format(url, timestamp, nonce_str, body_json) signature = sign_message(message) headers = { 'Authorization': 'WECHATPAY2-SHA256-RSA2048 mchid="{}" nonce_str="{}" timestamp="{}" signature="{}" serial_no="{}"'.format( app.config['WECHAT_MCH_ID'], nonce_str, timestamp, signature, app.config['WECHAT_SERIAL_NO'] ), 'Content-Type': 'application/json', 'Accept': 'application/json' } resp = requests.post(url, data=body_json, headers=headers) return resp.json()

这个代码里sign_message是核心函数:

from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding def sign_message(message_str): with open('keys/apiclient_key.pem', 'rb') as f: private_key = serialization.load_pem_private_key(f.read(), password=None) signature = private_key.sign( message_str.encode('utf-8'), padding.PKCS1v15(), hashes.SHA256() ) return b64encode(signature).decode()

注意message换行拼接的格式:请求方法\nURL\n时间戳\n随机串\n请求体\n,最后还要一个换行。如果 signature 一直验签失败,就把请求体 JSON 的 key 顺序固定,推荐用字典保持插入顺序,不要用sort_keys=True,因为微信官方要求原始字符串就是请求体的字节流。

5.2 Native 模式返回 code_url 生成二维码

Native 下单成功返回code_url,前端用这个链接生成二维码,用户扫码后微信内部发起支付。返回的数据里code_url有效期 2 小时,过期后需要重新下单。建议在后端缓存 code_url,避免用户刷新页面重复创建订单:

if order.status == 0: result = wechat_prepay(order_sn, order.total_amount) code_url = result.get('code_url') # 将 code_url 存到订单表或 Redis 里,key=order_sn

前端生成二维码一般用qrcode库,这里不展开。注意微信 Native 支付不能返回支付链接让用户去浏览器打开,只能扫码。

5.3 微信支付回调解密与验签

微信支付 API v3 的回调与支付宝不同:回调请求头里有Wechatpay-Signature、Wechatpay-Timestamp、Wechatpay-Nonce,请求体是加密的resource对象。验签后还需要使用 APIv3 密钥解密ciphertext。完整流程如下:

@pay_bp.route('/wechat/notify', methods=['POST']) def wechat_notify(): headers = request.headers body = request.get_data() wechatpay_signature = headers.get('Wechatpay-Signature') wechatpay_timestamp = headers.get('Wechatpay-Timestamp') wechatpay_nonce = headers.get('Wechatpay-Nonce') serial_no = headers.get('Wechatpay-Serial') # 1. 验证签名 message = '{}\n{}\n{}\n{}\n'.format(wechatpay_timestamp, wechatpay_nonce, body.decode(), '') signature_bytes = base64.b64decode(wechatpay_signature) # 使用平台证书验证,这里略,见下面说明 # 2. 解密 resource = request.get_json().get('resource') ciphertext = base64.b64decode(resource.get('ciphertext')) associated_data = resource.get('associated_data').encode() nonce = resource.get('nonce').encode() api_v3_key = app.config['WECHAT_API_V3_KEY'].encode() # 32字节密钥 from cryptography.hazmat.primitives.ciphers.aead import AESGCM aesgcm = AESGCM(api_v3_key) plaintext = aesgcm.decrypt(nonce, ciphertext, associated_data) data = json.loads(plaintext) if data.get('trade_state') == 'SUCCESS': out_trade_no = data['out_trade_no'] transaction_id = data['transaction_id'] update_order_status(out_trade_no, 1) return {'code': 'SUCCESS', 'message': '成功'} return {'code': 'FAIL', 'message': '失败'}

验签部分需要下载微信支付平台证书,证书可以从接口获取一次后缓存在本地。代码示例因篇幅不展开,但要注意:微信支付的回调必须返回状态码 200,返回体是 JSON 时不能使用200以外的状态码。另外回调可能重复推送,这里在update_order_status中通过状态机保证幂等,如果订单状态不是待支付,直接返回成功,不做更新。

5.4 小程序支付 JSAPI 模式的差异

如果用于小程序,需要在前端调用wx.requestPayment。后端下单时要拿到用户的openid,这需要先调用code2Session接口。可以和 Native 模式共用同一个视图函数,根据是否传入openid判断是 Native 还是 JSAPI。前端参数:

wx.requestPayment({ timeStamp: data.timeStamp, nonceStr: data.nonceStr, package: data.package, // "prepay_id=xxx" signType: 'RSA', paySign: data.paySign });

这里的paySign是后端用预支付交易会话标识prepay_id生成的签名,格式和请求签名类似,只是package要拼接prepay_id=. 这里有一个高频坑:JSAPI 下单时trade_type='JSAPI'必须传openid,否则报INVALID_ARGUMENT。

6. 项目部署上线:从 Flask 开发服务器到 gunicorn + Nginx

之前我们已经把业务代码写好了,但 Flask 自带的开发服务器不支持并发,而且性能极低,正式部署需要使用 gunicorn 或 uwsgi。项目里的logs目录就是为了收集日志准备的,部署文档也强调要用进程守护工具。

部署场景推荐方式说明
课程设计演示python manage.py runserver局域网可用,仅演示
低并发个人项目gunicorn + Nginx2-4 个 worker,支持 HTTPS
中小型生产gunicorn + supervisor + Nginx进程崩溃自动拉起
容器化部署Docker + gunicorn配合 CI/CD 自动化

6.1 gunicorn 启动命令与 worker 数量

如果你的机器是 4 核 CPU,建议配置gunicorn -w 4 -b 127.0.0.1:8000 manage:app。manage:app是 Flask 应用入口,需要工厂函数返回 app 实例。如果是 Flask 2.x,也可以直接用app变量。

gunicorn manage:app -w 4 -k gthread --threads 8 --timeout 60 --log-level info --access-logfile logs/access.log --error-logfile logs/error.log

这里-k gthread是关键,因为支付回调里会用到 requests 请求第三方接口,如果是同步 worker,单个请求阻塞 2 秒就会拖慢整个 worker。使用 gthread 模式,每个 worker 可以开多个线程,适合 IO 密集型任务。但注意数据库连接池只能由一个线程独享,所以建议把SQLALCHEMY_ENGINE_OPTIONS配置为{'pool_size': 10, 'pool_recycle': 3600}。

6.2 Nginx 反向代理与 HTTPS 强制

Nginx 配置里最需要注意的是设置proxy_set_header,否则 Flask 获取不到客户端的真实 IP,同时支付回调需要公网可达,必须开放 443 端口。

server { listen 80; server_name your-domain.com; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name your-domain.com; ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }

配置完成后,需要检查notify_url是否是 HTTPS 的地址。支付宝和微信都要求回调链接为 HTTPS,如果是 HTTP 支付宝沙箱可以用http://,但正式环境会校验域名是否备案以及是否是 HTTPS。本地调试时,可以用ngrok或natapp做内网穿透,但只建议测试阶段用,生产不要依赖。

6.3 支付回调排查三板斧

上线后最容易出问题的就是异步通知,我把常见异常和排查手段放在一张表里:

现象可能原因排查命令 / 方法
支付宝一直WAIT_BUYER_PAY没有正确返回success查看 access.log 中回调请求的响应码,打印响应体
微信回调验签失败平台证书未及时更新写一个轮询接口定期刷新证书,或使用Wechatpay-V3签名校验库
订单已支付,但用户网页显示待支付return_url里没有处理业务前端根据notify_url触发订单状态查询接口
支付金额为 0.01 的测试单配置错误检查环境变量是否被覆盖,使用flask config命令
同步回调时二次刷新页面状态机不允许状态回退不参与状态更新,只做展示

最后再介绍一个调试技巧:在 Flask 视图函数中加一个app.logger.info(payment_data)日志,把回调的原始报文打印到文件。我通常在notify入口写一行日志,将request.get_data()和请求头全部拉到本地。观察微信重试时的请求头Wechatpay-Timestamp,如果连续几次时间戳间隔为 15 秒,说明微信认为是失败的,这时不要只盯业务逻辑,先看 Nginx 的 access log 中返回的状态码是否为 200 或 302。确实需要返回{'code':'FAIL'}时,微信会按一定策略重试,你可以主动返回失败观察重试,但超过 5 次会被标记为失败单,需要人工处理。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询