兴业银行银企直联冲账查询及网银审核退回经办

之前写过兴业银行银企直联应当如何查询手续费及退票,但事实上兴业银行还会产生冲账问题。所谓冲账,就是指支付信息错误,导致根本无法到达实际收款银行,这时候因为根本无法到达收款银行,所以也就不会有收款银行回馈的支付失败信息(也就是退票),这时候银行就会自己产生一笔负向交易流水,以便将这笔付款进行冲正。

虽然看起来与退票类似,但两者其实完全不同:

  • 退票记录金额是正值,而冲账金额是负值;
  • 退票并不会退还手续费,冲账将退还手续费;
  • 退票有专门的查询参数,冲账查询方式则与手续费是一致的。

下面先展示一段项目中真实的支付报文,该报文将导致冲账问题,当然关键信息已脱敏

  <SECURITIES_MSGSRQV1>
    <XFERTRNRQ>
      <TRNUID>000145_DCXJXL1904260004_1</TRNUID>
      <XFERRQ>
        <XFERINFO>
          <ACCTFROM>
            <ACCTID>XXXXXX</ACCTID>
            <NAME>XXXX信息技术股份有限公司</NAME>
            <BANKDESC>兴业银行股份有限公司上海淮海支行</BANKDESC>
            <CITY>上海</CITY>
          </ACCTFROM>
          <ACCTTO INTERBANK="N" LOCAL="Y">
            <ACCTID>XXXXX</ACCTID>
            <NAME>XXX</NAME>
            <BANKDESC>乌鲁木齐市</BANKDESC>
            <CITY>上海黄浦区</CITY>
          </ACCTTO>
          <TRNAMT>16.00</TRNAMT>
          <PURPOSE>FKB_DCXJXL1904260004_000145</PURPOSE>
        </XFERINFO>
      </XFERRQ>
    </XFERTRNRQ>
  </SECURITIES_MSGSRQV1>

这段报文哪里错了呢?细看一下,收款银行ACCTTO.BANKDESC,这里居然错误的填写了乌鲁木齐市,事实上,这会导致人行无法找到对应的收款银行,从而也就导致了冲账。

下面我们再看下3.4.2 查询转账交易状态所查询到的支付结果

    <SECURITIES_MSGSRSV1>
        <XFERINQTRNRS>
            <TRNUID>190429103101328_3.4.2_1652</TRNUID>
            <STATUS>
                <CODE>0</CODE>
                <SEVERITY>INFO</SEVERITY>
            </STATUS>
            <XFERINQRS>
                <XFERLIST MORE="N">
                    <FROM>900861323722</FROM>
                    <TO>900861323722</TO>
                    <XFER>
                        <SRVRTID>900861323722</SRVRTID>
                        <XFERINFO>
                            <ACCTFROM>
                                <ACCTID>XXXXXX</ACCTID>
                                <NAME>XXXX信息技术股份有限公司</NAME>
                                <CITY>上海</CITY>
                            </ACCTFROM>
                            <ACCTTO INTERBANK="N" LOCAL="Y">
                                <ACCTID>XXXXX</ACCTID>
                                <NAME>XXX</NAME>
                                <BANKDESC>乌鲁木齐市</BANKDESC>
                                <CITY>上海黄浦区</CITY>
                            </ACCTTO>
                            <CHEQUENUM>8758456</CHEQUENUM>
                            <CURSYM>RMB</CURSYM>
                            <TRNAMT>16.00</TRNAMT>
                            <PURPOSE>FKB_DCXJXL1904260004_000145</PURPOSE>
                        </XFERINFO>
                        <XFERPRCSTS>
                            <XFERPRCCODE>PAYOUT</XFERPRCCODE>
                            <DTXFERPRC>2019-04-26 16:42:10</DTXFERPRC>
                            <MESSAGE>交易成功</MESSAGE>
                        </XFERPRCSTS>
                    </XFER>
                </XFERLIST>
            </XFERINQRS>
        </XFERINQTRNRS>
    </SECURITIES_MSGSRSV1>

