Files
websopy-taro/.workbuddy/memory/2026-07-02.md
赵忠林 511e4b8ff3 feat(pay): 支付成功后自动跳转到已购产品页面
- 支付成功后显示成功提示
- 延迟1.5秒自动跳转到已购产品页面
- 处理跳转失败时回退到用户首页
- 优化用户支付完成后的使用体验
2026-07-02 22:40:08 +08:00

10 KiB
Raw Blame History

2026-07-02 工作日志

小程序扫码支付"获取订单失败"问题排查与修复

  • 问题:用户扫小程序码后进入小程序支付页,提示"获取订单失败"
  • 根因:后端 Spring Security 的 SecurityConfig 中未将支付相关接口加入白名单(permitAll),扫码进入时用户未登录态,请求 /api/app/subscription/detail-by-no/** 被 401 拦截
  • 修复:在 SecurityConfig.java 的 antMatchers 中添加了以下接口白名单:
    • /api/app/subscription/detail-by-no/** — 根据订阅号查询订单详情
    • /api/app/subscription/mp-prepay/** — 创建小程序预支付订单
    • /api/app/subscription/mp-confirm/** — 支付成功确认
    • /api/app/subscription/wx-notify/** — 微信支付回调通知
    • /api/wx-login/** — 小程序码生成等微信登录相关接口(之前只覆盖了 /api/shop/wx-login/**,漏掉了 /api/wx-login/**
  • 前端代码位置src/passport/pay/index.tsx,支付页面从 scene/subscriptionNo 获取订单号,调 detail-by-no 接口
  • 后端代码位置AppSubscriptionController.java@RequestMapping("/api/app/subscription"))WxLoginController.java@RequestMapping("/api/wx-login"))
  • 注意:生成小程序码时 envVersion 默认为 trial(体验版),上线前需改为 release

后续排查:后端白名单加了仍报"获取订单失败"

  • 二次根因:前端 src/passport/pay/index.tsxfetchDetail 存在逻辑 bug
    • request() 默认 returnRaw=false,响应拦截器对 code===0 的响应已自动拆包,只返回 data 部分(订阅对象)
    • fetchDetail 又检查 res?.code === 200 || res?.code === 0,而拆包后的 res(订阅对象)没有 code 字段
    • 因此条件永远为 false,走进 setErrorMsg 分支,显示"获取订单失败"
  • 修复:移除多余的 code 检查,request 成功即代表业务成功,直接使用返回的 data 对象;错误由 catch 处理
  • 附加getSubscriptionNo 加了 console.log 打印页面参数和解析结果,方便真机调试排查 scene 传入问题

五次排查getOpenId 返回 success:true 但 openid 为 undefined

  • 根因:前端 src/api/passport/wx-login/index.tsgetOpenId 函数错误地假设后端返回的是 { openid, unionid, session_key } 对象,直接取 res.data.openid;但后端 /wx-login/getOpenId 实际返回的是 LoginResult 结构 { access_token, user },真正的 openid 在 user.openid

  • 修复:更新 getOpenId 类型为 ApiResult<WxLoginResult>,从 res.data.user?.openid 提取 openid同时把 access_tokenuser 一起返回给调用方复用

  • 后续影响handlePay 中拿到 openid 后,因为 getOpenId 已经自动注册/登录(后端会注册新用户并签发 token理论上用户已经自动登录可能无需再调 loginByOpenId;但当前代码保留双保险

  • 根因handlePay 流程有 bug——调了 Taro.login() 获取 code但没有用 code 去换 openid直接从 Taro.getStorageSync('openid') 读取扫码用户可能没登录过storage 里没存)

  • 前端修复src/passport/pay/index.tsx

    1. 引入 getOpenIdloginByOpenId API
    2. handlePay 中:Taro.login()getOpenId(code) 换取 openid → 缓存 openid
    3. 尝试 loginByOpenId 自动登录获取 token如果用户未注册弹出引导登录对话框
    4. mp-prepay 请求也加了 returnRaw: true 和调试日志
  • 后端修复AppSubscriptionController.java

    1. mp-prepay:将 userId == null || !userId.equals(sub.getUserId()) 改为 userId != null && !userId.equals(sub.getUserId()),允许未登录用户发起支付
    2. mp-confirm:同理,未登录时跳过 userId 校验,用 effectiveUserId = userId != null ? userId : sub.getUserId() 替代原 userId
  • 修改点1src/passport/pay/index.tsxfetchDetail 改为 returnRaw: true,拿到接口完整响应(含 code/message/data手动判断业务状态码code === 0 || code === 200),不再依赖拦截器拆包

  • 修改点2fetchDetailgetSubscriptionNo 加了详细 console.log/console.error打印

    • 原始页面参数scene、subscriptionNo
    • 请求 URL 和当前 token
    • 接口完整返回 JSON
    • catch 中的 err.name/type/code/message/data
  • 修改点3src/utils/request.ts 响应拦截器去掉了 process.env.NODE_ENV === 'development' 条件限制,所有环境都打印日志,方便真机调试 Console 查看

六次排查getOpenId 返回 success:true 但 openid 为 null

  • 现象:前端 getOpenId 返回 success: true, access_token: 有值, user.userId: 35619,但 openid: null, unionid: null
  • 根因:后端 /wx-login/getOpenId 虽然通过微信 jscode2session 换到了 openid没有把 openid 设置到 UserParam
    • 后续 userService.getByOauthId(userParam) 查找用户时userParam.openid 为空,可能无法命中已有用户;
    • 新用户注册时走 addUser(userParam),而 addUser 只在 userParam.openid 非空时才会写 User.openid,所以新用户 openid 也为空;
    • 对于已存在但 openid 为空的老用户如手机号注册直接返回openid 仍然是 null。
  • 修复WxLoginController.java/getOpenId 方法):
    1. 从微信返回中拿到 openid/unionid 后,立即 userParam.setOpenid(openid)userParam.setUnionid(unionid)
    2. 用户已存在但 user.openid 为空时,把 openid/unionid 更新到数据库并返回最新 user
    3. 这样 LoginResult.user.openid 一定有值,前端就能正确获取并传给 mp-prepay

七次排查mp-prepay 报 SIGN_ERROR签名错误

  • 现象:前端调用 /api/app/subscription/mp-prepay/{id} 时,后端返回 code: 1, message: "微信支付服务异常: 微信错误码: SIGN_ERROR, 签名错误"
  • 排查
    1. 当前小程序 JSAPI 支付使用 WxNativePayUtil.getConfig 构建微信支付 Config模式为 RSAPublicKeyConfig(公钥模式),但生产环境 wechatpay-public-key-id 为空。
    2. 读取项目下所有证书文件,用 openssl 检查各证书对应的商户号,发现:
      • /wechat/websopy/apiclient_cert.pem:商户号 1557418831(与小程序无关联)
      • /wechat/10398/apiclient_cert.pem:商户号 1246610101(与小程序已关联,见截图)
    3. 配置中的 mch-id1246610101,但配置的 private-key-relative-path 指向 wechat/websopy/,导致证书与商户号不匹配,微信返回 SIGN_ERROR
    4. 检查 10398 目录证书与私钥匹配性:公钥哈希一致,证书序列号为 48749613B40AA8F1D768583FC352358E13EB5AF0
  • 修复
    1. 修改 WxNativePayUtil.java:将 RSAPublicKeyConfig(公钥模式)改为 RSAAutoCertificateConfig(自动证书模式),不再依赖 wechatpay-public-key-idwechatpay-cert-relative-path
    2. 修正 application-dev.ymlapplication-prod.yml
      • mch-id1246610101(保持不变,这是正确的)
      • merchant-serial-number48749613B40AA8F1D768583FC352358E13EB5AF0(对应 10398 目录证书)
      • private-key-relative-pathwechat/10398/apiclient_key.pem
      • wechatpay-cert-relative-path 置空(自动证书模式)
  • 待确认:当前保留的 api-v3-key: "zGufUcqa7ovgxRL0kF5OlPr482EZwtn9" 是否属于商户号 1246610101,需要用户在微信支付商户后台确认。如果仍报签名错误,需要替换为正确的 APIv3 密钥。
  • 待处理:生产环境需将 /Users/gxwebsoft/JAVA/websopy-java/src/main/resources/wechat/10398/apiclient_key.pem 上传到 /www/wwwroot/file.ws/wechat/10398/apiclient_key.pem,并确保该文件可被 Java 进程读取。之前错误上传 wechat/websopy/ 目录的证书给 1246610101 使用,这是 SIGN_ERROR 的真正原因。

八次排查SUB202607022056385942 提示"订阅不存在"

  • 现象:调用 /api/_app/subscription/check-status/SUB202607022056385942/api/_app/subscription/detail-by-no/SUB202607022056385942 均返回"订阅不存在"
  • 根因getOrderQRCodeUnlimited 接口WxLoginController.java 第 441 行)只生成小程序码,并不创建订阅记录。如果生成小程序码时没有先调用 subscribe 接口,subscriptionNo 就只是个随机字符串,没有对应的数据库记录
  • 修复(方案 1:新增 POST /api/app/subscription/generate-pay-qrcode 接口,在一个请求中完成:
    1. 创建订阅记录(复用 subscribe 的核心逻辑,写入数据库)
    2. subscriptionNo 调用 WxMiniprogramUtil.generateMiniprogramQrCode 生成小程序码
    3. 返回 subscriptionNo + Base64 图片
  • 关联修改
    • AppPayProperties.java:新增 miniAppSecret 字段(用于生成小程序码获取 access_token
    • application-dev.yml / application-prod.yml:添加 mini-app-secret 配置项(当前为占位符,需用户填写真实秘钥)
    • AppSubscriptionController.java mp-prepay:已改为允许跨用户支付(userId != null && !userId.equals(sub.getUserId())
    • AppSubscriptionController.java mp-confirm:已改为未登录时跳过 userId 校验
  • 下一步
    1. 到微信公众平台获取小程序 AppSecret,填写到 application-dev.ymlapplication-prod.ymlmini-app-secret 字段
    2. 重新构建部署后端
    3. 调用新接口生成支付小程序码:POST /api/app/subscription/generate-pay-qrcode Body: {"productId": 1, "subscriptionPeriod": "month", "envVersion": "trial"}
    4. 用返回的 subscriptionNo 测试 check-statusdetail-by-no 接口,确认订阅记录已存在

补充修复

  • AppSubscriptionController.javagenerate-pay-qrcode 接口中,switch (subscriptionPeriod)case "month": 空 fall through 触发 IDE 红色警告。已添加 // fall through to default 注释消除警告。