支付系统注意事项-工作记录
文章目录
- 前言
- 支付系统注意事项-工作记录
- 1. 注销
- 2. 转账
- 4. 结算中心、跨机构转账示例
- 5. 交易中心
- 5.1. 转账前校验(本行 -> 他行)
- 5.2. 转账逻辑(本行 -> 本行)
- 5.3. 事务开发逻辑
- 5.4. 交易中心对外返回状态
- 6. 资金中心
- 6.1. 资金中心 - 银行体系
- 6.2. 资金中心-交易所
- 6.3. 资金中心-交易所与银行进行对比
- 7.利息相关
前言
如果您觉得有用的话,记得给博主点个赞,评论,收藏一键三连啊,写作不易啊^ _ ^。
而且听说点赞的人每天的运气都不会太差,实在白嫖的话,那欢迎常来啊!!!
支付系统注意事项-工作记录
1. 注销
注销流程需设置预注销状态,目的是在结息完成后切断交易入口,禁止后续交易进入,全链路交易接口增加状态校验兜底拦截入账。
注销入口增加账户粒度分布式锁,用于保证同一账户注销请求串行执行。锁仅覆盖状态校验、状态迁移、注销流水生成等短临界区,不包裹整个注销流程,避免长时间占用锁资源。
分布式锁采用看门狗续期机制:
- 续期周期设置为锁过期时间的 1/3;
- 设置最大锁持有时间上限;
- 禁止无限续期;
- 防止线程异常导致锁长期占用。
注销请求生成唯一注销流水号(closeFlowNo)实现防重放、幂等控制,结合账户锁双重避免并发场景下重复注销、重复资金转移,关于分布式锁和流水号防重放,可以根据具体的情况进行适当的调整和删减,避免过渡设计。
账户状态更新操作必须携带前置状态作为更新条件(乐观锁控制),例如更新为预注销时 where 账户状态=正常、更新为已注销时 where 账户状态=预注销,避免并发下状态被乱覆盖,保证状态流转严格有序。
在途交易校验:此在途交易是指因为结算钱包注资环节出现问题,导致轧差保证金不足,不允许跨行转账等等,会导致交易阻塞,会有多余交易在等待。
示例:
- 结算钱包注资失败;
- 轧差保证金不足;
- 跨行转账阻塞;
- 清算处理中交易;
注意避免长事务:长事务中修改数据会导致锁表,且数据库连接长时间不释放,高并发下容易打满数据库连接池。因此整体拆分为两段独立短事务,推荐采用事务消息(可靠事务 MQ)替代同步 RPC:本地短事务完成预注销状态更新、注销任务记录落库后,才投递转账事务消息;消费者消费消息后执行资金转出 RPC,转账完成再新开独立短事务更新账户为已注销、清零余额。 所有表变更操作均拆分至独立短事务执行,确保事务粒度最小化。
预注销状态生效后限制:停止自动结息、停止利息计提、关闭主动转账、代扣、代付、签约扣款、线上开户绑定、理财申购、存款存入所有资金流入流出入口,仅保留查询、余额提现(注销资金划转)只读 / 出账单一通道。
无论正向交易是注销前还是注销中产生,账户一旦变为「已注销」,交易冲正接口直接返回失败,不允许反向资金退回注销账户,若发生误转账至注销户,走运营人工划回流程,系统无自动冲正能力;
2. 转账
所有的转账金额交易都要走交易中心模块,主要为了后面的对账,如果不走交易中心模块,对账就会无法进行。
4. 结算中心、跨机构转账示例
┌─────────────────────────────────────────────────────┐ │ 结算钱包 │ │ ┌──────────────┐ ┌──────────────────────────────┐ │ │ │ 可用余额 │ │ 圈存余额 │ │ │ │ │ │ ┌────────────────────────┐ │ │ │ │ │ │ │ 轧差保证金 │ │ │ │ │ │ │ │ ┌──────────────────┐ │ │ │ │ │ │ │ │ │净借记限额轧差保证金│ │ │ │ │ │ │ │ │ └──────────────────┘ │ │ │ │ │ │ │ │ 其他保证金 │ │ │ │ │ │ │ └────────────────────────┘ │ │ │ │ │ │ 其他圈存余额 │ │ │ └──────────────┘ └──────────────────────────────┘ │ └─────────────────────────────────────────────────────┘ ┌───────────────────────────────────────────────────────────────────────────────┐ │1. 结算钱包分类规则 │ │ 结算钱包=可用余额 + 圈存余额 │ │ • 零售交易:单笔记账扣减净借记限额,每小时轧差结算一次 │ │ • 批发交易:实时结算,直接扣减可用余额 │ ├───────────────────────────────────────────────────────────────────────────────┤ │2. 圈存余额分层 │ │ 圈存余额=轧差保证金 + 其他圈存余额 │ │ 轧差保证金=净借记限额轧差保证金 + 其他保证金 │ ├───────────────────────────────────────────────────────────────────────────────┤ │3. 净借记限额轧差保证金业务逻辑 │ │ • 余额金额=净借记限额额度 │ │ • 小时轧差扣款顺序:优先使用可用余额,余额不足再扣轧差保证金 │ │ • 支持自动注资,按预设比例自动注资 │一、结算中心资金分层结构
结算中心整体分为两大块:可用余额、圈存余额
- 可用余额:批发业务实时结算资金池,批发对外付款、零售轧差清算扣款均从这里支出
- 圈存余额(监管硬性留存,固定不动)
- 轧差保证金(定额,管控零售交易额度)
- 净借记限额轧差保证金:金额等于净借记限额额度
- 轧差扣款优先级:小时轧差优先消耗可用余额;可用余额不足时,再动用轧差保证金;系统日间注资会按预设比例自动调平两类资金
- 其他圈存余额:监管要求留存的其余保证金资金
二、两类交易结算规则
- 轧差保证金(定额,管控零售交易额度)
- 零售交易
- 单笔占用净借记限额额度,按小时批量轧差结算
- 交易额度上限等于轧差保证金定额,累计占用超限则直接拦截交易
- 批发交易
- 实时结算,资金直接从可用余额扣减划转对手机构
三、轧差保证金核心特性
- 实时结算,资金直接从可用余额扣减划转对手机构
- 定额圈存资金,是存管所硬性兜底要求,必须足额留存才能开展对应规模零售业务
- 账面本金永久不变,仅作为零售交易额度管控标尺;实际对外清算扣款只会消耗可用余额
- 小时轧差清算举例:零售累计对外支出达保证金全额时,系统从可用余额划出等额资金完成清算,可用余额减少,轧差保证金金额维持原值不变
四、结算中心基础定位
结算中心核心用途:处理跨机构转账业务
五、日间注资 & 资金过夜管控逻辑 - 核心规则:资金不过夜,规避隔夜资金产生利息成本
- 日间流程:每日固定时间节点向钱包注入运营资金,用于当日批发、零售交易清算
- 日终回收:当日业务时段结束后,钱包内未消耗完毕的可用资金全额原路归还,不留存过夜
六、业务实操示例
账户总资金 200 万,系统自动圈存 10 万定额轧差保证金,剩余 90 万划入可用余额: - 批发付款:实时从 90 万可用余额扣款
- 零售交易:累计对外支付上限 10 万,额度累加超限拦截
- 小时轧差清算:零售累计支出 10 万,从可用余额划出 10 万清算,可用余额变为 80 万,轧差保证金仍为 10 万
- 当日结束:剩余未使用可用余额全部退回,无资金过夜计息。
七、注资逻辑
注资的逻辑是,资金不过夜,每一天固定时间节点预注资,示例,当天5点到明天5点会花这笔预注资的资金进行结算,同时达到固定注资时间节点,会把昨天没花完的钱还回来,防止资金过夜,产生利息。
5. 交易中心
5.1. 转账前校验(本行 -> 他行)
1、收款人开立运营机构是否在黑名单上。
2、使用请求报文中的机构号查询所在机构,将查询到的所在机构与报文中的所在机构进行对比。
3、检验报文中的业务类型,比如不能为投资理财、非202时校验收款人姓名不为空等等…
4、根据业务种类不同,可能会有不同的交易。
5、校验金额格式(小数点后两位)等等。
6、校验上传报文中凭证类型。
7、校验上传报文中的发起机构必须是本行机构。
8、根据上传报文中的限额开关判断是否进行限额校验。
9、校验接受运营机构。
5.2. 转账逻辑(本行 -> 本行)
| 表 | 作用 |
|---|---|
| 金融交易登记簿 | 渠道交易生命周期 |
| 交易主流水 | 一次业务交易 |
| 交易子流水 | 拆分后的资金动作 |
| 核心调用流水表 | 用于RPC追踪、超时补偿、对账 |
1、通用幂等校验、防重放验证,根据幂等原值查询报文流水表,如果存在则组装历史报文返回;不存在则插入报文流水表。
2、业务幂等校验、防重放验证,根据外围系统编号、外围渠道编码、外围系统流水号、外围系统日期查询金融交易登记簿。
建议基于: 外围系统编号; 外围渠道编码; 外围系统流水号; 外围系统日期;建立唯一索引,防止并发重复交易,如果存在则直接返回历史交易结果。
3、存储金融交易登记簿,初始化状态为:INIT
4、新增交易主流水表。
5、新增交易子流水表(转账子交易),初始化状态:INIT
6、转账前置校验:
- 实名认证校验;
- 钱包密码校验;
- 收款方账户存在校验;
- 收款方是否属于本行校验;
- 转出方限额校验;
- 转入方限额校验;
- 敏感词校验;
- 交易风控校验;
- 黑名单校验;
- 账户状态校验;
- 账户冻结状态校验;
- 反洗钱校验;
- 反欺诈校验。
校验失败:
- 更新交易子流水状态 FAIL;
- 更新交易主流水状态 FAIL;
- 更新金融交易登记簿状态 FAIL;
- 组装失败报文返回。
注:很多时候,这里很多校验可以算作风控校验,同时新增交易子流水(风控接口),风控调用成功后更新交易子流水(风控)。
7、调用资金中心前操作:
(1)新增核心调用流水表,默认状态:INIT,用于:RPC链路追踪,超时补偿,对账。
(2)更新金融交易登记簿: (当前处理步骤,核心流水号,更新时间)。
8、调用资金中心转账接口。
9、调用资金中心后处理:
(1)应答成功:
- 更新核心调用流水 SUCCESS;
- 更新交易子流水 SUCCESS;
- 更新交易主流水 SUCCESS;
- 更新金融交易登记簿 SUCCESS;
- 更新核心应答字段。
(2)明确失败(余额不足、账户冻结、风控拒绝、超限等): - 更新核心调用流水 FAIL;
- 更新交易子流水 FAIL;
- 更新交易主流水 FAIL;
- 更新金融交易登记簿 FAIL;
- 记录失败原因。
(3)结果未知(Connection reset、Socket timeout、Gateway timeout、RPC timeout): - 更新核心调用流水 PROCESSING;
- 更新交易子流水 PROCESSING;
- 更新交易主流水 PROCESSING;
- 更新金融交易登记簿 PROCESSING;
- 进入异步查询、补偿、对账流程。
10、组装应答报文返回。
5.3. 事务开发逻辑
交易受理事务: 同一个事务。
1、金融交易登记簿2、交易主流水3、交易子流水4、状态初始化目的:保证交易被系统受理后,一定能找到这笔交易。
交易执行事务结构如下:
事务1 ↓ 无事务RPC ↓ 事务2避免RPC导致长事务。
5.4. 交易中心对外返回状态
SUCCESS 成功:
交易中心||转账请求|资金中心||扣款成功|入账成功|返回成功FAIL 明确失败:
资金中心明确返回: * 余额不足; * 账户冻结; * 收款账户不存在; * 风控拒绝; * 超限。PROCESSING 处理中:
网络异常导致结果未知: 如收到下面几种状态: * Connection reset; * Socket timeout; * Gateway timeout; * RPC超时; * 下游响应丢失。6. 资金中心
6.1. 资金中心 - 银行体系
资金模型: 账户余额 + 账务记账 + 会计一致性
设计重点: 账户模型 + 余额并发控制 + 账务流水 + 幂等 + 事务一致性。
余额更新:
update accountsetbalance=balance-100 whereaccount_id=1and balance>=100基于数据库的原子性行为进行扣款或入账。
6.2. 资金中心-交易所
更关注交易过程中资金状态变化
资金模型:Balance + Locked
每个用户的每种币种有一个 Account 记录,资金分为两部分:
typeAccount struct{CommonModel UserId int CurrencyId int Balance decimal.Decimal // 可用余额 Locked decimal.Decimal // 冻结金额}- Balance:可用余额,可以提现或下单
- Locked:冻结金额,已被挂单占用
下单时:Balance -= locked, Locked += locked(资金从可用转入冻结)
成交时:Locked -= traded, 对手 Account.Balance += received(冻结资金结算给对方)
撤单时:Locked -= locked, Balance += locked(冻结资金退回可用)
核心目标:
- 支撑高频撮合;
- 支撑资产冻结与释放;
- 支撑订单生命周期资金流转;
- 保证资产状态正确。
因此交易所不用大量for update ,而是使用乐观锁 + 重试机制来保证,因为for update会导致:锁竞争 吞吐下降 数据库热点。
核心关注:
- 可用资产;
- 冻结资产;
- 订单状态;
- 成交结算;
交易所账户更新采用:
Optimistic Concurrency Control
核心思想:不阻塞,不等待,写入时检查是否被修改。
这是整个账务系统最精妙的设计。传统的做法是 SELECT … FOR UPDATE 加悲观锁,然后更新余额。goDCE 没有这样做,而是使用了一种条件 INSERT 模式:
func optimisticallyLockAccountAndCreate(db *utils.GormDB, balance, locked decimal.Decimal, attrs map[string]string)(err error){sql :=`INSERT INTO account_versions(...)SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? FROM accounts WHERE accounts.balance=? AND accounts.locked=? AND accounts.id=?`result :=db.Exec(sql,..., balance, locked, attrs["account_id"])ifresult.RowsAffected!=1{err=fmt.Errorf("Insert row failed.")}return}这段 SQL 的精妙之处在于:INSERT 的 SELECT 子句中带有 WHERE accounts.balance = ? AND accounts.locked = ? 条件。 这意味着:
- 在执行账户操作前,先读取当前的 balance 和 locked
- 在 INSERT account_version 时,检查这两个值是否还是之前读取的值
- 如果在此期间有其他事务修改了账户余额,WHERE 条件不满足,RowsAffected = 0,操作失败
这就是乐观并发控制(OCC)——不加锁,但在写入时检查数据是否被修改过。如果被修改了,就重试。
6.3. 资金中心-交易所与银行进行对比
| 比对项 | 交易所账户系统 | 银行资金中心 |
|---|---|---|
| 核心对象 | 用户资产 | 账户资金 |
| 余额模型 | Balance+Locked | 余额+账务 |
| 冻结 | 订单冻结 | 资金冻结 |
| 一致性 | OCC重试 | 事务+记账 |
| 流水 | 资产变更链 | 会计流水 |
| 高并发 | 极高 | 中高 |
| 核心风险 | 资产错乱 | 资金不平 |
7.利息相关
结息周期区分:
活期:按自然季度 / 月度结息(例如 3/6/9/12 月 21 日日终批量结息);
定期:到期一次性结息、自动转存结息;
协定存款、通知存款按产品约定计息周期批量执行;
计息前置账户过滤:
过滤以下账户不参与批量结息:已注销、预注销、冻结销户、状态异常、无存款余额账户;仅状态为「正常」账户参与计息;
计息逻辑:
按账户存款类型、基准利率 + 浮动利率、实际存款天数计息,计息基数取计息日前一日终账户日均余额 / 账面余额;产生应付利息暂存内部过渡户,结息完成批量入账至客户账户余额。
计提:
按照约定时间将利息计息到余额中。