这个接口不管怎么查询,返回的最终结果都是PAYOUT,下面我们再看下3.6账户余额和交易流水分页查询中查询到的交易信息

    <SECURITIES_MSGSRSV1>
        <SCUSTSTMTTRNRS>
            <TRNUID>190426224007047_3.6_1_9469</TRNUID>
            <STATUS>
                <CODE>0</CODE>
                <SEVERITY>INFO</SEVERITY>
            </STATUS>
            <SCUSTSTMTRS>
                <CURDEF>RMB</CURDEF>
                <ACCTFROM>
                    <ACCTID>XXXXX</ACCTID>
                </ACCTFROM>
                <TRANLIST MORE="N">
                    <DTSTART>2019-04-26</DTSTART>
                    <DTEND>2019-04-26</DTEND>
                    <STMTTRN>
                        <SRVRTID>9999A5OA</SRVRTID>
                        <TRNTYPE>DEBIT</TRNTYPE>
                        <TRNCODE>227</TRNCODE>
                        <DTACCT>2019-04-26T16:42:10</DTACCT>
                        <TRNAMT>1.00</TRNAMT>
                        <BALAMT>49974.00</BALAMT>
                        <CURRENCY>RMB</CURRENCY>
                        <MEMO></MEMO>
                        <CORRELATE_ACCTID></CORRELATE_ACCTID>
                        <CORRELATE_NAME></CORRELATE_NAME>
                        <CHEQUENUM></CHEQUENUM>
                        <BILLTYPE></BILLTYPE>
                        <BILLNUMBER></BILLNUMBER>
                        <CORRELATE_BANKNAME></CORRELATE_BANKNAME>
                        <CORRELATE_BANKCODE></CORRELATE_BANKCODE>
                        <BUSINESSTYPE>银行扣款</BUSINESSTYPE>
                        <ATTACHINFO>2019042600305614580000001</ATTACHINFO>
                        <HXJYLSBH>H00100201904260010541383430000</HXJYLSBH>
                        <SUMMNAME>收费</SUMMNAME>
                        <SUMMDESC>收费</SUMMDESC>
                        <PURPOSE>企业网银转账手续费;</PURPOSE>
                        <BRANCHNO>21168</BRANCHNO>
                        <CHANNELCODE>204</CHANNELCODE>
                        <CASHFLAG>1</CASHFLAG>
                        <CBBZ>0</CBBZ>
                        <BCZBZ>0</BCZBZ>
                        <ROUTECHOICE></ROUTECHOICE>
                        <BIZREF></BIZREF>
                        <TEXT1></TEXT1>
                        <TEXT2></TEXT2>
                        <TEXT3></TEXT3>
                    </STMTTRN>
                    <STMTTRN>
                        <SRVRTID>9999A5OA</SRVRTID>
                        <TRNTYPE>DEBIT</TRNTYPE>
                        <TRNCODE>231</TRNCODE>
                        <DTACCT>2019-04-26T16:42:10</DTACCT>
                        <TRNAMT>16.00</TRNAMT>
                        <BALAMT>49958.00</BALAMT>
                        <CURRENCY>RMB</CURRENCY>
                        <MEMO></MEMO>
                        <CORRELATE_ACCTID>XXXXX</CORRELATE_ACCTID>
                        <CORRELATE_NAME>XXX</CORRELATE_NAME>
                        <CHEQUENUM>118758456</CHEQUENUM>
                        <BILLTYPE></BILLTYPE>
                        <BILLNUMBER></BILLNUMBER>
                        <CORRELATE_BANKNAME>乌鲁木齐市</CORRELATE_BANKNAME>
                        <CORRELATE_BANKCODE></CORRELATE_BANKCODE>
                        <BUSINESSTYPE></BUSINESSTYPE>
                        <ATTACHINFO>2019042600305614588000001</ATTACHINFO>
                        <HXJYLSBH>H00100201904260010541383430000</HXJYLSBH>
                        <SUMMNAME>汇款</SUMMNAME>
                        <SUMMDESC>网上汇款</SUMMDESC>
                        <PURPOSE>FKB_DCXJXL1904260004_000145</PURPOSE>
                        <BRANCHNO>21168</BRANCHNO>
                        <CHANNELCODE>204</CHANNELCODE>
                        <CASHFLAG>1</CASHFLAG>
                        <CBBZ>0</CBBZ>
                        <BCZBZ>0</BCZBZ>
                        <ROUTECHOICE>4</ROUTECHOICE>
                        <BIZREF></BIZREF>
                        <TEXT1></TEXT1>
                        <TEXT2></TEXT2>
                        <TEXT3></TEXT3>
                    </STMTTRN>
                </TRANLIST>
                <LEDGERBAL>
                    <BALAMT>49958.00</BALAMT>
                    <DTASOF>2019-04-26</DTASOF>
                </LEDGERBAL>
                <AVAILBAL>
                    <BALAMT>49958.00</BALAMT>
                    <DTASOF>2019-04-26</DTASOF>
                </AVAILBAL>
            </SCUSTSTMTRS>
        </SCUSTSTMTTRNRS>
    </SECURITIES_MSGSRSV1>

