興業銀行銀企直聯衝賬查詢及網銀審覈退回經辦

之前寫過興業銀行銀企直聯應當如何查詢手續費及退票,但事實上興業銀行還會產生衝賬問題。所謂衝賬,就是指支付信息錯誤,導致根本無法到達實際收款銀行,這時候因爲根本無法到達收款銀行,所以也就不會有收款銀行回饋的支付失敗信息(也就是退票),這時候銀行就會自己產生一筆負向交易流水,以便將這筆付款進行衝正。

雖然看起來與退票類似,但兩者其實完全不同:

  • 退票記錄金額是正值,而衝賬金額是負值;
  • 退票並不會退還手續費,衝賬將退還手續費;
  • 退票有專門的查詢參數,衝賬查詢方式則與手續費是一致的。

下面先展示一段項目中真實的支付報文,該報文將導致衝賬問題,當然關鍵信息已脫敏

  <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並不是真正的最終狀態,出納等網銀操作用戶還是可以通過一些操作,在網銀中將該筆單據迴歸到正常可支付狀態並進行支付,這樣的話,就可能存在系統中支付狀態與網銀中支付狀態不一致,這個風險性還是需要注意的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章