前几天写了在兴业银行的银企直联中,如何查询手续费和退票流水,但没有完整的代码展示,所以这里再完整的提供下查询相关的代码。封装代码不涉及任何外部业务,如果你也正在接入兴业银行,且使用的开发语言是NET,那么你完全可以发挥拿来主义,完全不需要你修改一行代码!

2019-05-14补充 转账时手续费受多种因素影响,实际生产环境中,出现了仅仅因为判断时,因为系统中收款方银行开户地未填写城市名,导致判断时,错误的判断为跨行异地,即LOCAL=N,导致手续费由1元直接提升为5元

首先为了在转账时将企业内部系统业务Id作为PURPOSE,我在这里定义了一个ICIBTransactionPurposeBuilder接口,该接口的用途是用于规范IdPURPOSE互转约束,具体代码如下:

    /// <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);}

同时提供了该接口的默认实现,该实现内部其实什么都没做,且其所有方法实现都是virtual的,所以你完全可以继承该实现,重写某些你需要自定义的方法,比如IsCorrectPurpose

   /// <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;}}

接下来就是具体的CIBTransactionHelper,在贴上其代码之前,我先罗列下开发中要注意的问题点,这样就算你不是NET开发,这篇内容也会对你有一定的帮助:

  • 生成一个用于查询的TRNUID,该ID需要唯一,但又因为不需要重复使用,所以此处简单的按年月日+标签+随机数来组织
  • 3.3.6 账户余额和交易流水分页查询时,不允许当日与历史日期同查,所以此处要做判断处理,然而又因为可能业务所在服务与兴业银行银企直联服务器时间存在时间差,所以为一劳永逸的解决问题,在调用接口,直接将DTENDDTSTART设置为同一天进行查询(注意有可能一天内的交易流水有多页记录,需要按返回的MORE字段进行判断是否需要多次查询),然后外部进行循环以便执行指定日期范围的查询
  • 同一个银行账号,有可能在多个业务系统内存在交易记录,那么如何判断查询的交易流水是否是当前业务系统所属记录呢,这时候就需要ICIBTransactionPurposeBuilder了,后面会有具体的例子来展示如何处理该问题
  • 因为转账存在手续费,所以在进行退票前,业务上完全可以先将交易流水相关的一些核心信息(比如HXJYLSBH)以及其对应的手续费先通过Job等方式预先同步到本地数据库,那么在进行退票流水关联交易流水时,就不用再去通过3.3.6 账户余额和交易流水分页查询查询前几天的交易流水,而是转而进行本地查询
  • 如果你不关心手续费问题,那么你可能就不会去主动同步交易流水信息,这时候你就要按查询到的退票记录,自动推断其对应转账交易可能发生的日期,但因为实际交易时间可能发生在退票前的N天内,那么为了减少查询次数,需要将所有退票流水推断到的转账时间进行去重,然后再按结果时间范围进行3.3.6 账户余额和交易流水分页查询

2019-05-05调整 实际生产发现汇款时用于判断的BUSINESSTYPE居然为空,天坑!!!此处改为由SUMMNAME进行判断。

    using BEDA.CIB.Contracts;using BEDA.CIB.Contracts.Requests;using BEDA.CIB.Contracts.Responses;/// <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>/// 构造函数/// </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>/// 当退票时查询几天内的交易流水,默认按兴业银行文档设置为2天/// </summary>public int RefundDayDiff { get; set; } = 2;/// <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="transList">交易流水,默认为null,代表按退票流水自动查询,如果不为null则与退票流水进行对比</param>/// <returns>Key为交易流水id,Tuple.Item1为交易流水,Tuple.Item2为退票流水</returns>public IDictionary<string, Tuple<STMTTRN, STMTTRN>> GetRefundMapping(IList<STMTTRN> refundList, string acctid, 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);}var query = from refund in refundListjoin trans in transListon refund.MEMO equals trans.HXJYLSBHwhere trans.SUMMNAME == TransactionSummaryNameselect 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> refundList){var transList = new List<STMTTRN>();var timeList = refundList.Select(x => x.DTACCT.Date).Distinct().OrderBy(d => d).ToList();//虽然底层查询时是拆分成按每日查询,但因为退票需要倒查两天的交易流水,所以将日期按连续性拆分成日期范围还是有必要的var timeRange = this.GetTimeRange(timeList);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){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(this.RefundDayDiff)){//退票需要查询交易流水日期范围为交易当天或交易前一天//所以如果出现跳日,比如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是否是当前业务组织的PURPOSElist = 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 DEBUGvar tmp = groups.ToList();