下面就是同样通过3.6账户余额和交易流水分页查询中查询到的冲账信息

    <SECURITIES_MSGSRSV1>
        <SCUSTSTMTTRNRS>
            <TRNUID>190428154005926_3.6_1_9957</TRNUID>
            <STATUS>
                <CODE>0</CODE>
                <SEVERITY>INFO</SEVERITY>
            </STATUS>
            <SCUSTSTMTRS>
                <CURDEF>RMB</CURDEF>
                <ACCTFROM>
                    <ACCTID>216170100100279407</ACCTID>
                </ACCTFROM>
                <TRANLIST MORE="N">
                    <DTSTART>2019-04-28</DTSTART>
                    <DTEND>2019-04-28</DTEND>
                    <STMTTRN>
                        <SRVRTID>07020004</SRVRTID>
                        <TRNTYPE>DEBIT</TRNTYPE>
                        <TRNCODE>144</TRNCODE>
                        <DTACCT>2019-04-28T10:39:56</DTACCT>
                        <TRNAMT>-16.00</TRNAMT>
                        <BALAMT>49974.00</BALAMT>
                        <CURRENCY>RMB</CURRENCY>
                        <MEMO></MEMO>
                        <CORRELATE_ACCTID>XXXXX</CORRELATE_ACCTID>
                        <CORRELATE_NAME>XXX</CORRELATE_NAME>
                        <CHEQUENUM>118758456</CHEQUENUM>
                        <BILLTYPE></BILLTYPE>
                        <BILLNUMBER></BILLNUMBER>
                        <CORRELATE_BANKNAME>乌鲁木齐市</CORRELATE_BANKNAME>
                        <CORRELATE_BANKCODE></CORRELATE_BANKCODE>
                        <BUSINESSTYPE></BUSINESSTYPE>
                        <ATTACHINFO>2019042800323201412000001</ATTACHINFO>
                        <HXJYLSBH>H00100201904280011321490470000</HXJYLSBH>
                        <SUMMNAME>冲账</SUMMNAME>
                        <SUMMDESC>隔日冲账</SUMMDESC>
                        <PURPOSE>无收款行</PURPOSE>
                        <BRANCHNO>21168</BRANCHNO>
                        <CHANNELCODE>101</CHANNELCODE>
                        <CASHFLAG>1</CASHFLAG>
                        <CBBZ>3</CBBZ>
                        <BCZBZ>0</BCZBZ>
                        <ROUTECHOICE>4</ROUTECHOICE>
                        <BIZREF></BIZREF>
                        <TEXT1></TEXT1>
                        <TEXT2></TEXT2>
                        <TEXT3></TEXT3>
                    </STMTTRN>
                    <STMTTRN>
                        <SRVRTID>07020004</SRVRTID>
                        <TRNTYPE>DEBIT</TRNTYPE>
                        <TRNCODE>144</TRNCODE>
                        <DTACCT>2019-04-28T10:39:56</DTACCT>
                        <TRNAMT>-1.00</TRNAMT>
                        <BALAMT>49975.00</BALAMT>
                        <CURRENCY>RMB</CURRENCY>
                        <MEMO></MEMO>
                        <CORRELATE_ACCTID></CORRELATE_ACCTID>
                        <CORRELATE_NAME></CORRELATE_NAME>
                        <CHEQUENUM></CHEQUENUM>
                        <BILLTYPE></BILLTYPE>
                        <BILLNUMBER></BILLNUMBER>
                        <CORRELATE_BANKNAME></CORRELATE_BANKNAME>
                        <CORRELATE_BANKCODE></CORRELATE_BANKCODE>
                        <BUSINESSTYPE></BUSINESSTYPE>
                        <ATTACHINFO>2019042800323201412000002</ATTACHINFO>
                        <HXJYLSBH>H00100201904280011321490470000</HXJYLSBH>
                        <SUMMNAME>冲账</SUMMNAME>
                        <SUMMDESC>隔日冲账</SUMMDESC>
                        <PURPOSE>无收款行</PURPOSE>
                        <BRANCHNO>21168</BRANCHNO>
                        <CHANNELCODE>101</CHANNELCODE>
                        <CASHFLAG>1</CASHFLAG>
                        <CBBZ>3</CBBZ>
                        <BCZBZ>0</BCZBZ>
                        <ROUTECHOICE></ROUTECHOICE>
                        <BIZREF></BIZREF>
                        <TEXT1></TEXT1>
                        <TEXT2></TEXT2>
                        <TEXT3></TEXT3>
                    </STMTTRN>
                </TRANLIST>
                <LEDGERBAL>
                    <BALAMT>49975.00</BALAMT>
                    <DTASOF>2019-04-28</DTASOF>
                </LEDGERBAL>
                <AVAILBAL>
                    <BALAMT>49975.00</BALAMT>
                    <DTASOF>2019-04-28</DTASOF>
                </AVAILBAL>
            </SCUSTSTMTRS>
        </SCUSTSTMTTRNRS>
    </SECURITIES_MSGSRSV1>

