说实在的,写这篇博客完全是意料之外,毕竟封装SDK后,银行支付理论上讲就仅仅只是简单的调用加判断,但应用实际上生产后,居然发现支付失败会有多种原因,而不同的错误,其后续处理逻辑也不一样,所以这篇博客内容就是讲如何区分失败原因,以及后续应当如何处理。

3.4.1转账汇款指令提交为例,支付代码基本如下:(全部代码可见此处

/// <summary>
        /// 3.4.1	转账汇款指令提交
        /// </summary>
        public static void XFERTRNRQSample()
        {
            string tid = string.Format("{0:yyyyMMddHHmmss}_3.4.1", DateTime.Now);
            var rq = GetRequest<FOXRQ<V1_XFERTRNRQ, V1_XFERTRNRS>>();
            rq.SECURITIES_MSGSRQV1 = new V1_XFERTRNRQ
            {
                XFERTRNRQ = new XFERTRNRQ
                {
                    TRNUID = tid,
                    CLTCOOKIE = "123",
                    XFERRQ = new XFERRQ
                    {
                        XFERINFO = new XFERINFO
                        {
                            ACCTFROM = new ACCTFROM
                            {
                                ACCTID = mainAccountId,
                                NAME = mainAccountName
                            },
                            ACCTTO = GetACCTTO(3),
                            PURPOSE = string.Format("fkb_{0:yyMMddHHmmssfff}", DateTime.Now),
                            TRNAMT =  7.77m,
                            //DTDUE = DateTime.Now,//如果需要网银审核,在该日期之后如果还未审核,则支付请求过期
                            MEMO = "转账测试审核过期",
                        }
                    }
                }
            };
            //rq.SECURITIES_MSGSRQV1.XFERTRNRQ.TRNUID =  "20190130102306_3.4.1"; //过期 20190129151451_3.4.1
            var rs = client.Execute(rq);
            Console.WriteLine(rs.ResponseContent);
        }
        /// <summary>
        /// 获取转账账号 
        /// 0 普通转账、对公收款账号(行内)
        /// 1 兴业银行对私收款账号(行内)
        /// 2 普通转账、对公收款账号(跨行)
        /// 3 普通转账、对私收款账号(跨行)
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private static ACCTTO GetACCTTO(byte type)
        {
            switch (type)
            {
                case 0://普通转账、对公收款账号(行内)
                    return new ACCTTO
                    {
                        ACCTID = "117010100100107091",
                        INTERBANK = "Y",
                        LOCAL = "Y",
                        NAME = "test",
                    };
                case 1://兴业银行对私收款账号(行内)
                    return new ACCTTO
                    {
                        ACCTID = "622909117529613510",
                        INTERBANK = "Y",
                        LOCAL = "Y",
                        NAME = "小金人",
                    };
                case 2://普通转账、对公收款账号(跨行)
                    return new ACCTTO
                    {
                        ACCTID = "123455555",
                        INTERBANK = "N",
                        LOCAL = "Y",
                        NAME = "平安银行测试2222",
                        BANKDESC = "平安银行股份有限公司上海九江路支行",
                        CITY = "上海市"
                    };
                default://普通转账、对私收款账号(跨行)
                    return new ACCTTO
                    {
                        ACCTID = "6225885123771966",
                        INTERBANK = "N",
                        LOCAL = "Y",
                        NAME = "陈晨",
                        BANKDESC = "中国工商银行股份有限公司北京通州支行新华分理处",
                        CITY = "北京市"
                    };
            }
        }

其对应正确的支付结果响应报文内容一般是这样的

<FOX>
    <SIGNONMSGSRSV1>
        <SONRS>
            <STATUS>
                <CODE>0</CODE>
                <SEVERITY>INFO</SEVERITY>
            </STATUS>
            <DTSERVER>2019-04-25 18:23:49</DTSERVER>
        </SONRS>
    </SIGNONMSGSRSV1>
    <SECURITIES_MSGSRSV1>
        <XFERTRNRS>
            <TRNUID>000147_DCXJXTY1904100003_3</TRNUID>
            <STATUS>
                <CODE>0</CODE>
                <SEVERITY>INFO</SEVERITY>
            </STATUS>
            <XFERRS>
                <SRVRID>900079609784</SRVRID>
                <XFERINFO>
                    <ACCTFROM>
                        <ACCTID>117010100100000177</ACCTID>
                        <NAME>中国民族证券有限责任公司12</NAME>
                        <BANKDESC>兴业银行</BANKDESC>
                        <CITY>福州</CITY>
                    </ACCTFROM>
                    <ACCTTO INTERBANK="N" LOCAL="N">
                        <ACCTID>6225885123771966</ACCTID>
                        <NAME>陈晨</NAME>
                        <BANKDESC>中国工商银行股份有限公司北京通州支行新华分理处</BANKDESC>
                        <CITY>北京</CITY>
                    </ACCTTO>
                    <CHEQUENUM>0497343</CHEQUENUM>
                    <CURSYM>RMB</CURSYM>
                    <TRNAMT>278.00</TRNAMT>
                    <PURPOSE>FKB_DCXJXTY1904100003_000147</PURPOSE>
                    <DTDUE>2019-04-25</DTDUE>
                </XFERINFO>
                <XFERPRCSTS>
                    <XFERPRCCODE>PAYOUT</XFERPRCCODE>
                    <DTXFERPRC>2019-04-25 18:23:49</DTXFERPRC>
                    <MESSAGE>交易成功</MESSAGE>
                </XFERPRCSTS>
                <CLIENTREF>000147_DCXJXTY1904100003_3</CLIENTREF>
            </XFERRS>
        </XFERTRNRS>
    </SECURITIES_MSGSRSV1>
</FOX>

正确的响应结果包含了SIGNONMSGSRSV1.SONRS.STATUSSECURITIES_MSGSRSV1.XFERTRNRS.STATUS,以及SECURITIES_MSGSRSV1.XFERTRNRS.XFERRS.XFERPRCSTS,想想一个响应结果,需要判断三处内容,简直可怕!!!但可怕也没办法,你要对接兴业银行,你就必须得过这三道门!!!

下面我们开始讲怎么判断支付失败,从本质上讲,支付失败的原因可以简单粗暴的分为三类:

  • 业务数据错误,比如收款账号的信息(卡号、用户名等)不正确
  • 财务配置错误,比如银企直联的账号密码错误,付款账号错误等
  • 程序自身不足

对于业务数据错误,一般来说,需要用到银企直联的,往往都是涉及企业内部的各种业务审批流,而在对应业务系统中,从理论上讲,财务支付人员是不可能,也不应该具备直接修改收款账号之类的用户信息的(如果是能直接修改信息的,那一般都是直接走网银了,哪还需要银企直联),所以对于这类错误,财务往往是需要将单据审批退回或审批失败,然后由申请人员将对应信息修改正确后再重新提交。

目前实际遇到的业务数据错误响应有以下几种:

  • 账号格式错误,兴业银行的账号是不允许有空格的,所以安全起见,可以通过正则Regex.Replace(account, @"\s", string.Empty)将空格全部移除
<FOX>
    <SIGNONMSGSRSV1>
        <SONRS>
            <STATUS>
                <CODE>0</CODE>
                <SEVERITY>INFO</SEVERITY>
            </STATUS>
            <DTSERVER>2019-04-25 18:30:52</DTSERVER>
        </SONRS>
    </SIGNONMSGSRSV1>
    <SECURITIES_MSGSRSV1>
        <XFERTRNRS>
            <TRNUID>000147_DCXJXTY1904100003_4</TRNUID>
            <STATUS>
                <CODE>2002</CODE>
                <SEVERITY>ERROR</SEVERITY>
                <MESSAGE>账号格式错误</MESSAGE>
            </STATUS>
        </XFERTRNRS>
    </SECURITIES_MSGSRSV1>
</FOX>

财务配置错误则不同,一般来讲,这类错误只会发生在开发过程中,但偶尔会有些场景会导致该类异常出现,比如以下场景:在某个集团中,所有子公司都将账户授权挂靠在了某个子公司(实际银企直联申请公司)下面,这样通过一个兴业银行银企直联前置机,就可以进行所有子公司的支付业务,但假设某些原因某家子公司A取消了授权,但应用系统内却未及时删除相应配置时,在用A公司对应账号进行支付时,就有可能会提示以下响应内容

  • 付款账户不存在
<FOX>
    <SIGNONMSGSRSV1>
        <SONRS>
            <STATUS>
                <CODE>0</CODE>
                <SEVERITY>INFO</SEVERITY>
            </STATUS>
            <DTSERVER>2019-04-25 17:58:37</DTSERVER>
        </SONRS>
    </SIGNONMSGSRSV1>
    <SECURITIES_MSGSRSV1>
        <XFERTRNRS>
            <TRNUID>000147_DCXJXTY1904100003_1</TRNUID>
            <STATUS>
                <CODE>2006</CODE>
                <SEVERITY>ERROR</SEVERITY>
                <MESSAGE>付款账户不存在</MESSAGE>
            </STATUS>
        </XFERTRNRS>
    </SECURITIES_MSGSRSV1>
</FOX>

其它开发时常见的财务配置错误还有这些

  • 直联账号错误
<FOX>
    <SIGNONMSGSRSV1>
        <SONRS>
            <STATUS>
                <CODE>15502</CODE>
                <SEVERITY>ERROR</SEVERITY>
                <MESSAGE>密码已经锁定,请通知管理员解锁</MESSAGE>
            </STATUS>
            <DTSERVER>2019-04-09 15:51:44</DTSERVER>
        </SONRS>
    </SIGNONMSGSRSV1>
</FOX>
  • 直联账号无权限
<FOX>
    <SIGNONMSGSRSV1>
        <SONRS>
            <STATUS>
                <CODE>0</CODE>
                <SEVERITY>INFO</SEVERITY>
            </STATUS>
            <DTSERVER>2019-02-27 10:39:51</DTSERVER>
        </SONRS>
    </SIGNONMSGSRSV1>
    <SECURITIES_MSGSRSV1>
        <ICCONTRTRNRS>
            <TRNUID>20190227104032_3.12.5.1</TRNUID>
            <STATUS>
                <CODE>2005</CODE>
                <SEVERITY>ERROR</SEVERITY>
                <MESSAGE>对不起,操作员权限不足。</MESSAGE>
            </STATUS>
        </ICCONTRTRNRS>
    </SECURITIES_MSGSRSV1>
</FOX>
  • 账号未签约
<FOX>
    <SIGNONMSGSRSV1>
        <SONRS>
            <STATUS>
                <CODE>0</CODE>
                <SEVERITY>INFO</SEVERITY>
            </STATUS>
            <DTSERVER>2019-02-27 10:40:30</DTSERVER>
        </SONRS>
    </SIGNONMSGSRSV1>
    <SECURITIES_MSGSRSV1>
        <ICCONTRTRNRS>
            <TRNUID>20190227104112_3.12.5.1</TRNUID>
            <STATUS>
                <CODE>2373</CODE>
                <SEVERITY>ERROR</SEVERITY>
                <MESSAGE>(2373)账号117010100100000177无签约信息</MESSAGE>
            </STATUS>
        </ICCONTRTRNRS>
    </SECURITIES_MSGSRSV1>
</FOX>
  • 没有该项业务的授权书 (2019-06-26补充)
<FOX>
    <SIGNONMSGSRSV1>
        <SONRS>
            <STATUS>
                <CODE>0</CODE>
                <SEVERITY>INFO</SEVERITY>
            </STATUS>
            <DTSERVER>2019-06-20 17:13:29</DTSERVER>
        </SONRS>
    </SIGNONMSGSRSV1>
    <SECURITIES_MSGSRSV1>
        <XFERTRNRS>
            <TRNUID>000146_ECXJGP1906180002_1</TRNUID>
            <STATUS>
                <CODE>2000</CODE>
                <SEVERITY>ERROR</SEVERITY>
                <MESSAGE>没有该项业务的授权书</MESSAGE>
            </STATUS>
        </XFERTRNRS>
    </SECURITIES_MSGSRSV1>
</FOX>

最后程序自身不足这还是很好说明的,就拿业务数据异常问题来讲,当第一次支付失败,用户已经将数据修正后,系统再次提交时,还是按原来的业务ID来生成,并没考虑到兴业银行银企直联中,是不允许重复的TRNUID的,于是就会出现以下响应内容

  • 交易失败,交易请求中的业务流水号TRNUID值已被使用,对于该问题,我们可以在主键之外再加个支付次数来解决一个单据需要多次支付时的问题,当然这需要编码解决
<FOX>
    <SIGNONMSGSRSV1>
        <SONRS>
            <STATUS>
                <CODE>0</CODE>
                <SEVERITY>INFO</SEVERITY>
            </STATUS>
            <DTSERVER>2019-04-25 18:33:21</DTSERVER>
        </SONRS>
    </SIGNONMSGSRSV1>
    <SECURITIES_MSGSRSV1>
        <XFERTRNRS>
            <TRNUID>000147_DCXJXTY1904100003_4</TRNUID>
            <STATUS>
                <CODE>12345</CODE>
                <SEVERITY>WARN</SEVERITY>
                <MESSAGE>交易失败,交易请求中的业务流水号TRNUID值已被使用!</MESSAGE>
            </STATUS>
        </XFERTRNRS>
    </SECURITIES_MSGSRSV1>
</FOX>

总结来说,SIGNONMSGSRSV1.SONRS.STATUS失败的话,一定会是财务配置错误SECURITIES_MSGSRSV1.XFERTRNRS.XFERRS.XFERPRCSTS这里只会出现支付结果,这与本篇内容的支付失败已经不是一个概念;SECURITIES_MSGSRSV1.XFERTRNRS.STATUS这块是问题最多的,同时包含财务配置错误业务数据错误程序自身不足,对于这里,没什么好的判断方法,只能根据STATUS.CODE来进行判断。