#endifforeach (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;}}

要用到的BEDA.CIB可见此处,该SDK支持NET452Standard2.0版本,可以注意到CIBTransactionHelper构造函数最后一个参数是ICIBTransactionPurposeBuilder,因为在兴业银行的测试环境中,同时有其它对接用户也在进行直联测试,所以我们此时就需要自定义一个ICIBTransactionPurposeBuilder,将查询流水获取到的数据进行过滤,因为只是测试,所以在发起3.4.1转账汇款指令提交请求时,传入的PURPOSE只是简单的按特定字符前缀+日期的格式string.Format("fkb_{0:yyMMddHHmmssfff}", DateTime.Now)进行组织,所以我们也只需要继承CIBTransactionPurposeBuilder并简单的将IsCorrectPurpose给重写下就行,当然实际业务场景中你肯定不能这么任性。
2020-04-26补充:貌似中国人民银行新规,如果PURPOSE不是以汉字开头的,很可能会当日冲账

        class CustCIBTransactionPurposeBuilder : CIBTransactionPurposeBuilder{public override bool IsCorrectPurpose(string purpose){if (!string.IsNullOrWhiteSpace(purpose)){return Regex.IsMatch(purpose, @"^fkb_\d{15}$");}return false;}}

最后就是调用示例,具体如下

        const long cid = 1100343164;const string uid = "qw1";const string pwd = "a1111111";//密码错误6次账号会被永久锁定无法解锁const string ip = "127.0.0.1";const int port = 8007;const string mainAccountId = "117010100100000177";public static void TransactionHelperSample(){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 refundList = helper.GetRefundRecords(mainAccountId, new DateTime(2019, 3, 19), new DateTime(2019, 3, 19));
#if DEBUGif (refundList.Count == 0){//为方便测试,手工增加一条退票记录//fkb_190319140625534  H00100201903190004631399460000refundList.Add(new STMTTRN{DTACCT = new DateTime(2019, 3, 19),BUSINESSTYPE = CIBTransactionHelper.RefundBusinessType,HXJYLSBH = "K00100201903190004631399460000",//假编号MEMO = "H00100201903190004631399460000",SUMMNAME = "汇出退回",SUMMDESC = "汇出退回解付",PURPOSE = "账号户名不符",});}
#endif//如果你已经通过GetTransactionRecords获取并持久化了手续费及HXJYLSBH//那么下面Mappding这步就可以忽略,转为直接查本地数据库helper.RefundDayDiff = 2;//人行退票实际允许的范围是3个工作日,极端情况下会出现7个工作日//所以此处虽然兴业银行说是只要查2天范围,但此处还是允许自定义日期范围var refundDic = helper.GetRefundMapping(refundList, mainAccountId, transList);}

你可以在此处查看完整的示例代码。