可以看到交易发生日期是2019-04-26,实际冲账日期是2019-04-28,虽然银行方面没能指出冲账和交易报文可以通过哪些字段进行对应,但对比两份报文,可以发现虽然交易的HXJYLSBH与冲账的HXJYLSBH不同,但两者的CHEQUENUMCORRELATE_ACCTIDCORRELATE_NAME这三者却是完全一致的,TRNAMT则是完全相反,另外冲账退回的手续费,其HXJYLSBH与冲账记录的HXJYLSBH也是完全一致的,所以到此我们也就知道了冲账应该如何关联到原始交易记录

  • 通过CHEQUENUM进行关联,如果觉得不够保险,可以增加CORRELATE_ACCTIDCORRELATE_NAMETRNAMT三者共同判断
  • 冲账退回的手续费与正常交易的手续费一样,还是通过HXJYLSBH进行关联

接下来我们就是需要在CIBTransactionHelper中增加冲账相关的查询代码,代码涉及的SDK在此处下载,完整的代码如下,如果需要了解各个接口说明,则可以查看此篇内容

    /// <summary>
    /// 兴业银行交易辅助类
    /// </summary>
    public class CIBTransactionHelper
    {
        private long _cid;
        private string _userId;
        private string _pwd;
        private ICIBTransactionPurposeBuilder _buider;
        /// <summary>
        /// 转账对应的SUMMNAME
        /// </summary>
        public static string TransactionSummaryName = "汇款";
        /// <summary>
        /// 退票对应的SUMMNAME
        /// </summary>
        public static string RefundSummaryName = "汇出退回";
        /// <summary>
        /// 手续费对应的SUMMNAME
        /// </summary>
        public static string ChargesSummaryName = "收费";
        /// <summary>
        /// 冲账对应的SUMMNAME
        /// </summary>
        public static string RubricSummaryName = "冲账";
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="cid">兴业银行银企直联客户号</param>
        /// <param name="userId">兴业银行银企直联登录用户名</param>
        /// <param name="pwd">兴业银行银企直联登录密码</param>
        /// <param name="host">前置机域名,默认为127.0.0.1</param>
        /// <param name="port">前置机端口,默认为8007</param>
        /// <param name="builder">转账交易用途构建实现,如果不传则使用默认实现<see cref="CIBTransactionPurposeBuilder"/></param>
        public CIBTransactionHelper(long cid, string userId, string pwd,
            string host = "127.0.0.1", int port = 8007, ICIBTransactionPurposeBuilder builder = null)
        {
            if (cid <= 0 || string.IsNullOrWhiteSpace(userId) || string.IsNullOrWhiteSpace(pwd)
                || string.IsNullOrWhiteSpace(host))
            {
                throw new ArgumentException();
            }
            this._cid = cid;
            this._userId = userId;
            this._pwd = pwd;
            this._buider = builder;
            if (builder == null)
            {
                this._buider = new CIBTransactionPurposeBuilder();
            }
            this.Client = new CIBClient(host, port);
        }
        /// <summary>
        /// 兴业银行银企直联客户端
        /// </summary>
        public ICIBClient Client { get; set; }
        /// <summary>
        /// 生成一个用于查询的TRNUID,注意转账之类的业务切记不要采用此方法获取TRNUID
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static string GetQueryTRNUID(string key)
        {
            var tmp = (Math.Abs(Guid.NewGuid().GetHashCode()) % 10000).ToString("0000");
            return string.Format("{0:yyMMddHHmmssfff}_{1}_{2}", DateTime.Now, key, tmp);
        }
        /// <summary>
        /// 获取兴业银行3.6查询接口请求主体
        /// </summary>
        /// <param name="acctid"></param>
        /// <param name="dtStart"></param>
        /// <param name="dtEnd"></param>
        /// <param name="page"></param>
        /// <param name="selType"></param>
        /// <returns></returns>
        public FOXRQ<V1_SCUSTSTMTTRNRQ, V1_SCUSTSTMTTRNRS> GetCIBRequest_3_6(string acctid, DateTime dtStart, DateTime dtEnd, int page, int selType)
        {
            return new FOXRQ<V1_SCUSTSTMTTRNRQ, V1_SCUSTSTMTTRNRS>()
            {
                SIGNONMSGSRQV1 = new SIGNONMSGSRQV1
                {
                    SONRQ = new SONRQ
                    {
                        CID = this._cid,
                        USERID = this._userId,
                        USERPASS = this._pwd,
                    }
                },
                SECURITIES_MSGSRQV1 = new V1_SCUSTSTMTTRNRQ
                {
                    SCUSTSTMTTRNRQ = new SCUSTSTMTTRNRQ
                    {
                        TRNUID = GetQueryTRNUID("3.6" + "_" + selType),
                        SCUSTSTMTRQ = new SCUSTSTMTTRN_SCUSTSTMTRQ
                        {
                            VERSION = "2.0",
                            ACCTFROM = new ACCTFROM
                            {
                                ACCTID = acctid
                            },
                            INCTRAN = new INCTRAN
                            {
                                DTEND = dtEnd,
                                DTSTART = dtStart,
                                TRNTYPE = 2,
                                PAGE = page,
                            },
                            SELTYPE = selType
                        }
                    }
                }
            };
        }
        /// <summary>
        /// 获取退票记录
        /// </summary>
        /// <param name="acctid"></param>
        /// <param name="dtStart"></param>
        /// <param name="dtEnd"></param>
        /// <returns></returns>
        public IList<STMTTRN> GetRefundRecords(string acctid, DateTime dtStart, DateTime dtEnd)
        {
            return this.GetRecords(acctid, dtStart, dtEnd, 3);
        }
        private List<STMTTRN> GetRecords(string acctid, DateTime dtStart, DateTime dtEnd, int selType)
        {
            var list = new List<STMTTRN>();
            dtStart = dtStart.Date;
            dtEnd = dtEnd.Date;
            if (dtStart <= dtEnd)
            {
                //历史与当日不能同查,所以此处要加以判断,因为每日流水可能较大,所以此处简单拆分成按每天查询
                var dt = dtStart;
                for (; dt <= dtEnd;)
                {
                    for (var i = 1; ; i++)
                    {
                        var rq = GetCIBRequest_3_6(acctid, dt, dt, i, selType);
                        var rs = this.Client.Execute(rq);
                        if (rs != null && rs.ResponseSuccess && rs.SIGNONMSGSRSV1?.SONRS?.STATUS?.IsCorrect == true
                            && rs.SECURITIES_MSGSRSV1?.SCUSTSTMTTRNRS?.STATUS?.IsCorrect == true
                            && rs.SECURITIES_MSGSRSV1.SCUSTSTMTTRNRS.SCUSTSTMTRS?.TRANLIST?.List != null)
                        {
                            list.AddRange(rs.SECURITIES_MSGSRSV1.SCUSTSTMTTRNRS.SCUSTSTMTRS.TRANLIST.List);
                            if (rs.SECURITIES_MSGSRSV1.SCUSTSTMTTRNRS.SCUSTSTMTRS.TRANLIST.MORE == "Y")
                            {
                                continue;
                            }
                        }
                        break;
                    }
                    dt = dt.AddDays(1);
                }
            }
            return list;
        }
        /// <summary>
        /// 获取交易记录(含手续费、冲账等)
        /// </summary>
        /// <param name="acctid"></param>
        /// <param name="dtStart"></param>
        /// <param name="dtEnd"></param>
        /// <returns></returns>
        public IList<STMTTRN> GetTransactionRecords(string acctid, DateTime dtStart, DateTime dtEnd)
        {
            return this.GetRecords(acctid, dtStart, dtEnd, 1);
        }
        /// <summary>
        /// 根据退票记录获取其对应的交易记录
        /// </summary>
        /// <param name="refundList">退票流水</param>
        /// <param name="acctid">当前退票属于哪个账号</param>
        /// <param name="refundDayDiff">若需自动查询交易流水时,查询几天内的交易流水,默认按兴业银行文档设置为2天</param>
        /// <param name="transList">交易流水,默认为null,代表按退票流水自动查询,如果不为null则与退票流水进行对比</param>
        /// <returns>Key为交易流水id,Tuple.Item1为交易流水,Tuple.Item2为退票流水</returns>
        public IDictionary<string, Tuple<STMTTRN, STMTTRN>> GetRefundMapping(IList<STMTTRN> refundList, string acctid, int refundDayDiff = 2, IList<STMTTRN> transList = null)
        {
            var dic = new Dictionary<string, Tuple<STMTTRN, STMTTRN>>();
            if (refundList != null && refundList.Count > 0)
            {
                refundList = refundList.Where(x => x.SUMMNAME == RefundSummaryName).OrderBy(x => x.DTACCT).ToList();
                if (refundList.Count > 0)
                {
                    if (transList == null || transList.Count == 0)
                    {//如果未传递交易流水,则自动按退票日期获取对应日期的所有交易流水
                        transList = this.GetTransactionRecords(acctid, refundList, refundDayDiff);
                    }
                    var query = from refund in refundList
                                join trans in transList
                                on refund.MEMO equals trans.HXJYLSBH
                                where trans.SUMMNAME == TransactionSummaryName
                                select Tuple.Create(trans, refund);
                    dic = query.ToDictionary(k => this._buider.GetIdFromPurpose(k.Item1.PURPOSE), v => v);
                }
            }
            return dic;
        }
        private IList<STMTTRN> GetTransactionRecords(string acctid, IList<STMTTRN> list, int dayDiff)
        {
            var transList = new List<STMTTRN>();
            var timeList = list.Select(x => x.DTACCT.Date).Distinct().OrderBy(d => d).ToList();
            //虽然底层查询时是拆分成按每日查询,但因为退票或冲账需要倒查N天的交易流水,所以将日期按连续性拆分成日期范围还是有必要的
            var timeRange = this.GetTimeRange(timeList, dayDiff);
            foreach (var t in timeRange)
            {
                var tmp = this.GetTransactionRecords(acctid, t.Item1.AddDays(-1), t.Item2);
                transList.AddRange(tmp);
            }
            return transList;
        }
        private IList<Tuple<DateTime, DateTime>> GetTimeRange(IList<DateTime> timeList, int dayDiff)
        {
            var timeRange = new List<Tuple<DateTime, DateTime>>();
            var dtStart = timeList[0];
            var dtEnd = timeList[0];
            for (var i = 1; i <= timeList.Count; i++)
            {
                DateTime dt = DateTime.MaxValue;
                if (i < timeList.Count)
                {
                    dt = timeList[i];
                }
                if (dt >= dtEnd && dt <= dtEnd.AddDays(dayDiff))
                {
                    //退票需要查询交易流水日期范围为交易当天或交易前一天
                    //所以如果出现跳日,比如03-19和03-21,也应该算是连续日期
                    dtEnd = dt;
                }
                else
                {
                    timeRange.Add(Tuple.Create(dtStart, dtEnd));
                    dtStart = dt;
                    dtEnd = dt;
                }
            }
            return timeRange;
        }
        /// <summary>
        /// 根据交易流水获取对应的交易记录及手续费
        /// </summary>
        /// <param name="list">交易流水</param>
        /// <returns>Key为交易流水id,Tuple.Item1为交易流水,Tuple.Item2为手续费</returns>
        public IDictionary<string, Tuple<STMTTRN, decimal>> GetServiceChargesMapping(IList<STMTTRN> list)
        {
            var dic = new Dictionary<string, Tuple<STMTTRN, decimal>>();
            if (list != null && list.Count > 0)
            {
                //此处判断PURPOSE是否是当前业务组织的PURPOSE
                list = list.Where(x => (x.SUMMNAME == TransactionSummaryName && this._buider.IsCorrectPurpose(x.PURPOSE))
                || x.SUMMNAME == ChargesSummaryName).ToList();
                if (list.Count > 0)
                {
                    var groups = list.GroupBy(x => x.HXJYLSBH);  // new { x.SRVRTID, x.DTACCT }
#if DEBUG
                    var tmp = groups.ToList();
#endif
                    foreach (var g in groups)
                    {
                        var trans = g.FirstOrDefault(x => x.SUMMNAME == TransactionSummaryName);
                        if (trans == null)
                        {
                            continue;
                        }
                        var id = this._buider.GetIdFromPurpose(trans.PURPOSE);
                        if (string.IsNullOrWhiteSpace(id))
                        {
                            continue;
                        }
                        //可能会无需手续费
                        var charge = g.FirstOrDefault(x => x.SUMMNAME == ChargesSummaryName)?.TRNAMT ?? 0;
                        dic.Add(id, Tuple.Create(trans, charge));
                    }
                }
            }
            return dic;
        }
        /// <summary>
        /// 根据交易流水获取其内包含的冲账记录及冲账退回的手续费
        /// </summary>
        /// <param name="list">交易流水</param>
        /// <returns>Tuple.Item1为冲账交易流水,需按其值<see cref="STMTTRN.CHEQUENUM"/>凭证代号与历史交易记录CHEQUENUM的进行比较,Tuple.Item2为冲账时退回的手续费</returns>
        public IList<Tuple<STMTTRN, decimal>> GetRubricRecords(IList<STMTTRN> list)
        {
            var retList = new List<Tuple<STMTTRN, decimal>>();
            if (list != null && list.Count > 0)
            {
                list = list.Where(x => x.SUMMNAME == RubricSummaryName).ToList();
                if (list.Count > 0)
                {
                    var groups = list.GroupBy(x => x.HXJYLSBH);//冲账逻辑与退票本质无差别
                    foreach (var g in groups)
                    {
                        var trans = g.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.CHEQUENUM));//凭证代号不为空代表冲账交易记录,为空代表冲账手续费
                        if (trans == null)
                        {
                            continue;
                        }
                        var charge = g.FirstOrDefault(x => string.IsNullOrWhiteSpace(x.CHEQUENUM))?.TRNAMT ?? 0;//冲账退回的手续费
                        retList.Add(Tuple.Create(trans, charge));
                    }
                }
            }
            return retList;
        }
        /// <summary>
        /// 根据冲账记录获取其对应的交易记录
        /// </summary>
        /// <param name="rubricList">冲账流水及冲账手续费</param>
        /// <param name="acctid">当前冲账属于哪个账号</param>
        /// <param name="rubricDayDiff">若需自动查询交易流水时,查询几天内的交易流水,默认设置为3天</param>
        /// <param name="transList">交易流水,默认为null,代表按退票流水自动查询,如果不为null则与退票流水进行对比</param>
        /// <returns>Key为交易流水id,Tuple.Item1为交易流水,Tuple.Item2为冲账流水,Tuple.Item3为冲账手续费</returns>
        public IDictionary<string, Tuple<STMTTRN, STMTTRN, decimal>> GetRubricMapping(IList<Tuple<STMTTRN, decimal>> rubricList, string acctid, int rubricDayDiff = 3, IList<STMTTRN> transList = null)
        {
            var dic = new Dictionary<string, Tuple<STMTTRN, STMTTRN, decimal>>();
            if (rubricList != null && rubricList.Count > 0)
            {
                if (transList == null || transList.Count == 0)
                {//如果未传递交易流水,则自动按冲账日期获取对应日期的所有交易流水
                    transList = this.GetTransactionRecords(acctid, rubricList.Select(x => x.Item1).ToList(), rubricDayDiff);
                }
                var query = from rubric in rubricList
                            join trans in transList
                            on rubric.Item1.CHEQUENUM equals trans.CHEQUENUM
                            where trans.SUMMNAME == TransactionSummaryName
                            select Tuple.Create(trans, rubric.Item1, rubric.Item2);
                dic = query.ToDictionary(k => this._buider.GetIdFromPurpose(k.Item1.PURPOSE), v => v);
            }
            return dic;
        }
    }
    /// <summary>
    /// 兴业银行交易流水用途构建约束接口
    /// </summary>
    public interface ICIBTransactionPurposeBuilder
    {
        /// <summary>
        /// 根据内部系统业务Id构建Purpose
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        string GetPurpose(string id);
        /// <summary>
        /// 从交易流水Purpose中获取内部系统业务Id,注意此处Purpose应当为网上汇款交易流水中的PURPOSE
        /// </summary>
        /// <param name="purpose"></param>
        /// <returns></returns>
        string GetIdFromPurpose(string purpose);
        /// <summary>
        /// 当前PURPOSE是否符合标准
        /// </summary>
        /// <param name="purpose"></param>
        /// <returns></returns>
        bool IsCorrectPurpose(string purpose);
    }
    /// <summary>
    /// 兴业银行交易流水用途构建默认实现
    /// </summary>
    public class CIBTransactionPurposeBuilder : ICIBTransactionPurposeBuilder
    {
        /// <summary>
        /// 从交易流水Purpose中获取内部系统业务Id,注意此处Purpose应当为网上汇款交易流水中的PURPOSE
        /// </summary>
        /// <param name="purpose"></param>
        /// <returns></returns>
        public virtual string GetIdFromPurpose(string purpose)
        {
            return purpose;
        }
        /// <summary>
        /// 根据内部系统业务Id构建Purpose
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual string GetPurpose(string id)
        {
            return id;
        }
        /// <summary>
        /// 当前PURPOSE是否符合标准
        /// </summary>
        /// <param name="purpose"></param>
        /// <returns></returns>
        public virtual bool IsCorrectPurpose(string purpose)
        {
            return true;
        }
    }

