最后总结下农行支付的几个需要注意的地方:
1.退款接口
由于是同步接口,一般返回的状态都是"受理成功",而不是"交易成功"。
农行文档(6.6的第6点)清晰描述道:
"若交易成功,则商户可以取得交易结果对象的其他属性来进行后续的作业。
注意:GetKeyValue("ReturnCode")返回 0000 可能代表退款成功,也可能代表退款受理成功。
还要看GetKeyValue("ErrorMessage")返回的中文信息是“交易成功”还是“受理成功”。"
意味着用户的退款状态需要由业务方主动查询退款结果才更新,对用户而言有一定的延迟。
(钱到帐了,但是订单和支付状态未变)
而且没有第三方退款流水号iRspRef和退款时间HostDate/HostTime等值,这样意味着我们必须轮询农行方才行。
2.查询退款接口
成功的示例见下:只返回了平台退款流水号iRspRef和退款时间OrderDate/OrderTime
,其中状态的判断不是文档里描述的05--已退款。
另外,有些字段名可能前后会携带空格字符。
{
"PayTypeID":"AliRefund",
"OrderNo":"R0621061B0155736016065",
"OrderDate":"2021/06/10",
"OrderTime":"16:07:02",
"RefundAmount":"0.01",
"Status":"04",
"iRspRef":"6AECEP01160423007350",
" MerRefundAccountNo":"19015601040024091",
" MerRefundAccountName":"浙江学海教育科技有限公司",
"SplitAccInfoItems":[
]
}
最大的坑当属PayTypeID了,如果你只操作了支付宝的退款,又怎么能确定微信等退款返回的是何值。
所以我在程序里是根据contains()包含关系来判断是否为退款。
这里的不确定性,也带来了很多未知的bug。
农行文档(附录二-响应码一览表的com.abc.pay.client.ebus.QueryOrderRequest)对PayTypeID写道:
ImmediatePay:直接支付
PreAuthPay:预授权支付
DividedPay:分期支付
AgentPay:授权支付
Refund:退款
DefrayPay:付款
PreAuthed:预授权确认
PreAuthCancel:预授权取消
从初步的联调结果来看,这里的词典遗漏不少。
最后贴出我对接查询交易结果的示例:
/**
* 返回代码.
* 0000-代表成功
*/
String returnCode = json.GetKeyValue(AbcBankConfig.RETURN_CODE);
/**
* 返回信息
*/
String errorMessage = json.GetKeyValue(AbcBankConfig.ERROR_MESSAGE);
if (AbcBankConfig.RC_SUCCESS.equalsIgnoreCase(returnCode)) {
String orderDecodeStr = Base64Code.Decode64(json.GetKeyValue("Order"));
JSONObject orderObject = com.alibaba.fastjson.JSON.parseObject(orderDecodeStr);
//格式: YYYY/MM/DD
String txDate = orderObject.getString("OrderDate").replaceAll("/", "-");
//格式:HH:MM:SS
String txTime = orderObject.getString("OrderTime");
//01-未支付;02-无回应;03-微信和支付宝支付成功;04-农行清算或支付成功;05-已退款;07-授权确认成功;00-授权已取消;99-失败;
String status = orderObject.getString("Status");
String payTypeID = orderObject.getString("PayTypeID");
//PayTypeID 根据交易类型判断是退款还是支付
// AliRefund-支付宝退款
// WeiXinRefund-微信退款
if (payTypeID.contains(AbcBankConfig.PayType.PAY_TYPE_REFUND)) {
//退款处理
} else {
//支付处理
}
}
3.查询退款结果和查询支付结果,是共用一个接口,这里的关键入参OrderNo,前者应该传平台退款流水号,后者应该传入平台支付流水号。
当回调出现故障时,我们必须调用主动查询接口来更新支付状态,所以我们需要知道第三方支付流水号,
也就是说,查询字段QueryDetail必须是1,不能填写0(不会返回第三方支付流水号)。等于0的场景是用不上的,不明白农行的设计意图何在?
4.退款接口的入参,OrderNo是平台支付流水号,NewOrderNo是平台退款流水号。
如果OrderNo传值错误,会报错说“无此账单”。
{
"TrxResponse":{
"ReturnCode":"2307",
"ErrorMessage":"无此账单!",
"TrxType":"Refund",
"OrderNo":"006210B6101501522608N",
"NewOrderNo":"R0621061B0155736016065",
"TrxAmount":"0.01",
"BatchNo":"",
"VoucherNo":"",
"HostDate":"",
"HostTime":"",
"iRspRef":""
}
}
这里,特别指出,不是放在第一点一起说,是因为字段的命名让我们容易差生错觉。
5.支付回调接口
HTTP的POST方式,这里获取入参的方式比较特别,放着请求体不用,
偏偏要通过表单和链接传递参数,让你程序必须使用getParameter来接收报文。
public class Notify{
public String abcRefundNotifyRes(HttpServletRequest request, HttpServletResponse response) {
String msg = request.getParameter("MSG");
//而我们一般对接微信和其他银行的接收入参是如下写法
String xmlResult = IOUtils.toString(request.getInputStream(),
request.getCharacterEncoding());
}
}
6.农行对接文档的吐槽
有一个不足之处是,缺少请求和响应的示例报文。导致我们写程序只能走一步调试一步。
还有就是在总体设计的时候,不去注重描述协议的规范和格式,甚至把他们自认为有用的UML贴出来给你看。
有这个功夫,不是应该提供一个SDK,让接入方直接引入,然后Builder模式一把就可以发送报文,
易于接入才是王道,偏要让接入方去反编译他的class文件。
然后你提出不要jar中的固化打印一些无用的调试日志,他们会跟你说,那是你自己的个性化需求,需要自己实现。
我想去掉不打印日志,都没得开关,让接入方如何不去“个性化”实现。。。(日志不是slf4j打印,
且是自己单独写服务器的某文件中,不携带任何业务信息,就算采集到ELK,也根本不能帮助排查问题。)
7.最后想要吐嘈的是他们的验签方法。
有两个地方需要尤为注意:
第一、编码格式,并不全是utf-8;
发送json格式请求的时候是utf-8,验证签名却应该是gbk。支付回调报文的验签却又不同,应该是gb2312.
第二、支付回调的报文必须采用他们给的XMLDocument.java才验证通过。
这一块,如果不给出示例代码,对接的过程中会踩不少坑!!
8.最后梳理下他们的报文协议
https+证书,发http采用HttpClient框架,读取证书文件在应用启动后,
保存在应用内存缓存起来,后期只需要根据account账户来缓存里取即可。
既然要使用https,就必须初始化农行的根证书abc.truststore,取得javax.net.ssl.SSLContext后赋值给HttpClient的http连接池。
发送报文前,必须要使用商户私钥(使用密码读取pfx文件,取出私钥内容)加密,计算出签名。
接收报文的时候,使用农行的支付平台证书TrustPay.cer进行加密,对比两者的签名是否一致。
9.没有SDK的痛苦!!
写道这里,有没有一种感觉,需要理解透这些协议,以及字段的含义,才能够开始接入联调。农行给你整一堆的jsp和html页面,以及jar包等着你去反编译呢~~
提供一个官网可下载的sdk,以及spring boot版本的sample,就是那么难~~
网友评论