兴业银行银企直联查转账手续费和退票流水(C#代码篇)相关推荐

  1. 兴业银行银企直连系统开发接口

    GitHub地址:https://github.com/wuyanfei/CIB const request = require('request') const encoding = require ...

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

    之前写过兴业银行银企直联应当如何查询手续费及退票,但事实上兴业银行还会产生冲账问题.所谓冲账,就是指支付信息错误,导致根本无法到达实际收款银行,这时候因为根本无法到达收款银行,所以也就不会有收款银行回 ...

  3. 兴业银行银企直联对接记录及SDK

    年前因为业务需要,接触了兴业银行的银企直联,因为感兴趣,所以大致搜了下国内一些银行,发现目前银行银企直联模式新接入的用户都只有前置代理机这一途径,所以兴业银行也不例外. 可在此处查看兴业银行的接口文档 ...

  4. java设计一个bank类实现银行_SAP银企直连之平安银行(ECC版)

    关于讲解SAP中国本地化银企直连系统功能,它通过ECC和S4 HANA 1909两个不同版本的演示来讲解银企直连付款相关功能实施和应用,有兴趣的可以联系微信号:timijia进行付费获取.以下资料仅供 ...

  5. SAP本地化-银企直连

    一.发展历史 2011年,在SAP ECC6 Ehp5中,通过功能增强FIN_LOC_CI_16发布中国版的银企直连(Electronic payment integration with Chine ...

  6. [转]各大银行网银转账手续费一览表

    工商:免费  农行:免费  中行:免费  建行:免费  交行:免费  招商:免费  广发:免费  广大:免费  邮政:按汇款的0.5 %收取费用,最高50元.  民生:免费  浦发:免费  中信:免费 ...

  7. SAP 深入谈谈银企直连

    一.发展历史 2011年,在SAP ECC6 Ehp5中,通过功能增强FIN_LOC_CI_16发布中国版的银企直连(Electronic payment integration with Chine ...

  8. SAP 银企直连基本开发过程及常用增强总结

    一.银企直连介绍 银企直联是一种新的网上银行系统与企业的财务软件系统在线直接联接的接入方式.银企直联通过因特网或专线连接方式,实现了银行和企业计算机系统的有机融合和平滑对接.企业通过财务系统的界面就可 ...

  9. SAP 银企直连 通过 Http Get 方式下载交易明细文件

    前言 SAP 银企直连,农业银行查询接口交易明细会生成固定格式的文本文件到前置机上面. 可以通过Http Get 方式下载. 提示:以下是本篇文章正文内容,下面案例可供参考 一.接口说明与核心代码 & ...

最新文章

  1. stackoverflow国内被墙的打开办法
  2. mov eax,dword ptr fs:[0] 指令
  3. 学python编程_少儿学Python编程的一些思考
  4. CentOS 安装MySQL(rpm)提示错误Header V3 DSA/SHA1 Signature
  5. mysql产品优化方案_mysql的优化方案
  6. 容器编排技术 -- Kubernetes 使用Service暴露应用
  7. ActiveMQ Topic发布订阅消息
  8. 链表头结点存在的意义
  9. 越是糟心时,越要用起写作这个武器
  10. MongoDB 快速入门--高级
  11. python爬虫四(cookie,代理池,模拟登录(打码))
  12. 浅析javaIO的原理过程
  13. python中的时间和时区详解(datetime / dateutil / pytz)
  14. OSI第三层:网络层功能及作用OSI第三层:网络层功能及作用
  15. 火箭十八连胜内幕大曝光(坚持看完)(搞笑)
  16. 百度蜘蛛IP大全,更新于2020年7月3日
  17. 泊松过程的概念及其例题分析
  18. R语言windows函数自动生成可视化图像画布框、使用plot函数可视化数据点图、使用type参数指定数据点为实线
  19. Win10 无线网络802.1X认证 PEAP问题
  20. .NET周报【11月第4期 2022-11-30】

热门文章

  1. matlab guide图像处理实例,现代数字图像处理技术提高及应用案例详解 MATLAB版-教科书.pdf...
  2. 解决uni-uploadFile上传图片丢失后缀名的问题
  3. python汉字转拼音首字母_python获取一组汉字拼音首字母的方法
  4. 新闻资讯博客小程序源码/支持微信、QQ、百度小程序/支持流量主
  5. 【阅读笔记】SiamMask
  6. 使用Quitter自动关闭(或隐藏)Mac上的空闲应用程序
  7. AVI文件格式及相关操作
  8. 一朵云开启智慧交通新未来
  9. 由legacy+MBR改为UEFI+GPT引导方式
  10. C语言中printf打印形式(%02X, %2X, %-2X, %.nf, %m.nf, %e, %m.ne, %2d, %-2d, %02d, %.2d)