调用示例如下

            var helper = new CIBTransactionHelper(cid, uid, pwd, ip, port, new CustCIBTransactionPurposeBuilder());
            var transList = helper.GetTransactionRecords(mainAccountId, new DateTime(2019, 3, 19), new DateTime(2019, 3, 19));
            var changeDic = helper.GetServiceChargesMapping(transList);

            var rubricList = helper.GetRubricRecords(transList);
#if DEBUG
            if (rubricList.Count == 0)
            {
                var tmpTransList = new List<STMTTRN>();
                //为方便测试,手工增加一条冲账记录及冲账流水
                tmpTransList.Add(new STMTTRN
                {
                    DTACCT = new DateTime(2019, 3, 19),
                    HXJYLSBH = "H00100201904280011321490470000",//假编号
                    CHEQUENUM = "110340545",
                    SUMMNAME = CIBTransactionHelper.RubricSummaryName,
                    SUMMDESC = "隔日冲账",
                    PURPOSE = "无收款行",
                    TRNAMT=-15.54m,
                });
                tmpTransList.Add(new STMTTRN
                {
                    DTACCT = new DateTime(2019, 3, 19),
                    HXJYLSBH = "H00100201904280011321490470000",//假编号
                    CHEQUENUM = "",
                    SUMMNAME = CIBTransactionHelper.RubricSummaryName,
                    SUMMDESC = "隔日冲账",
                    PURPOSE = "无收款行",
                    TRNAMT = -0.60m,
                });
                rubricList = helper.GetRubricRecords(tmpTransList);
            }
