系统里的资金交易如何与银行通道对账?我们这样设计
对账模型
{对账模型} | ||||||
我方通道交易流水 | ||||||
↑↓ | ||||||
↑↓ | → → |
对账结果 | → → |
差异账 | ||
↑↓ | ||||||
拉取通道对账单 | → → |
通道对账单 |
【FAQ】
【Q】依据支付时间还是支付完成时间对账?
【A】按支付交易完成时间做对账。
【Q】银行单边账是怎么产生的?
【A】case1:银行不返回交易完成时间。这种情况下,在日切临界点,我们0:00前查询通道,0:00后收到通道响应,我们按我们服务器时间更新交易完成时间。这就出现我司与通道侧支付完成时间不一致的情况。case2:再一种银行单边由不靠谱程序产生。我司调用通道侧时使用了事务。结果因调用超时通道侧落单,我司没落单,从而出现通道侧单边。
统一数据词典
T日:T日指交易日。银行系统在T+1日生成T日交易的账单,因此,对账发生在T+1日
账单/对账单:bill
银行账单/银行对账单:bank bill batch
对账:bill check
对账批次:bill check batch
明细:detail
差异:diff
定时任务:job。定时任务命名以-Job结尾,如对账Job命名为BankBillCheckJob
所涉及到的系统现有的词汇包括↓
通道/银行:bank
交易:trans
金额:amount,以“分”为单位存储
交易笔数/交易量:qty
数据表设计
数据表 | 表名 | comment | 主要字段 | |
---|---|---|---|---|
银行账单 | 银行账单批次表 | bank_bill_batch | 银行账单表,每银行每天一条记录 |
batchNo-账单批次号(系统生成,PK) bankId-系统里记录的银行通道编号 bankBatchNo-varchar-银行侧对账单批次号/对账文件名(没有则为空) trans_date-int-交易日期/yyyyMMdd -- total_amount-总金额 total_qty-总笔数 createTIme-记录创建时间,即账单的首次拉取时间 updateTime-最后更新时间
|
银行账单交易流水 | bank_bill_detail | 银行账单交易明细 |
id-PK batchNo-账单批次号 bankId-系统里记录的银行通道编号 transOrderNo-系统请求银行的交易流水号(发生银行单边时,此字段为空) bankOrderNo-银行侧交易单号 transState-银行侧交易状态(程序里转换为系统里的交易状态) transAmount-银行侧交易金额,以分为单位存储 payeeAccount-银行侧收款方账号(银行卡号) transTime-银行侧交易完成时间 createTIme-记录创建时间 |
|
银行对账 | 银行对账结果批次表 | bank_bill_check_batch | 与bank_bill_batch一对一 |
batchNo-批次号(PK,来自bank_bill_batch) bankId-系统里记录的银行通道编号 checkState-对账处理状态 -(I-初始待对账/P-系统对账中/D-差异待处理/S-对账完成) check_date-对账时间 trans_date-交易日期(针对哪天的交易做的对账)
|
银行对账结果明细表 | bank_bill_check_detail | 银行账单与系统交易对账结果 |
id-PK transOrderNo-系统请求银行的交易流水号 bankOrderNo-银行侧交易单号 batchNo-批次号(来自bank_bill_batch) bankId-银行编号 双方记录的交易状态、收款人账号、金额、交易完成时间 checkState-对账处理状态 -(I-初始待对账/D-差异待处理/S-对账完成) diff_field-存在差异的字段,STATE-状态 / AMOUNT-金额 / BANKONLY-银行单边 / SYSONLY系统单边 check_time - 对账时间 diff_process_time - 差异处理时间 diff_process_remark - 差异处理备注
|
|
银行对账差异处理记录(Optional) | bank_bill_check_diff_process | 记录差异账的处理 | (暂略) |
银行对账结果明细表->数据样例
id |
系统请求银行 的交易流水号 |
银行侧 交易单号 |
账单批次号 /银行编号 |
系统/银行侧 收款人账号 |
系统/银行侧 交易金额 |
系统/银行侧 交易完成时间 |
系统/银行侧 交易状态 | 对账状态 | 差异字段 | 对账时间 | 差异处理时间 |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 2023120700901 | 231211110575607 |
B20231207PAB /PAB |
6217***1069 /6217***1069 |
10000/10000 |
2023-12-07 11:11:11 / 2023-12-07 11:11:11 |
S/F | D | STATE |
2023-12-08 01:06:00 |
|
2 | 231211110575608 |
B20231207PAB /PAB |
- /6217***5638 |
-/1 |
/- |
-/F | S | BANKONLY |
2023-12-08 01:06:00 |
2023-12-08 09:30:00 |
|
3 | 2023120700902 |
B20231207PAB /PAB |
6217***1069 /- |
1/- |
- /- |
F/- | D | SYSONLY |
2023-12-08 01:06:00 |
||
4 | 2023120701094 | 231211110575613 |
B20231207PAB /PAB |
9558****0631 /9558****0631 |
500/500 |
- /- |
F/F | S | - |
2023-12-08 01:06:00 |
- |
5 | 2023120701950 | SCLY0906231725 |
B20231207ALI /PAB |
6228***7074 /6228***7074 |
12380/12380 |
2023-12-07 02:52:01 / 2023-12-07 02:52:01 |
S/S | S | - |
2023-12-08 01:06:00 |
- |
如何触发对账?
毋庸置疑,实现方案是使用定时任务(JOB)。每家银行出账单的时间是不一样的,例如有的是凌晨3:00生成,有的是上午8:00生成。因此,可以每隔20分钟触发JOB,调用各银行的API获取账单,直到拉取到,然后再进行对账。
拉取银行账单JOB | 银行对账JOB |
---|---|
↓ | ↓ |
获取需要拉取账单的银行通道列表-bankList 依次遍历 bankList |
查询bank_bill_batch及bank_bill_check_batch,获取待对账的账单批次-batchList 依次遍历 batchList |
↓ | ↓ |
拉取银行账单方法 (bank) { if(已经拉取到) return; 加锁,防重复执行控制 组装银行请求参数,拉取银行账单 解析数据,持久化入库,包括银行账单批次表和账单明细表(事务) } |
银行对账方法(){ 加锁,防重复执行控制 更新batch的checkState=P 对该批次里的交易与系统里的T日交易进行check 对账完成后,标记batch的checkState,存在差异交易则为D,否则为S |
实现要点
🍀业务防重复执行锁
key:日期+银行编号
TTL:拉取银行账单 与 银行对账,保证这两者在T+1日均执行一次即可。 因此可设置TTL=24h
🍀对账及时性保证
银行账单是在T+1日生成T日账单。不同银行的对账单的具体生成时间点有所不同,有的是01:00,有的可能是09:00,甚至有的是中午11:00。因此,定时JOB的开始时间可以从00:30开始,每隔半小时触发。银行账单一旦拉取完成,后续JOB触发时不再重复拉取。
上面表格里的方案是两个JOB,即将拉取银行账单JOB与银行对账JOB分开了。 这是有缺点的。——可能出现对账不及时的情况(银行账单都拉取到了,系统却迟迟没有对账)(见后文【花絮】)。
那么,如何优化呢? 保留一个JOB即可。 拉取银行账单的业务完成后,则异步触发银行对账,保证银行对账及时性。
🍀哪些通道需要对账?
最“朴实”的实现方式,不外乎是读取通道表得到bankList,针对这个集合里的每个通道做对账。
注意上面的朴实是加了双引号的。
比较好的姿势,是根据T日的通道交易流水,筛选出来真正需要对账的bankList。
【花絮】
我组的对账起初也是2个JOB(开发人员错误的以为分开为2个JOB才叫解耦),其中,拉取银行账单JOB是整点每隔1小时执行(cron=0 0 1-12/1 * * ?),银行对账JOB是整半点每隔1小时执行(cron=0 30 1-12/1 * * ?)。后来,产品经理和结算人员反馈银行账单拉取不及时,对账也不及时。怎么办?开发人员就反复调整这2个cron表达式,让其触发时间间隔更接近。 例如,变更银行对账JOB的cron=cron=0 15 1-12/1 * * ?。 但这样依然无法根本解决对账不及时的问题。 因此,更合适的实现方案是,拉取到对账单后就异步触发对账,并且缩短JOB执行间隔为半小时。
【EOF】知识就是力量,但更重要的是...。欢迎关注我的微信公众号「靠谱的程序员」,解密靠谱程序员的日常,让我们一起做靠谱的程序员。
当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge
本文来自博客园,转载请注明原文链接:https://www.cnblogs.com/buguge/p/17891549.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体