之前写过兴业银行银企直联应当如何查询手续费及退票,但事实上兴业银行还会产生冲账问题。所谓冲账,就是指支付信息错误,导致根本无法到达实际收款银行,这时候因为根本无法到达收款银行,所以也就不会有收款银行回馈的支付失败信息(也就是退票),这时候银行就会自己产生一笔负向交易流水,以便将这笔付款进行冲正。
虽然看起来与退票类似,但两者其实完全不同:
下面先展示一段项目中真实的支付报文,该报文将导致冲账问题,当然关键信息已脱敏
<SECURITIES_MSGSRQV1>
<XFERTRNRQ>
<TRNUID>000145_DCXJXL1904260004_1TRNUID>
<XFERRQ>
<XFERINFO>
<ACCTFROM>
<ACCTID>XXXXXXACCTID>
<NAME>XXXX信息技术股份有限公司NAME>
<BANKDESC>兴业银行股份有限公司上海淮海支行BANKDESC>
<CITY>上海CITY>
ACCTFROM>
<ACCTTO INTERBANK="N" LOCAL="Y">
<ACCTID>XXXXXACCTID>
<NAME>XXXNAME>
<BANKDESC>乌鲁木齐市BANKDESC>
<CITY>上海黄浦区CITY>
ACCTTO>
<TRNAMT>16.00TRNAMT>
<PURPOSE>FKB_DCXJXL1904260004_000145PURPOSE>
XFERINFO>
XFERRQ>
XFERTRNRQ>
SECURITIES_MSGSRQV1>
这段报文哪里错了呢?细看一下,收款银行ACCTTO.BANKDESC
,这里居然错误的填写了乌鲁木齐市
,事实上,这会导致人行无法找到对应的收款银行,从而也就导致了冲账。
下面我们再看下3.4.2 查询转账交易状态
所查询到的支付结果
<SECURITIES_MSGSRSV1>
<XFERINQTRNRS>
<TRNUID>190429103101328_3.4.2_1652TRNUID>
<STATUS>
<CODE>0CODE>
<SEVERITY>INFOSEVERITY>
STATUS>
<XFERINQRS>
<XFERLIST MORE="N">
<FROM>900861323722FROM>
<TO>900861323722TO>
<XFER>
<SRVRTID>900861323722SRVRTID>
<XFERINFO>
<ACCTFROM>
<ACCTID>XXXXXXACCTID>
<NAME>XXXX信息技术股份有限公司NAME>
<CITY>上海CITY>
ACCTFROM>
<ACCTTO INTERBANK="N" LOCAL="Y">
<ACCTID>XXXXXACCTID>
<NAME>XXXNAME>
<BANKDESC>乌鲁木齐市BANKDESC>
<CITY>上海黄浦区CITY>
ACCTTO>
<CHEQUENUM>8758456CHEQUENUM>
<CURSYM>RMBCURSYM>
<TRNAMT>16.00TRNAMT>
<PURPOSE>FKB_DCXJXL1904260004_000145PURPOSE>
XFERINFO>
<XFERPRCSTS>
<XFERPRCCODE>PAYOUTXFERPRCCODE>
<DTXFERPRC>2019-04-26 16:42:10DTXFERPRC>
<MESSAGE>交易成功MESSAGE>
XFERPRCSTS>
XFER>
XFERLIST>
XFERINQRS>
XFERINQTRNRS>
SECURITIES_MSGSRSV1>
这个接口不管怎么查询,返回的最终结果都是PAYOUT
,下面我们再看下3.6账户余额和交易流水分页查询
中查询到的交易信息
<SECURITIES_MSGSRSV1>
<SCUSTSTMTTRNRS>
<TRNUID>190426224007047_3.6_1_9469TRNUID>
<STATUS>
<CODE>0CODE>
<SEVERITY>INFOSEVERITY>
STATUS>
<SCUSTSTMTRS>
<CURDEF>RMBCURDEF>
<ACCTFROM>
<ACCTID>XXXXXACCTID>
ACCTFROM>
<TRANLIST MORE="N">
<DTSTART>2019-04-26DTSTART>
<DTEND>2019-04-26DTEND>
<STMTTRN>
<SRVRTID>9999A5OASRVRTID>
<TRNTYPE>DEBITTRNTYPE>
<TRNCODE>227TRNCODE>
<DTACCT>2019-04-26T16:42:10DTACCT>
<TRNAMT>1.00TRNAMT>
<BALAMT>49974.00BALAMT>
<CURRENCY>RMBCURRENCY>
<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>2019042600305614580000001ATTACHINFO>
<HXJYLSBH>H00100201904260010541383430000HXJYLSBH>
<SUMMNAME>收费SUMMNAME>
<SUMMDESC>收费SUMMDESC>
<PURPOSE>企业网银转账手续费;PURPOSE>
<BRANCHNO>21168BRANCHNO>
<CHANNELCODE>204CHANNELCODE>
<CASHFLAG>1CASHFLAG>
<CBBZ>0CBBZ>
<BCZBZ>0BCZBZ>
<ROUTECHOICE>ROUTECHOICE>
<BIZREF>BIZREF>
<TEXT1>TEXT1>
<TEXT2>TEXT2>
<TEXT3>TEXT3>
STMTTRN>
<STMTTRN>
<SRVRTID>9999A5OASRVRTID>
<TRNTYPE>DEBITTRNTYPE>
<TRNCODE>231TRNCODE>
<DTACCT>2019-04-26T16:42:10DTACCT>
<TRNAMT>16.00TRNAMT>
<BALAMT>49958.00BALAMT>
<CURRENCY>RMBCURRENCY>
<MEMO>MEMO>
<CORRELATE_ACCTID>XXXXXCORRELATE_ACCTID>
<CORRELATE_NAME>XXXCORRELATE_NAME>
<CHEQUENUM>118758456CHEQUENUM>
<BILLTYPE>BILLTYPE>
<BILLNUMBER>BILLNUMBER>
<CORRELATE_BANKNAME>乌鲁木齐市CORRELATE_BANKNAME>
<CORRELATE_BANKCODE>CORRELATE_BANKCODE>
<BUSINESSTYPE>BUSINESSTYPE>
<ATTACHINFO>2019042600305614588000001ATTACHINFO>
<HXJYLSBH>H00100201904260010541383430000HXJYLSBH>
<SUMMNAME>汇款SUMMNAME>
<SUMMDESC>网上汇款SUMMDESC>
<PURPOSE>FKB_DCXJXL1904260004_000145PURPOSE>
<BRANCHNO>21168BRANCHNO>
<CHANNELCODE>204CHANNELCODE>
<CASHFLAG>1CASHFLAG>
<CBBZ>0CBBZ>
<BCZBZ>0BCZBZ>
<ROUTECHOICE>4ROUTECHOICE>
<BIZREF>BIZREF>
<TEXT1>TEXT1>
<TEXT2>TEXT2>
<TEXT3>TEXT3>
STMTTRN>
TRANLIST>
<LEDGERBAL>
<BALAMT>49958.00BALAMT>
<DTASOF>2019-04-26DTASOF>
LEDGERBAL>
<AVAILBAL>
<BALAMT>49958.00BALAMT>
<DTASOF>2019-04-26DTASOF>
AVAILBAL>
SCUSTSTMTRS>
SCUSTSTMTTRNRS>
SECURITIES_MSGSRSV1>
下面就是同样通过3.6账户余额和交易流水分页查询
中查询到的冲账信息
<SECURITIES_MSGSRSV1>
<SCUSTSTMTTRNRS>
<TRNUID>190428154005926_3.6_1_9957TRNUID>
<STATUS>
<CODE>0CODE>
<SEVERITY>INFOSEVERITY>
STATUS>
<SCUSTSTMTRS>
<CURDEF>RMBCURDEF>
<ACCTFROM>
<ACCTID>216170100100279407ACCTID>
ACCTFROM>
<TRANLIST MORE="N">
<DTSTART>2019-04-28DTSTART>
<DTEND>2019-04-28DTEND>
<STMTTRN>
<SRVRTID>07020004SRVRTID>
<TRNTYPE>DEBITTRNTYPE>
<TRNCODE>144TRNCODE>
<DTACCT>2019-04-28T10:39:56DTACCT>
<TRNAMT>-16.00TRNAMT>
<BALAMT>49974.00BALAMT>
<CURRENCY>RMBCURRENCY>
<MEMO>MEMO>
<CORRELATE_ACCTID>XXXXXCORRELATE_ACCTID>
<CORRELATE_NAME>XXXCORRELATE_NAME>
<CHEQUENUM>118758456CHEQUENUM>
<BILLTYPE>BILLTYPE>
<BILLNUMBER>BILLNUMBER>
<CORRELATE_BANKNAME>乌鲁木齐市CORRELATE_BANKNAME>
<CORRELATE_BANKCODE>CORRELATE_BANKCODE>
<BUSINESSTYPE>BUSINESSTYPE>
<ATTACHINFO>2019042800323201412000001ATTACHINFO>
<HXJYLSBH>H00100201904280011321490470000HXJYLSBH>
<SUMMNAME>冲账SUMMNAME>
<SUMMDESC>隔日冲账SUMMDESC>
<PURPOSE>无收款行PURPOSE>
<BRANCHNO>21168BRANCHNO>
<CHANNELCODE>101CHANNELCODE>
<CASHFLAG>1CASHFLAG>
<CBBZ>3CBBZ>
<BCZBZ>0BCZBZ>
<ROUTECHOICE>4ROUTECHOICE>
<BIZREF>BIZREF>
<TEXT1>TEXT1>
<TEXT2>TEXT2>
<TEXT3>TEXT3>
STMTTRN>
<STMTTRN>
<SRVRTID>07020004SRVRTID>
<TRNTYPE>DEBITTRNTYPE>
<TRNCODE>144TRNCODE>
<DTACCT>2019-04-28T10:39:56DTACCT>
<TRNAMT>-1.00TRNAMT>
<BALAMT>49975.00BALAMT>
<CURRENCY>RMBCURRENCY>
<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>2019042800323201412000002ATTACHINFO>
<HXJYLSBH>H00100201904280011321490470000HXJYLSBH>
<SUMMNAME>冲账SUMMNAME>
<SUMMDESC>隔日冲账SUMMDESC>
<PURPOSE>无收款行PURPOSE>
<BRANCHNO>21168BRANCHNO>
<CHANNELCODE>101CHANNELCODE>
<CASHFLAG>1CASHFLAG>
<CBBZ>3CBBZ>
<BCZBZ>0BCZBZ>
<ROUTECHOICE>ROUTECHOICE>
<BIZREF>BIZREF>
<TEXT1>TEXT1>
<TEXT2>TEXT2>
<TEXT3>TEXT3>
STMTTRN>
TRANLIST>
<LEDGERBAL>
<BALAMT>49975.00BALAMT>
<DTASOF>2019-04-28DTASOF>
LEDGERBAL>
<AVAILBAL>
<BALAMT>49975.00BALAMT>
<DTASOF>2019-04-28DTASOF>
AVAILBAL>
SCUSTSTMTRS>
SCUSTSTMTTRNRS>
SECURITIES_MSGSRSV1>
可以看到交易发生日期是2019-04-26
,实际冲账日期是2019-04-28
,虽然银行方面没能指出冲账和交易报文可以通过哪些字段进行对应,但对比两份报文,可以发现虽然交易的HXJYLSBH
与冲账的HXJYLSBH
不同,但两者的CHEQUENUM
、CORRELATE_ACCTID
、CORRELATE_NAME
这三者却是完全一致的,TRNAMT
则是完全相反,另外冲账退回的手续费,其HXJYLSBH
与冲账记录的HXJYLSBH
也是完全一致的,所以到此我们也就知道了冲账应该如何关联到原始交易记录
CHEQUENUM
进行关联,如果觉得不够保险,可以增加CORRELATE_ACCTID
、CORRELATE_NAME
、TRNAMT
三者共同判断HXJYLSBH
进行关联接下来我们就是需要在CIBTransactionHelper
中增加冲账相关的查询代码,代码涉及的SDK在此处下载,完整的代码如下,如果需要了解各个接口说明,则可以查看此篇内容
///
/// 兴业银行交易辅助类
///
public class CIBTransactionHelper
{
private long _cid;
private string _userId;
private string _pwd;
private ICIBTransactionPurposeBuilder _buider;
///
/// 转账对应的SUMMNAME
///
public static string TransactionSummaryName = "汇款";
///
/// 退票对应的SUMMNAME
///
public static string RefundSummaryName = "汇出退回";
///
/// 手续费对应的SUMMNAME
///
public static string ChargesSummaryName = "收费";
///
/// 冲账对应的SUMMNAME
///
public static string RubricSummaryName = "冲账";
///
/// 构造函数
///
/// 兴业银行银企直联客户号
/// 兴业银行银企直联登录用户名
/// 兴业银行银企直联登录密码
/// 前置机域名,默认为127.0.0.1
/// 前置机端口,默认为8007
/// 转账交易用途构建实现,如果不传则使用默认实现
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);
}
///
/// 兴业银行银企直联客户端
///
public ICIBClient Client { get; set; }
///
/// 生成一个用于查询的TRNUID,注意转账之类的业务切记不要采用此方法获取TRNUID
///
///
///
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);
}
///
/// 获取兴业银行3.6查询接口请求主体
///
///
///
///
///
///
///
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
}
}
}
};
}
///
/// 获取退票记录
///
///
///
///
///
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;
}
///
/// 获取交易记录(含手续费、冲账等)
///
///
///
///
///
public IList<STMTTRN> GetTransactionRecords(string acctid, DateTime dtStart, DateTime dtEnd)
{
return this.GetRecords(acctid, dtStart, dtEnd, 1);
}
///
/// 根据退票记录获取其对应的交易记录
///
/// 退票流水
/// 当前退票属于哪个账号
/// 若需自动查询交易流水时,查询几天内的交易流水,默认按兴业银行文档设置为2天
/// 交易流水,默认为null,代表按退票流水自动查询,如果不为null则与退票流水进行对比
/// Key为交易流水id,Tuple.Item1为交易流水,Tuple.Item2为退票流水
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;
}
///
/// 根据交易流水获取对应的交易记录及手续费
///
/// 交易流水
/// Key为交易流水id,Tuple.Item1为交易流水,Tuple.Item2为手续费
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;
}
///
/// 根据交易流水获取其内包含的冲账记录及冲账退回的手续费
///
/// 交易流水
/// Tuple.Item1为冲账交易流水,需按其值 凭证代号与历史交易记录CHEQUENUM的进行比较,Tuple.Item2为冲账时退回的手续费
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;
}
///
/// 根据冲账记录获取其对应的交易记录
///
/// 冲账流水及冲账手续费
/// 当前冲账属于哪个账号
/// 若需自动查询交易流水时,查询几天内的交易流水,默认设置为3天
/// 交易流水,默认为null,代表按退票流水自动查询,如果不为null则与退票流水进行对比
/// Key为交易流水id,Tuple.Item1为交易流水,Tuple.Item2为冲账流水,Tuple.Item3为冲账手续费
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;
}
}
///
/// 兴业银行交易流水用途构建约束接口
///
public interface ICIBTransactionPurposeBuilder
{
///
/// 根据内部系统业务Id构建Purpose
///
///
///
string GetPurpose(string id);
///
/// 从交易流水Purpose中获取内部系统业务Id,注意此处Purpose应当为网上汇款交易流水中的PURPOSE
///
///
///
string GetIdFromPurpose(string purpose);
///
/// 当前PURPOSE是否符合标准
///
///
///
bool IsCorrectPurpose(string purpose);
}
///
/// 兴业银行交易流水用途构建默认实现
///
public class CIBTransactionPurposeBuilder : ICIBTransactionPurposeBuilder
{
///
/// 从交易流水Purpose中获取内部系统业务Id,注意此处Purpose应当为网上汇款交易流水中的PURPOSE
///
///
///
public virtual string GetIdFromPurpose(string purpose)
{
return purpose;
}
///
/// 根据内部系统业务Id构建Purpose
///
///
///
public virtual string GetPurpose(string id)
{
return id;
}
///
/// 当前PURPOSE是否符合标准
///
///
///
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
并不是真正的最终状态,出纳等网银操作用户还是可以通过一些操作,在网银中将该笔单据回归到正常可支付状态并进行支付,这样的话,就可能存在系统中支付状态与网银中支付状态不一致,这个风险性还是需要注意的。