#endif
            //如果你已经通过GetTransactionRecords获取并持久化了手续费、HXJYLSBH及CHEQUENUM
            //那么下面Mappding这步就可以忽略,转为直接查本地数据库
            var rubricDic = helper.GetRubricMapping(rubricList, mainAccountId, rubricDayDiff: 3, transList: transList);

说完了冲账,我们再来说下网银审核退回经办,因为该部分内容不多,就不单独开篇描述。
正常来讲,我们既然通过银企直联将支付信息发送给了银行,那么我们肯定是要进行支付的,所以一般来说,网银审批时,基本都是会审批通过,但有时候存在一些特殊情况,虽然支付信息已经同步到了银行,但财务必需要在网银中终止该笔交易,也就是财务不进行支付,这时候就会产生退回经办操作,具体就是出纳和财务复审进行退回经办,当这两者操作完后,通过3.4.2 查询转账交易状态,我们能查到的状态为SEND_BACK,这时候还需要由操作员进行最终的驳回操作,这样才能使查询状态最终返回值为CANCEL,然后问题就出在这个操作员身上,因为银企直联时,需要将U盾插在银企直联前置机所在服务器上,也就是说,往往财务并不能直接访问前置机服务器,那么这个通过操作员账号进行驳回的操作也就无法进行,虽然兴业银行有EXPIRED的处理逻辑,但如果你未设置期望支付时间,那么你需要花费一个月的时间才能等到EXPIRED这个状态,所以这在实际操作中基本是无法被用户接受的,那这时候,你可以考虑将SEND_BACK做为一个终结状态来进行业务处理。当然因为实际SEND_BACK并不是真正的最终状态,出纳等网银操作用户还是可以通过一些操作,在网银中将该笔单据回归到正常可支付状态并进行支付,这样的话,就可能存在系统中支付状态与网银中支付状态不一致,这个风险性还是需要注意的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值