环境/框架:windows7+Intellij Idea+jdk8+tomcat+Spring
支付类型:扫码支付模式二(统一下单接口)
只是希望少一点人踩同样坑的列表(未完):
测试接口问题:4月初开始写的时候看到开发文档的最佳实践-支付验收一节,一直以为测试时需要走先沙盒路径,结果报的错和这个帖子(微信支付公众号支付提示验证签名失败、报错“请调用getsignkey生成沙箱密钥”)一模一样,查完参数又查签名,始终找不到原因。后来百度了几篇教程才突然发现:用统一下单接口的都没有提到沙盒……马上试了下正式的unifiedorder接口、果然一次就拿到二维码链接……再后来总算给账号让登公众平台,结果找了半天似乎也只能配置扫码支付模式一的测试url。所以虽然明知会污染对账单,为了赶进度、目前还是直接拿正式付款的账号和接口进行开发测试的。
appid:微信支付需要提前申请商户号
mch_id
和公众账号IDappid
,这是所有请求的必带参数,商户号无歧义,但一个企业可能会同时有企业号corpid和公众号openid,样子也都是以wx
2个字符开头,填错可能报“mchid和appid不符”。http返回没有消息体:记得检查是否漏了必填参数。微信只要正常收到请求,http response status code就都是
200 ok
这一点上也真是省力…、client只需解析body string。但遇到一次消息体为null的异常,检查到最后发现是因为读取数据库账号id表时出了问题,漏掉了appid根本没写进xml。-
签名校验问题
微信支付文档中关于签名计算流程写的挺明白的,因为是Java没有直接例程、抄起来就自己写了、结果还是遇到了一下细节问题:Checksum需要用UTF-8:开始时图省力调用了别人写的md5 util方法、结果里面使用了系统默认编码(tomcat在windows下是GBK),而用Idea直接跑static main时用的却是utf-8(猜测是自动检测了源文件编码)。最终现象是明明单元测试时sign生成方法和微信的签名验证工具一致、一放到tomcat跑起来就签名失败、调试很久才找到原因。所以后来整理了下相关的utf-8配置问题、写在了这篇。
除NULL外,值为空字串的参数也要剔除:微信返回的xml里可能包含whitespace node,比如
<sub_mch_id></sub_mch_id>
,而我当时使用的默认的Jackson xml mapper将它解析成了length=0的空string""
,拼接signStr时就变成了...&sub_mch_id=&...
,导致校验失败。网上说的“一定要确认参数名称和大小写”确实要注意,但另外一些“有中文就不行”、“凡string都要包在CDATA内”等,似乎是没什么大关系的。
-
补一段签名计算代码吧:
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);
}
-
退款
- 商户证书相关写在了这篇
-
返回结果格式 :申请退款和查询退款的返回结果里有很多
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>
开发文档阅读:不是很重要但是真挺让人抓狂的一点,微信支付开发文档网站风格设计完全一样、内容却像是根据不同支付方式各自维护的,比如请比较:
安全规范:
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
百度来的真的很容易走错啊。