对接农业银行支付(微信和支付宝)的总结(三)

最后总结下农行支付的几个需要注意的地方:

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,就是那么难~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容