微信支付代码分析报告:第5~10步与支付回调
外卖1
适用对象:已了解 Java 方法调用和基本 HTTP 请求,希望对照时序图理解微信小程序支付的学习者。整理日期:2026年10月25日。
阅读目标:区分商户下单、预支付标识、后端二次签名、客户端调起收银台及服务端通知。本报告基于用户提供的代码进行静态分析,保留讨论中的澄清;未执行真实支付或验证线上配置。源码节选、解释性改写和补充示例分别标明。
核心结论
图中步骤按系统之间的交互划分,不是一个步骤对应一个 Java 方法。post() 发送第5步的下单请求,也接收第6步的微信响应。PayNotifyController 主要接收第13步通知,并调用业务层完成第14步。取得 prepay_id 或返回支付参数,都不代表用户已经付款。
原始时序图与代码定位

| 图中步骤 | 交互方向与任务 | 源码定位 |
|---|---|---|
| 4:申请微信支付 | 小程序向商户后端申请支付参数 | OrderController.payment();PUT /user/order/payment |
| 5:调用微信下单接口 | 商户后端向微信创建预支付交易单 | pay() → jsapi() → post() → execute() |
| 6:返回预支付交易标识 | 微信返回 prepay_id,商户读取响应 | post() 接收响应,pay() 解析 |
| 7:将组合数据再次签名 | 商户后端生成调起支付签名 | pay() 构造签名串并计算 paySign |
| 8:返回支付参数 | 商户后端把五项参数交给小程序 | JSONObject → OrderPaymentVO → Result.success() |
| 9:用户确认支付 | 用户表达支付意愿、小程序执行交互 | 前端按钮与事件处理,不是后端独立接口 |
| 10:调起微信支付 | 小程序调起微信收银台 | wx.requestPayment() 或 uni-app 对应封装 |
| 11~12:返回并显示支付结果 | 微信客户端向小程序回调、前端展示 | requestPayment 的 success / fail 回调 |
| 13:推送支付结果 | 微信后台向商户发送通知 | PayNotifyController.paySuccessNotify() |
| 14:更新订单状态 | 商户业务层更新数据库 | OrderServiceImpl.paySuccess() → orderMapper.update() |
第11步客户端结果与第13步服务端通知是两条不同路径,不能假定服务端通知一定在前端显示结果后才发生。前端回调用于界面反馈,订单支付状态应以后端通知或查单结果为准。
规则依据:微信支付:小程序调起支付及回调说明。
支付入口:从第4步进入第5步
OrderController 类级路径为 /user/order,方法级路径为 /payment,HTTP 方法为 PUT,完整接口为 PUT /user/order/payment。@RequestBody 将 JSON 请求体反序列化为 OrdersPaymentDTO。DTO 包含 orderNumber 和 payMethod;当前业务方法未按 payMethod 分支处理支付方式。
源码节选:控制器将请求交给业务层
1 |
|
源码节选:业务层准备下单参数
1 | Long userId = BaseContext.getCurrentId(); |
BaseContext 取得当前登录用户在本系统中的 ID,userMapper 查询其用户记录,取出微信 openid。userId 是本系统标识;openid 是用户在对应小程序 AppID 下的微信标识。传给工具类的四项分别是商户订单号、元为单位的金额、商品描述与付款人 openid。
当前源码将金额写死为 0.01 元,没有读取订单实际金额。金额常量宜用 new BigDecimal(“0.01”) 避免从 double 构造的精度问题;真实业务应由后端根据订单确定金额。
第5步:组装参数并调用微信下单接口
pay() 先调用本地 jsapi();jsapi() 构造 JSON,再调用本地 post();真正跨网络发送请求发生在 HTTP 客户端 execute()。创建 JSONObject、HttpPost 本身不会发送请求。
源码节选:jsapi() 构造请求体
1 | JSONObject jsonObject = new JSONObject(); |
| 字段 | 来源 | 作用 |
|---|---|---|
| appid | WeChatProperties | 发起交易的小程序标识 |
| mchid | WeChatProperties | 收款商户号 |
| description | 业务层实参 | 本例为“苍穹外卖订单” |
| out_trade_no | ordersPaymentDTO.orderNumber | 关联商户业务订单 |
| notify_url | WeChatProperties | 第13步微信发送支付通知的商户地址,不是小程序跳转页面 |
| amount.total | total 元乘以100并转整数 | 以分为单位的整数金额 |
| amount.currency | 固定 CNY | 人民币 |
| payer.openid | 查询到的用户记录 | 指明对应小程序下的付款人 |
金额示例:0.01 元 × 100 = 1 分;12.50 元 × 100 = 1250 分。setScale(2, ROUND_HALF_UP) 先保留两位小数,intValue() 再转整数;intValue() 本身会截掉小数部分。规范的两位小数人民币金额乘以100后已经是整数。
请求体结构示例,非真实交易数据
1 | { |
JSAPI 常量对应 https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi,使用 POST。该接口创建预支付交易单并返回调起支付所需的标识。
接口依据:微信支付:JSAPI/小程序下单。
源码核心调用节选:请求头配置未全部列出
1 | CloseableHttpClient httpClient = getClient(); |
- getClient():创建配置好签名、验签能力的 HTTP 客户端。
- new HttpPost(url):构造目标 POST 请求。
- Accept: application/json:希望接收 JSON;Content-Type: application/json:声明请求体是 JSON。
- new StringEntity(body, “UTF-8”):用 UTF-8 将 JSON 字符串放入请求体。
- execute(httpPost):发送下单请求,并等待取得微信 HTTP 响应。
getClient() 使用 withMerchant(mchid, mchSerialNo, merchantPrivateKey) 配置商户号、商户证书序列号及商户私钥;使用 withWechatPay(wechatPayCertificates) 配置微信支付平台证书。源码所用的 WechatPayHttpClientBuilder 负责请求签名和响应验签,因此 post() 没有手写第一次签名算法。
第6步:微信返回 prepay_id,商户读取响应
成功响应示意
1 | { |
第6步的返回方是微信后台,接收方是商户系统。post() 不生成 prepay_id,它负责接收并读取微信的响应。响应中的 prepay_id 表示可以用于调起支付的预支付交易标识,不能作为已付款凭证。
源码节选:读取响应并在本地返回
1 | String bodyAsString = EntityUtils.toString(response.getEntity()); |
response.getEntity() 取得 HTTP 响应体,EntityUtils.toString() 将其读取为字符串。随后沿 post() → jsapi() → pay() 的本地调用链返回。
源码节选:pay() 解析预支付标识
1 | String bodyAsString = jsapi(orderNum, total, description, openid); |
JSON.parseObject() 把 JSON 字符串转换成对象;getString(“prepay_id”) 按字段取出标识。若未取得该字段,工具类不会执行二次签名,而会返回解析后的原始响应对象。
| 返回类型 | 方向 | 含义 |
|---|---|---|
| HTTP 响应 | 微信后台 → 商户系统 | 图中的第6步 |
| Java 方法 return | post() → jsapi() → pay() | 商户内部传递响应数据,不是新的跨系统交互 |
发送与接收的对应关系
1 | jsapi() 调用 post() |
因此,“jsapi() 调用 post() 是第5步”和“post() 接收第6步响应”可以同时成立。execute() 跨越发送和等待过程,方法边界不等于时序图步骤边界。
| 编号 | 产生方 | 用途 |
|---|---|---|
| out_trade_no | 商户系统 | 关联自己的业务订单 |
| prepay_id | 微信后台 | 供客户端调起支付,取得它不代表付款完成 |
| transaction_id | 微信后台 | 标识微信支付交易,在后续支付结果等数据中取得 |
第7步:商户后端为调起支付生成第二次签名
第一次签名用于商户后端调用微信下单接口,由 SDK 处理;第二次签名用于小程序调起支付,由 pay() 计算。二次签名不是把第一次签名结果再签一遍。
源码节选:生成时间戳及随机字符串
1 | String timeStamp = String.valueOf(System.currentTimeMillis() / 1000); |
currentTimeMillis() 是毫秒级 Unix 时间,除以1000转成秒,再转为字符串;randomNumeric(32) 生成32位数字字符组成的随机字符串,用于增加请求随机性。
源码节选:构造四行待签名数据
1 | ArrayList<Object> list = new ArrayList<>(); |
四项按小程序 AppID、时间戳、随机字符串、package 的值排列。第四项必须是 prepay_id=xxx,不能只使用 xxx。每项末尾都有换行符,最后一项也不例外。
解释性改写:签名串的等价结构
1 | String signMessage = appid + "\n" |
规则依据:微信支付:小程序调起支付签名。
源码节选:签名与Base64编码
1 | byte[] message = signMessage.getBytes(); |
- getBytes():把签名串转成字节。源码使用默认字符集,可明确写为 getBytes(StandardCharsets.UTF_8)。
- Signature.getInstance(“SHA256withRSA”):选择签名算法。
- PemUtil.loadPrivateKey():读取商户 API 私钥;initSign():初始化签名。这里不是使用 API v3 对称密钥。
- update(message):输入待签名数据;sign():计算签名字节。
- Base64 编码:把签名字节转成便于 JSON 传输的字符串,作为 paySign。Base64 不是加密,数字签名也不是把支付参数加密成密文。
源码节选:生成五项前端参数
1 | JSONObject jo = new JSONObject(); |
appid 参与签名,但原生 wx.requestPayment() 不需要单独传入 appId;调用运行在对应的小程序环境中。签名时的小程序 AppID 必须与下单及实际调起支付的小程序一致。前端应使用后端返回的原始时间戳、随机字符串及 package,不能重新生成。
第8步:转换为 VO 并返回小程序
源码节选:检查特定错误并封装VO
1 | if (jsonObject.getString("code") != null |
成功时,工具类返回的 jsonObject 已包含五项调起支付参数,而不是最初仅包含 prepay_id 的微信响应。失败时可能返回微信错误对象;当前业务层只专门处理 ORDERPAID,没有覆盖全部下单失败情况。
toJavaObject() 按匹配字段转为 OrderPaymentVO。四个同名字段直接映射;package 与 packageStr 名字不同,需要手动 setPackageStr()。Java 的 package 是关键字,不能直接用作普通字段名。
| 工具类JSON | 接口VO | 微信前端API |
|---|---|---|
| timeStamp | timeStamp | timeStamp |
| nonceStr | nonceStr | nonceStr |
| package | packageStr | package |
| signType | signType | signType,本例RSA |
| paySign | paySign | paySign |
控制器返回 Result.success(orderPaymentVO),Spring 将返回对象序列化成 JSON 发给小程序。此时只是支付参数准备完成,还没有因此发生扣款。
第9~10步:用户交互与客户端调起支付
第9步是用户和界面的交互,不是后端独立方法。在商户页面点击“去支付”表示支付意愿;在微信收银台输入密码或使用指纹确认付款,发生在第10步调起收银台之后。
压缩包 project-rjwm-weixin-uniapp/pages/order/index.vue 第97行附近,将“去支付”按钮绑定到 payOrderHandle()。对应 index.js 第133行附近,当前启用代码调用 submitOrderSubmit(params),成功后直接跳转 /pages/order/success。api.js 中 submitOrderSubmit() 对应 POST /order/submit。
压缩包当前启用逻辑节选
1 | submitOrderSubmit(params).then(res => { |
这条启用链路是提交业务订单后跳转“下单成功”页面,没有在此调用后端 payment 接口或拉起微信收银台。按钮文字“去支付”和页面跳转本身不能证明支付已经接通。
同一 index.js 第161~205行附近保留了一段被注释的旧支付逻辑:uni.requestPayment() 写死 signType: ‘MD5’,读取 params.package,并通过旧 payOrder 接口取得数据。它与当前后端的 RSA、VO 字段 packageStr、PUT /user/order/payment 不匹配,不能直接解除注释后视为接通。
补充示例:与当前后端匹配的原生小程序调用,非现有启用源码
1 | // payParams 是 PUT /user/order/payment 响应中的业务数据。 |
左侧 package 是微信 API 的固定参数名,右侧 packageStr 是后端 VO 字段。signType 应使用后端返回的 RSA。uni-app 使用 uni.requestPayment() 对应封装时,核心支付参数也要按上述关系映射。
调用支付 API 后,微信客户端调起收银台,用户完成支付或退出后触发相应回调。fail 不只表示取消,也可能是签名或参数错误。success 回调主要用于前端反馈,不应直接替代后端订单支付状态确认。
接口依据:微信支付:小程序调起支付。
第13~14步:PayNotifyController 接收通知并更新订单
PayNotifyController 主要接收图中第13步。类上的 /notify 与方法上的 /paySuccess 拼成 /notify/paySuccess,由微信后台调用。第5步的 notify_url 应指向该接口的实际可访问地址;本次未核验具体配置值和网络可达性。
源码节选:接收支付通知
1 |
|
- readData(request):通过 request.getReader() 逐行读取微信通知的请求体。
- decryptData(body):从 resource 中取出 ciphertext、nonce、associated_data,使用配置中的 API v3 密钥和 AesUtil 解密业务数据。
- 解析明文 JSON:取得商户订单号 out_trade_no 和微信支付交易号 transaction_id。当前代码记录 transactionId,没有将其传入业务层持久化。
- orderService.paySuccess(outTradeNo):进入第14步更新业务订单。
- responseToWeixin(response):返回 HTTP 200 及 SUCCESS JSON,确认本次通知已处理。这是对第13步通知请求的响应,不是第11步给小程序的支付结果。
源码节选:第14步更新订单
1 | public void paySuccess(String outTradeNo) { |
getByNumber() 按商户订单号查询记录,然后按数据库主键更新:订单状态为待接单、支付状态为已支付、结账时间为当前时间。当前代码没有在这里设置支付方式,虽然方法注释提及支付方式;应以实际赋值语句为准。
所提供 Controller 展示了读取、解密和状态更新,没有在该类内展示通知签名验证、交易状态和金额核对、重复通知幂等控制。是否由其他层实现,本次资料无法确认;本文不将教学片段视为已经完整验证的生产支付实现。
已确认的实现差异与阅读边界
| 观察 | 源码证据 | 影响 |
|---|---|---|
| 金额固定为一分钱 | payment() 中 new BigDecimal(0.01) | 不代表已经按实际订单金额收款 |
| 旧前端支付调用被注释 | 压缩包 pages/order/index.js | 当前启用链路只提交订单并跳转 |
| 签名类型不匹配 | 旧前端MD5,后端RSA | 需要按当前后端参数对接 |
| 预支付字段名不同 | JSON package,VO packageStr | 前端映射为 package: payParams.packageStr |
| 错误处理有限 | 只专门检查ORDERPAID | 其他失败响应不能当作成功参数 |
| 客户端与后台结果分离 | requestPayment 与通知 Controller 是不同入口 | 成功页面不是数据库已支付状态的替代证据 |
以上为静态观察,没有修改业务代码,没有执行登录、下单、扣款或退款。示例值不是真实交易数据,报告不包含私钥、API 密钥或访问令牌。
资料范围与来源
- 后端教学文件:F:/sky-take-out/资料/day08/微信支付功能代码/ 下的 OrderController.java、OrderServiceImpl.java、OrderService.java、OrderMapper.java、OrderMapper.xml、PayNotifyController.java。
- 用户补充工具类:D:/idea21workplace/sky-take-out/sky-take-out/sky-common/src/main/java/com/sky/utils/WeChatPayUtil.java。
- DTO / VO:D:/idea21workplace/sky-take-out/sky-take-out/sky-pojo/src/main/java/com/sky/ 下的 dto/OrdersPaymentDTO.java 与 vo/OrderPaymentVO.java。
- 小程序压缩包:F:/sky-take-out/微信小程序源代码/mp-weixin.zip。核对其 pages/order/index.js、pages/order/index.vue、pages/api/api.js、pages/order/success.vue 及 utils/request.js。
- 原始时序图:用户提供的 C:/Users/wzh/Desktop/图片1.png,已嵌入本文。
规则参考:JSAPI/小程序下单;小程序调起支付签名;小程序调起支付与回调。
资料来自不同目录和前端压缩包,不能自动视为同一版本的完整工程。本报告按实际可见代码描述,并明确列出不匹配处。代码行号可能随学习注释和后续编辑变化,定位时优先按类名、方法名和接口路径。



