微信支付的坑

时间:2022-10-30 20:24:59

环境/框架:windows7+Intellij Idea+jdk8+tomcat+Spring

支付类型:扫码支付模式二(统一下单接口)

只是希望少一点人踩同样坑的列表(未完):

  1. 测试接口问题:4月初开始写的时候看到开发文档的最佳实践-支付验收一节,一直以为测试时需要走先沙盒路径,结果报的错和这个帖子(微信支付公众号支付提示验证签名失败、报错“请调用getsignkey生成沙箱密钥”)一模一样,查完参数又查签名,始终找不到原因。后来百度了几篇教程才突然发现:用统一下单接口的都没有提到沙盒……马上试了下正式的unifiedorder接口、果然一次就拿到二维码链接……再后来总算给账号让登公众平台,结果找了半天似乎也只能配置扫码支付模式一的测试url。所以虽然明知会污染对账单,为了赶进度、目前还是直接拿正式付款的账号和接口进行开发测试的。

  2. appid:微信支付需要提前申请商户号mch_id和公众账号IDappid,这是所有请求的必带参数,商户号无歧义,但一个企业可能会同时有企业号corpid和公众号openid,样子也都是以 wx 2个字符开头,填错可能报“mchid和appid不符”。

  3. http返回没有消息体:记得检查是否漏了必填参数。微信只要正常收到请求,http response status code就都是200 ok 这一点上也真是省力…、client只需解析body string。但遇到一次消息体为null的异常,检查到最后发现是因为读取数据库账号id表时出了问题,漏掉了appid根本没写进xml。

  4. 签名校验问题
    微信支付文档中关于签名计算流程写的挺明白的,因为是Java没有直接例程、抄起来就自己写了、结果还是遇到了一下细节问题:

    1. Checksum需要用UTF-8:开始时图省力调用了别人写的md5 util方法、结果里面使用了系统默认编码(tomcat在windows下是GBK),而用Idea直接跑static main时用的却是utf-8(猜测是自动检测了源文件编码)。最终现象是明明单元测试时sign生成方法和微信的签名验证工具一致、一放到tomcat跑起来就签名失败、调试很久才找到原因。所以后来整理了下相关的utf-8配置问题、写在了这篇

    2. 除NULL外,值为空字串的参数也要剔除:微信返回的xml里可能包含whitespace node,比如<sub_mch_id></sub_mch_id>,而我当时使用的默认的Jackson xml mapper将它解析成了length=0的空string "",拼接signStr时就变成了...&sub_mch_id=&...,导致校验失败。

    3. 网上说的“一定要确认参数名称和大小写”确实要注意,但另外一些“有中文就不行”、“凡string都要包在CDATA内”等,似乎是没什么大关系的。

    4. 补一段签名计算代码吧:

      public static String computeSign(Map<String, String> params, String key) {
      if (params == null || key == null)
      throw new NullPointerException("param map or key is null");
      String signSrc = params.entrySet().stream()
      .filter(e -> e.getValue() != null)
      .filter(e -> !(e.getValue().trim().length() == 0))
      .filter(e -> !"sign".equals(e.getKey()))
      .map(e -> e.getKey() + "=" + e.getValue())
      .sorted()
      .collect(Collectors.joining("&"));
      signSrc = signSrc + "&key=" + key;
      String sign = null;
      try {
      sign = compute("MD5", signSrc, UTF_8).toUpperCase();
      } catch (NoSuchAlgorithmException e) {
      LOGGER.error("computeSign", e);
      }
      return sign;
      }
      // checksum
      public static String compute(String checksumAlg, String src, Charset charSet)
      throws NoSuchAlgorithmException {
      final byte[] hashBytes = MessageDigest.getInstance(checksumAlg).digest(src.getBytes(charSet));
      return DatatypeConverter.printHexBinary(hashBytes);
      }
  5. 退款

    1. 商户证书相关写在了这篇
    2. 返回结果格式 :申请退款和查询退款的返回结果里有很多out_refund_no_$n(甚至coupon_refund_fee_$n_$m)这样带后缀的参数,初次看文档时整个人也是“$$”了。直到测试时收到的实际结果……我并没有很多年的经验里是第一次看到竟然有人会想到用改xml标签名的方式来表示一个列表的(设计一个复杂一点的element、甚至直接用attribute不好吗)。(总之因为强烈地repel)退款这块数据并没有很好处理……这里只是附贴一下结果样例(2017/04,省略了一些通用项):
      申请退款返回:

      <xml>
      <transaction_id><![CDATA[40007***76642]]></transaction_id>
      <out_trade_no><![CDATA[1492844-5410-000-002-929]]></out_trade_no>
      <out_refund_no><![CDATA[1493007-4988-0018]]></out_refund_no>
      <refund_id><![CDATA[50000***05548]]></refund_id>
      <refund_channel><![CDATA[]]></refund_channel>
      <refund_fee>1</refund_fee>
      <coupon_refund_fee>0</coupon_refund_fee>
      <total_fee>2</total_fee>
      <cash_fee>2</cash_fee>
      <coupon_refund_count>0</coupon_refund_count>
      <cash_refund_fee>1</cash_refund_fee>
      </xml>

      查询退款返回:

      <xml>
      <cash_fee><![CDATA[2]]></cash_fee>
      <out_refund_no_0><![CDATA[1493007-4988-0018]]></out_refund_no_0>
      <out_trade_no><![CDATA[1492844-5410-000-002-929]]></out_trade_no>
      <refund_account_0><![CDATA[REFUND_SOURCE_UNSETTLED_FUNDS]]></refund_account_0>
      <refund_channel_0><![CDATA[ORIGINAL]]></refund_channel_0>
      <refund_count>1</refund_count>
      <refund_fee>1</refund_fee>
      <refund_fee_0>1</refund_fee_0>
      <refund_id_0><![CDATA[50000***05548]]></refund_id_0>
      <refund_recv_accout_0><![CDATA[支付用户的零钱]]></refund_recv_accout_0>
      <refund_status_0><![CDATA[SUCCESS]]></refund_status_0>
      <refund_success_time_0><![CDATA[2017-04-24 12:18:26]]></refund_success_time_0>
      <total_fee><![CDATA[2]]></total_fee>
      <transaction_id><![CDATA[40007***76642]]></transaction_id>
      </xml>
  6. 开发文档阅读:不是很重要但是真挺让人抓狂的一点,微信支付开发文档网站风格设计完全一样、内容却像是根据不同支付方式各自维护的,比如请比较:
    安全规范:
    https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
    https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3
    统一下单:
    https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
    https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
    百度来的真的很容易走错啊。