深色模式
SBA 资金编排
本页说明
讲什么:什么是 SBA、Procedure 数据模型与核心字段、入金和出金的完整状态机(含运营关注点)、BST 银证入金/出金编排详细流程、SBA 服务 API 概览、异步任务队列与轮询机制、去重与幂等机制、冻结-转账-释放模式、Procedure 查询与定位指南,以及常见异常场景与处理方案 适合谁:需要理解内部资金编排逻辑的产品经理和运营人员 前置阅读:系统架构与数据流预计阅读:12 分钟 负责人:出入金产品团队
核心要点:SBA 是所有资金操作的中枢编排系统——每笔入金/出金都创建一条 Procedure 记录,经历"冻结→转账→释放"的标准流程。理解 Procedure 状态机是排查资金异常的基础。
什么是 Procedure
每一笔入金或出金,在 SBA 编排系统中都会创建一条 Procedure 记录。它是这笔资金操作从创建到完成(或失败/取消)的完整追踪载体——可以理解为一张"资金操作工单"。
Procedure 与业务层的关系
Procedure 不是凭空产生的——它由业务层(入金服务/出金服务)在特定时机创建:
| 业务对象 | SBA 对象 | 创建时机 | 关联方式 |
|---|---|---|---|
Apply(入金申请) | ProcedureCashDeposit | 流水匹配成功 + 满足自动入账条件后 | Apply 记录中的 sba_procedure_id 字段 |
Task(出金任务) | ProcedureCashWithdraw | 出金三步审批全部通过后(Remittance 阶段) | sba_list 关联表(Task ID ↔ Procedure ID) |
PM 视角:用户在 App 看到的是 Apply(入金)或 Task(出金),运营在 CRM 看到的也是 Apply/Task。Procedure 是"幕后角色"——只有在排查资金流转细节时才需要查看。但一旦出现"钱没到账"或"钱扣了没转出去",Procedure 就是定位问题的关键。
核心字段
| 字段 | 业务含义 | 运营常用场景 |
|---|---|---|
id | Procedure 唯一标识,全局递增 | CRM 搜索、排查问题时的主键 |
biz_type | 业务类型:cash_deposit(入金)或 cash_withdraw(出金) | 区分入金/出金 Procedure |
nn_uid | 用户 ID(牛牛号) | 关联到具体用户 |
market | 交易市场:1=港股、2=美股、4=A 股通 | BST 银证需按市场分别创建 Procedure |
trade_acc_id | 交易账户 ID | 一个用户可能有多个交易账户 |
status | 主状态——标识 Procedure 处于生命周期的哪个大阶段 | CRM 列表页的状态筛选 |
ext_status | 子状态——在同一主状态下的更细粒度追踪 | 定位"卡在哪一步"的关键 |
version | 乐观锁版本号,每次更新 +1,防止并发写覆盖 | CRM 操作报错"version conflict"时需刷新重试 |
transaction_date | 结算日(交易日格式 YYYYMMDD),决定入账归属哪个交易日 | 跨日出金延迟的原因所在 |
reason | 终态原因——仅在失败/拒绝/取消时填写 | 排查"为什么这笔出金失败了" |
Version 冲突处理
CRM 运营操作 Procedure 时偶尔会遇到"version conflict"错误。这是乐观锁机制——说明在你操作的同时,系统(定时任务、银行回调等)也在更新这条 Procedure。
处理方法:刷新页面获取最新版本,重新操作即可。如果反复冲突(>3 次),说明有并发任务在频繁更新——等待 10-30 秒后再试。绝对不要通过直接改数据库绕过 version 检查。
审计流水(Flow)
每次 Procedure 发生状态变更,SBA 都会写入一条 Flow 记录(审计日志)。CRM 中点击"查看历史"看到的操作记录就来自这张表。
| 字段 | 含义 | 运营用途 |
|---|---|---|
procedure_id | 关联的 Procedure ID | 根据 Procedure ID 查询完整变更历史 |
from_status / to_status | 变更前后的主状态 | 判断状态流转是否符合预期 |
from_ext_status / to_ext_status | 变更前后的子状态 | 精确定位变更细节 |
operator | 操作人(system = 系统自动 / staff_xxxx = CRM 运营) | 区分是系统操作还是人工操作 |
remark | 备注信息 | 人工操作时运营填写的原因说明 |
created_at | 变更时间(精确到毫秒) | 定位问题的时间线 |
Flow 记录不可修改、不可删除,是出入金操作的完整审计轨迹。
排查技巧:当 Procedure 处于异常状态时,按 created_at 降序查看 Flow 记录,找到最后一次变更——变更前的状态、操作人、时间往往能直接定位问题原因。
完整状态码表
Procedure 的全部主状态 + 子状态组合 → 出金数据字典 § SBA Procedure 状态码
入金 Procedure 状态机
入金的状态机相对简单(6 个状态),因为复杂性在于匹配引擎——匹配引擎完成后,Procedure 只负责最后的入账步骤。
状态说明与运营关注点
| 主状态 | 子状态 | 业务含义 | 终态? | 运营关注点 |
|---|---|---|---|---|
new | waiting | Procedure 已创建,等待自动/人工决策 | 否 | 正常状态,系统秒级自动处理。若长时间停留(>1 分钟),检查 SBA 服务是否正常 |
new | manual_confirm | 命中高风险或特定规则,需 CRM 运营审核 | 否 | 需运营操作——在 CRM 入金审核队列中处理。审核前确认:金额是否合理、用户是否在黑名单、流水是否匹配 |
pending | (空) | 审核通过,正在执行风控资产变更(加钱) | 否 | 过渡状态,通常毫秒级完成。若卡住,可能是风控系统异常 |
end_ok | deposit_ok | 入金成功,资金已到证券账户 | 是 | 正常终态。用户可在 App 看到余额增加 |
end_reject | (空) | 被拒绝——CRM 审核不通过或风控变更失败 | 是 | 检查 reason 字段了解拒绝原因。如果是风控变更失败(非人工拒绝),需联系技术排查 |
end_reverse | deposit_ok | 已入账的资金被冲正(撤回) | 是 | 高风险操作——仅在确认入金错误(如重复入账、欺诈)时执行。冲正后用户余额会减少 |
触发人工审核的场景
以下情况会导致入金 Procedure 进入 manual_confirm,需要 CRM 运营处理:
| # | 场景 | 判定规则 | 运营处理 |
|---|---|---|---|
| 1 | 高风险入金 | deposit_type = HIGH_RISK(5) | 核实用户身份和资金来源,确认无风险后审核通过 |
| 2 | 异常入金 | deposit_type = ABNORMAL(3) | 检查流水详情,确认金额和来源后人工匹配 |
| 3 | 预审批入金 | deposit_type = PRE_APPROVAL(2) | 核实预审批条件是否满足 |
| 4 | 黑名单命中 | 入金黑名单匹配 | 确认是否误报,非误报则拒绝 |
| 5 | 大额入金 | 超过自动入账阈值 | 核实来源合理性后审核通过 |
BST 入金编排详细流程
BST 银证入金跳过了匹配引擎——银行直接推送或轮询获取结果,SBA 直接创建 Procedure 并入账。以下是天星 BST 入金的完整系统流程:
招行/民生 BST 入金的区别在于阶段二:不需要轮询,银行通过 SM2 Socket 双向链路实时推送入金结果,延迟从"秒~分钟级"降到"秒级"。
BST 入金的前提是银行卡已完成银证授权(Mandate 状态 = OPEN)。授权流程与银行卡管理 → 银行卡与授权 § BST 银证授权
入金扩展字段(ProcedureCashDeposit)
| 字段 | 含义 | 运营场景 |
|---|---|---|
transaction_id | 非银证:银行流水 ID;银证:业务 ID | 与银行侧核对的关键标识 |
amount | 入金金额 | 审核时确认金额是否合理 |
ccy | 币种(HKD / USD / CNH) | 确认币种与银行流水一致 |
is_bs_transfer | 0=非银证;1=银证通道 | 区分银证入金和普通入金的处理逻辑 |
transfer_method | TransType 代码(101/102/201-304 等) | 标识入金来源银行和通道 |
deposit_type | 入金模式:NORMAL(1) / PRE_APPROVAL(2) / ABNORMAL(3) / TRANS_AUTO(4) / HIGH_RISK(5) / NORMAL_HOLD(11) | 决定是否需要人工审核 |
hold_type | 冻结类型(仅 NORMAL_HOLD 模式):1=EXCHANGE / 2=CASH_OUT / 17=CASH_IN | 资金冻结用途 |
apply_id | 冻结 ID(32 位唯一标识,NORMAL_HOLD 模式必填) | 关联冻结记录 |
source | 入金来源:1=moomoo 发起;2=银行发起 | 银行发起的入金通过 AsbBstCreateFromBankJob 处理 |
bank_ref_id | 银行侧流水号(ASBBST + 序号) | 与银行核对的唯一标识 |
TransType 全表 → 入金规则速查 § TransType
入金 Procedure 状态相对简单——复杂性在匹配引擎,Procedure 只负责最后入账。接下来的出金 Procedure 要复杂得多,因为它要处理冻结、扣款、银行转账和失败回滚。
出金 Procedure 状态机
出金状态机是整个系统中最复杂的(17 个状态),因为需要处理冻结、扣款、转账、失败回滚、跨交易日延迟等多种场景。
三阶段概览
| 阶段 | 涉及状态 | 发生什么 | PM 关键问题 | 用户感知 |
|---|---|---|---|---|
| 1. 冻结 | new(freeze) → new(waiting) | 系统冻结用户可用余额 | "用户余额减少了吗?" | 可用余额减少,但资产总额不变(冻结金额仍在账户内) |
| 2. 审核 | new(waiting) → new(manual_confirm) → new(confirmed) | 自动/人工审核 | "用户能取消吗?" | 冻结阶段可取消;审核通过后进入扣款则不可取消 |
| 3. 扣款+转账 | new(blank) → pending(deduct_done) → pending(transfer_auto/manual) | 余额正式扣减,发送转账指令到银行 | "钱扣了但没到银行?" | 余额已扣,等待银行处理 |
| 4. 结果 | end_ok / end_reject / end_cancel | 银行确认成功或失败 | "用户什么时候收到钱?" | end_ok = 银行已确认,资金已汇出 |
正常路径(Happy Path)
大多数出金按这条路径完成——冻结 → 扣款 → 转账 → 成功:
正常路径之外,出金还可能遇到失败、取消、跨交易日等异常分支——这些路径是排查问题时最需要关注的:
异常与回滚路径
关键状态说明与运营关注点
| 主状态 | 子状态 | 含义 | 终态? | 运营关注点 |
|---|---|---|---|---|
new | freeze | 初始状态——等待冻结用户余额 | 否 | 毫秒级完成。若停留超过 30 秒,检查风控系统是否响应 |
new | waiting | 冻结成功,等待定时脚本或人工处理 | 否 | 普通出金由定时脚本自动拾取;BST 出金自动进入扣款。若长时间停留,检查定时任务是否正常 |
new | manual_confirm | 需 CRM 人工审核 | 否 | 需运营操作——出金审核队列。审核前确认:出金金额 vs 可用余额、目标银行卡是否本人、是否命中风控规则 |
new | confirmed | 人工审核通过,待扣款 | 否 | 过渡状态。系统自动进入扣款流程 |
new | (空) | 待扣款(new_blank) | 否 | 扣款中。若停留超过 1 分钟,可能是风控资产变更接口超时 |
pending | deduct_done | 扣款完成,待确认转账方式 | 否 | 关键节点——扣款已完成,不可回退到冻结。BST 自动进入转账;网银/FPS 进入人工转账 |
pending | transfer_auto | BST 自动转账中 | 否 | 银证通道正在向银行发送转账指令。天星需轮询结果(最多 10 次);招行/民生实时推送 |
pending | transfer_manual | 人工转账中(网银/FPS/电汇等) | 否 | 需运营操作——在银行网银完成转账后,在 CRM 点击"确认转账完成"(SetManualTransferDone) |
pending | return | 转账失败,待补回资金 | 否 | 系统自动补回已扣金额到用户账户。若补回失败,严重告警——用户钱"消失了" |
pending | unfreeze | 取消出金,解冻中 | 否 | 系统自动释放冻结金额。若解冻失败,用户余额会被"卡住" |
pending | move_next | 非交易时段,延迟到下个交易日 | 否 | BST 出金在非交易时段提交时触发。Procedure 回到 new(freeze) 重走流程 |
pending | waiting_trade | 等待交易日到来 | 否 | 周末/假期提交的出金在此等待。交易日到达后自动进入终态 |
pending | transfer_reject | 已拒绝/取消,等终态确认 | 否 | 过渡状态。系统自动设置最终的 reject 或 cancel |
end_ok | transfer_done | 出金成功 | 是 | 银行已确认汇款成功。用户应在 1-3 个工作日内收到资金 |
end_reject | (空) | 出金失败 | 是 | 检查 reason 字段。资金已自动补回用户账户 |
end_cancel | (空) | 出金取消 | 是 | 用户主动取消或运营取消。资金已自动解冻 |
end_reverse | transfer_done | 已完成的出金被冲正 | 是 | 极高风险操作——仅在银行确认汇款失败/退回时使用 |
pending_move 循环机制
pending_move 是独特的循环状态:非交易时段提交的 BST 出金,会被 MoveNextTransactionDay 操作移到下个交易日——Procedure 回到 new(freeze) 重新开始冻结-扣款-转账流程。
| 场景 | 示例 | Procedure 行为 | 用户感知 |
|---|---|---|---|
| 周五 18:00 提交 BST 出金 | 交易时段已过 | pending_move → 下周一 new(freeze) | 周五提交,周一才会处理 |
| 假期前一天提交 | 同上逻辑 | 延到假期后第一个交易日 | 假期后第一个交易日处理 |
| 交易日 16:30 之后提交 | 超过当日截止时间 | 延到次日 new(freeze) | 次日处理 |
| 连续假期(如春节) | 周五→下周二才是交易日 | 可能循环多次 pending_move | 假期结束后处理 |
用户常见疑问
"为什么我周五提交的出金周一才到账?"——因为 BST 银证通道依赖交易日,非交易时段的出金会被延迟到下个交易日。这不是系统故障,是正常的业务规则。
运营话术建议:"您的出金已受理,由于银证通道在交易日处理,下一个交易日(周一)会自动为您办理,预计 1-2 个工作日到账。"
交易日判定规则
系统判定"当前是否在交易时段"的规则:
| 市场 | 交易日 | 截止时间 | 超过截止时间后 |
|---|---|---|---|
| HK(港股) | 周一至周五,排除香港公众假期 | 16:15 HKT | 出金延到下个交易日 |
| US(美股) | 周一至周五,排除美国公众假期 | 次日 04:00 HKT(对应美东 16:00) | 出金延到下个交易日 |
| HKCC(港股通) | 港股通交易日(同时排除港、沪/深假期) | 16:15 HKT | 出金延到下个交易日 |
公众假期来源:系统维护一份交易日历(由结算团队每年末更新),包含港交所、NYSE、SSE/SZSE 的休市日。transaction_date 字段由 SBA 根据当前时间和交易日历自动计算。
排查技巧:如果出金在工作日白天仍触发 pending_move,检查交易日历是否包含了当天(可能是临时休市或交易日历未更新)。
BST 出金编排详细流程
以下是天星 BST 出金从用户发起到资金到账的完整系统流程:
网银/FPS 出金的区别在于阶段三:不走自动转账,而是进入 pending(transfer_manual) 等待运营在银行企业网银手动完成转账后确认。
出金扩展字段(ProcedureCashWithdraw)
| 字段 | 含义 | 运营场景 |
|---|---|---|
amount | 出金金额 | 审核时确认金额合理性 |
fee | 手续费(当前全部为 0) | — |
ccy | 币种 | 确认币种与用户选择一致 |
transfer_method | 通道 Key:auto_bs / hase / hsbc / boc_fps / cgb_fps_api 等 | 确定走哪个出金通道 |
transfer_dest_bank | 目标银行代码 | 核对目标银行信息 |
transfer_dest_card | 目标银行卡号 | 核对收款账号 |
bank_launched | 0=moomoo 发起;1=银行发起(BST 独有) | 区分谁发起了这笔出金 |
transfer_ref_id | 银行侧流水号(招行=bank_tx_seq,民生=OrgRefNo,天星=ASBBST+序号) | 与银行核对的关键标识 |
settled_amount | 现金部分金额 | 融资账户出金时,区分现金 vs 融资部分 |
margin_amount | 融资(贷款)部分金额 | 融资出金时需关注 |
payee_name | 收款人姓名(民生银证填英文名) | 核对收款人身份 |
request_id | 银行侧请求 ID(天星) | 轮询结果时的主键 |
通道代码全表 → 出金数据字典 § 出金通道代码
理解了状态机后,下一步是了解运营在 CRM 中可以对 Procedure 执行哪些操作——每个 CRM 按钮对应一个 SBA API。
SBA 服务 API 概览
SBA 编排系统对外暴露的操作接口——这些操作对应 CRM 中的按钮。理解"哪个按钮调用哪个 API"有助于排查"为什么按钮点了没反应"。
入金 Procedure 操作
| 操作 | 谁触发 | 效果 | 前置状态 | CRM 对应 | 权限要求 |
|---|---|---|---|---|---|
CashDepositCreate | 入金服务(自动) | 创建入金 Procedure,开始入账流程 | — | 自动触发,CRM 无按钮 | 系统自动 |
CashDepositManualConfirm | CRM 运营 | 审核通过,继续入账 | new(manual_confirm) | 入金审核 →"审批通过" | 一线运营 |
CashDepositManualReject | CRM 运营 | 审核拒绝,Procedure → end_reject | new(manual_confirm) | 入金审核 →"拒绝" | 一线运营 |
CashDepositReverse | CRM 运营 | 冲正已完成的入金,资金从证券账户撤回 | end_ok | 入金详情 →"冲正" | 主管级 |
冲正操作注意
CashDepositReverse 会直接从用户证券账户扣减已入账金额。执行前必须确认:
- 确实是错误入账——如重复入账、欺诈、银行退款
- 用户当前可用余额足够扣减——如果用户已用入金资金交易或出金,可用余额不足将导致冲正失败
- 已通知用户——冲正后用户余额会减少,需提前沟通
冲正后不可撤销。误冲正需要重新走入金流程。
出金 Procedure 操作
| 操作 | 谁触发 | 效果 | 前置状态 | CRM 对应 | 权限要求 |
|---|---|---|---|---|---|
CashWithdrawCreate | 出金服务(自动) | 创建出金 Procedure,开始冻结 | — | 自动触发 | 系统自动 |
CashWithdrawStartTransfer | CRM 运营 / 自动 | 确认转账参数无误,开始转账 | new(blank) 或 pending(deduct_done) | "开始转账" | 一线运营 |
CashWithdrawReject | CRM 运营 | 拒绝出金,触发解冻回滚 | new 阶段任意状态 | "拒绝" | 一线运营 |
CashWithdrawManualConfirm | CRM 运营 | 人工审核通过 | new(manual_confirm) | "审批通过" | 一线运营 |
CashWithdrawManualReject | CRM 运营 | 人工审核拒绝 | new(manual_confirm) | "审批拒绝" | 一线运营 |
SetManualTransferDone | CRM 运营 | 确认人工转账(网银/电汇等)已完成 | pending(transfer_manual) | "确认转账完成" | 一线运营 |
SetManualTransferFailed | CRM 运营 | 确认人工转账失败,触发补回 | pending(transfer_manual) | "转账失败" | 一线运营 |
CashWithdrawForceProcess | CRM 运营 | 强制处理卡住的 Procedure | 任意非终态 | "强制处理" | 主管级 |
CashWithdrawCancel | CRM 运营 | 取消出金,Procedure → end_cancel | new 阶段(冻结后、扣款前) | "取消出金" | 主管级 |
MoveNextTransactionDay | CRM 运营 / 定时任务 | 延迟到下个交易日处理 | pending(deduct_done) / pending(transfer_manual) | "延到下个交易日" | 一线运营 |
CashWithdrawReverse | CRM 运营 | 冲正已完成的出金 | end_ok | "冲正" | 主管级 |
按钮不可用?
CRM 按钮是否可点击取决于 Procedure 当前状态。例如"审批通过"只在 new(manual_confirm) 状态可用——如果 Procedure 已经进入 pending 阶段,按钮会灰显。
排查"按钮点了没反应":
- 检查 Procedure 当前
status+ext_status是否匹配操作的前置状态 - 检查
version是否是最新——刷新页面后重试 - 检查操作人权限——某些操作(如冲正)需要特定角色
CRM 操作最终触发 SBA 与银行的交互。但银行不是同步响应的——接下来看 SBA 如何通过异步任务队列管理与银行的交互。
异步任务队列模式
出入金系统与银行的交互大多是异步的——发送一个指令后,不会立刻拿到结果。SBA 通过任务队列管理这些异步流程。
执行模式
- 创建 Job:接收业务请求,组装参数,发送指令到银行
- Result Job:定期轮询银行 API 查询处理结果
- 重试机制:有明确的最大重试次数,超过后标记为异常,触发告警
- 幂等性:通过
procedure_id+request_id保证同一指令不会重复执行
天星 BST 任务链详情
天星 BST 是典型的异步轮询模式——每个操作由创建任务和结果轮询任务组成:
入金任务链:
| 任务名称 | 最大重试 | 轮询间隔 | 职责 | 失败处理 |
|---|---|---|---|---|
AsbBstCreateJob | — | — | 向天星发送入金请求,获取 request_id | 标记失败,通知用户 |
AsbBstCreateFromBankJob | — | — | 处理银行侧发起的入金(source=2) | 标记异常,待人工核实 |
AsbBstCreateResultJob | 60 次 | 每次约 3 秒 | 轮询银行查询入金结果——决定到账速度的关键 | 60 次仍无结果 → 飞书告警 + 人工介入 |
AsbBstCreateRetryJob | — | — | 对暂时失败的入金请求重试 | 重试失败 → 标记终态失败 |
AsbBstDepositJob | — | — | 入金成功后执行 SBA 记账(CashDepositCreate) | 记账失败 → 严重告警 |
出金任务链:
| 任务名称 | 最大重试 | 轮询间隔 | 职责 | 失败处理 |
|---|---|---|---|---|
AsbBstTransfer | 10 次 | 每次约 5 秒 | 发送出金请求并轮询结果 | 10 次仍无结果 → 飞书告警 |
SyncAsbBstWithdraw | — | 每 2 小时 | 定时拉取银行侧发起的出金请求 | 拉取失败 → 重试 |
银行侧发起的交易(source=2)处理逻辑:
| 方向 | 拉取任务 | 拉取窗口 | 处理方式 |
|---|---|---|---|
| 入金 | FetchBankRequest | 每 2 分钟 | 拉取后创建 AsbBstCreateFromBankJob 处理 |
| 出金 | SyncAsbBstWithdraw | 每 2 小时 | 拉取后对比本地记录,新增的创建出金 Procedure |
轮询超时排查
用户反馈"入金/出金一直在处理中"时,按以下步骤排查:
- 确认授权状态:Mandate 是否为 OPEN(status=2)。不是 → 需重新授权
- 查看重试次数:在 Job 记录中查看
retry_count——入金最多 60 次(约 3 分钟),出金最多 10 次(约 50 秒) - 检查银行响应:如果 Job 记录中有银行返回的错误码,根据错误码定位
- 重试耗尽仍无结果:联系天星银行核实银行侧状态
- 银行发起的交易(source=2):检查
FetchBankRequest/SyncAsbBstWithdraw拉取任务是否正常执行
招行/民生 SM2 Socket 任务
招行和民生不使用轮询——通过 SM2 加密的 Socket 双向链路实时通信:
| 维度 | 招行(cmb_stock_trans) | 民生(ms_stock_bank_transaction) |
|---|---|---|
| 连接方式 | SM2 Socket 长连接 | SM2 Socket 长连接 |
| 入金 | 银行推送 → 实时接收 | 银行推送 → 实时接收 |
| 出金 | moomoo 发送 → 银行推送结果 | moomoo 发送 → 银行推送结果 |
| 心跳 | 定时心跳保活 | 定时心跳保活 |
| 断线恢复 | sync_bst_data_helper 补偿 | 自动重连 + 重拉未处理记录 |
| 典型延迟 | 秒级 | 秒级 |
| 运营介入概率 | 低(除非 Socket 断连) | 低(除非 Socket 断连) |
不同银行的异步模式对比
| 银行 | 通信方式 | 是否需轮询 | 用户等待时间 | 运营介入概率 |
|---|---|---|---|---|
| 天星 BST | REST API (JSON) | 是(入金 60 次 / 出金 10 次) | 秒级~分钟级 | 中 |
| 招行 BST | SM2 Socket 双向链路 | 否(实时推送) | 秒级 | 低 |
| 民生 BST | SM2 Socket 双向链路 | 否(实时推送) | 秒级 | 低 |
| 汇丰 eDDA | REST API | 是 | T+0~T+1 | 中 |
| 恒生 eDDA | REST API | 是 | T+0~T+1 | 中 |
PM 视角:需要轮询的银行,用户等待时间更长,异常时运营介入的概率更高。这就是为什么天星 BST 虽然也是"全自动",但从提交到到账的时间可能比招行/民生长。
具体的 Job 名称和重试次数 → 内银系 BST 总览 § 定时任务
去重与幂等机制
资金操作最怕"重复"——一笔入金记两次账、一笔出金扣两次钱。SBA 在多个层面建立去重保障。
三层去重
| 层级 | 机制 | 去重键 | 防止什么 |
|---|---|---|---|
| 1. 业务层 | Apply/Task 唯一性 | 入金:flow_id + bank_type;出金:task_id | 防止同一笔流水/任务重复创建 Procedure |
| 2. SBA 层 | Procedure 创建幂等 | procedure_id + request_id | 防止 SRPC 超时重试导致重复创建 |
| 3. 银行层 | 银行侧流水号 | 天星:ASBBST 前缀 + 序号;招行:bank_tx_seq;民生:OrgRefNo | 防止重复发送转账指令到银行 |
天星去重示例
天星 BST 的 bank_ref_id 使用 ASBBST 前缀 + 全局递增序号(如 ASBBST000012345),保证:
- 入金:同一个
request_id不会重复记账——即使AsbBstDepositJob被重复执行 - 出金:同一个
bank_ref_id不会向银行重复发送转账——即使AsbBstTransfer超时重试
并发控制
| 机制 | 实现方式 | 场景 |
|---|---|---|
| 乐观锁 | Procedure 的 version 字段,更新时 WHERE version = ? | 防止 CRM 运营和系统定时任务同时更新同一条 Procedure |
| 分布式锁 | Redis 锁,key = procedure:{id} | 防止同一 Procedure 被多个 Worker 并发处理 |
| 幂等检查 | 入金:检查 transaction_id 是否已存在;出金:检查 task_id 对应的 Procedure 是否已创建 | 防止重复创建 |
去重保证"不多做",接下来的冻结模式保证"不出错"——在银行确认成功之前,用户的资金始终被安全锁定。
冻结-转账-释放模式
出金不是"直接把钱转走"那么简单。SBA 采用冻结-转账-释放三步模式来保证资金安全。
为什么需要先冻结
从系统发出出金指令到银行确认成功之间,有一段时间窗口(秒级到小时级不等)。如果在这段时间内不冻结资金:
- 用户可能同时发起另一笔出金或交易
- 可能导致账户出现负余额
- 可能产生资金争用
冻结就是在这个时间窗口内"预留"这笔资金,确保它不被其他操作使用。
冻结对用户资产的影响
| 资产指标 | 冻结前 | 冻结后(出金处理中) | 出金成功 | 出金失败(解冻) |
|---|---|---|---|---|
| 资产总额 | 100,000 | 100,000(不变) | 90,000(减少) | 100,000(恢复) |
| 可用余额 | 100,000 | 90,000(减少 10,000) | 90,000 | 100,000(恢复) |
| 冻结金额 | 0 | 10,000 | 0 | 0 |
用户在出金处理期间看到"可用余额"减少,但"资产总额"不变——这是正常的冻结行为。
三种结局
| 结局 | 冻结处理 | 用户感知 | 运营操作 |
|---|---|---|---|
| 成功 | 冻结释放,资金正式扣减 | 余额减少,收到到账通知 | 无需操作 |
| 失败 | 冻结释放,资金恢复可用 | 余额恢复,收到出金失败通知 | 检查失败原因,必要时通知用户 |
| 超时 | 保持冻结,等待确认 | 余额未恢复,用户可能联系客服 | 必须人工确认银行侧状态后决定释放或扣款 |
超时处理流程
超时是最棘手的场景——钱可能已经到了用户银行卡(银行侧成功),但 moomoo 还没收到确认:
| 步骤 | 操作 | 说明 | 超时阈值 |
|---|---|---|---|
| 1 | 收到超时告警 | 飞书通知运营 | 天星:轮询 10 次后(约 50 秒) |
| 2 | 联系银行确认 | 核实银行侧这笔汇款的最终状态 | — |
| 3a | 银行确认成功 | 在 CRM 执行 SetManualTransferDone,释放冻结 + 正式扣款 | — |
| 3b | 银行确认失败 | 在 CRM 执行 SetManualTransferFailed,释放冻结 + 回滚资金 | — |
| 3c | 银行也不确定 | 保持冻结,继续跟进。绝对不能贸然释放冻结 | — |
冻结异常是严重告警
如果出金失败但冻结没有正确释放(系统异常),用户的资金会被"卡住"——余额显示减少了但出金也没成功。这是 P0 严重告警项(监控告警体系),需要运营使用 CashWithdrawForceProcess 手动释放冻结。
操作步骤:
- 在 CRM 找到该 Procedure
- 确认银行侧状态(未转出 / 已转出 / 不确定)
- 如果银行未转出:
CashWithdrawForceProcess→ 选择"回滚"→ 释放冻结 + 恢复余额 - 如果银行已转出:
CashWithdrawForceProcess→ 选择"完成"→ 释放冻结 + 正式扣款 - 如果银行不确定:不要操作,继续跟进银行
冻结类型代码 → 出金规则手册 § 冻结类型
Procedure 查询与定位指南
当用户反馈"入金没到账"或"出金一直在处理中"时,Procedure 是定位问题的核心。以下是运营在 CRM 中的排查路径:
入金问题排查
出金问题排查
| 步骤 | 检查项 | 在哪查 | 正常值 | 异常处理 |
|---|---|---|---|---|
| 1 | 出金 Task 状态 | CRM → 出金管理 | DONE / PROCESSING | PENDING = 审批未完成 |
| 2 | SBA Procedure 状态 | CRM → SBA 管理 | end_ok | 根据当前状态定位(见上方状态表) |
| 3 | Procedure ext_status | Procedure 详情 | transfer_done | 定位具体卡在哪一步 |
| 4 | Flow 审计记录 | Procedure → 查看历史 | 状态按时间正常递进 | 找到最后一条 Flow 确定卡点 |
| 5 | reason 字段 | Procedure 详情 | 空(成功时) | 非空 = 查看具体拒绝/失败原因 |
| 6 | transfer_ref_id | Procedure 详情 | 银行流水号 | 与银行核对的关键 |
关键搜索字段
| 搜索方式 | 字段 | 适用场景 |
|---|---|---|
| 用户维度 | nn_uid(牛牛号) | 查看某个用户的所有出入金 Procedure |
| Procedure 维度 | id | 已知 Procedure ID,直接定位 |
| 银行流水维度 | transfer_ref_id / transaction_id | 与银行核对时使用 |
| 时间维度 | created_at + status | 批量排查某时间段的异常 Procedure |
| 通道维度 | transfer_method | 排查某个通道的整体问题 |
常见异常场景
入金异常
| # | 场景 | 表现 | 可能原因 | 运营处理 |
|---|---|---|---|---|
| 1 | Procedure 卡在 new(waiting) | 入金请求已创建但长时间不入账 | SBA 服务异常;命中了人工审核规则但未标记 | 检查 SBA 入金编排服务日志;查看 Flow 记录确认是否有状态变更 |
场景 1 参考话术
"您的入金已在处理中,系统正在进行资金核实,通常几分钟内完成。如超过 30 分钟仍未到账,请提供您的牛牛号,我们为您优先跟进。"
| 2 | 风控变更失败 | Procedure 从 pending → end_reject | 风控系统拒绝(触发内部规则);资产变更接口异常 | 查看 reason 字段,联系风控团队确认原因 | | 3 | 重复入金 | 同一笔流水创建了多个 Procedure | 去重机制未生效(网络超时重试);银行推送了重复流水 | 冲正多余的入金(CashDepositReverse),保留正确的一笔 | | 4 | 银行退款(BST) | 天星银行返回 REFUNDED 状态 | 银行侧风控拦截;用户银行账户余额不足 | 确认已入账的资金已冲正;联系用户说明原因 | | 5 | 入金金额不一致 | 流水金额与申请金额不匹配 | 用户转账时手动修改了金额;跨行手续费扣减 | 转人工匹配处理,确认实际到账金额 | | 6 | BST 入金轮询超时 | 天星入金 60 次轮询仍无结果 | 银行系统延迟;网络异常 | 查看 AsbBstCreateResultJob 的 retry_count;联系天星银行核实 |
出金异常
| # | 场景 | 表现 | 可能原因 | 运营处理 |
|---|---|---|---|---|
| 1 | 冻结失败 | Procedure 从 new(freeze) → pending(transfer_reject) | 可用余额不足;风控冻结接口异常 | 检查用户可用余额;若余额充足但仍失败,联系风控团队 |
| 2 | 扣款失败 | Procedure 从 new(blank) → pending(transfer_reject) | 风控资产变更接口超时;冻结已释放但扣款未完成 | 检查风控系统是否正常;查看 Flow 记录定位扣款失败节点 |
| 3 | 银行拒绝转账 | Procedure 进入 pending(return) | 银行卡信息错误(卡号/SWIFT);银行侧风控拦截;收款账户异常 | 检查银行返回的错误码;确认银行卡信息正确;资金自动补回 |
| 4 | 冻结未释放 | 出金失败但用户余额未恢复 | 解冻请求发送失败;风控系统未正确处理释放 | 严重告警——使用 CashWithdrawForceProcess 强制释放 |
| 5 | 轮询超时(天星) | Procedure 卡在 pending(transfer_auto) | 银行系统延迟;网络异常 | 联系天星银行确认银行侧状态,根据结果手动设置成功或失败 |
| 6 | 跨日延迟 | BST 出金 pending(move_next) | 非交易时段提交(正常行为) | 向用户解释交易日规则。非交易时段的出金会延到下个交易日 |
场景 6 参考话术
"您的出金已受理。由于银证通道在交易时段处理,您的出金将在下一个交易日(周一)自动办理,预计 1-2 个工作日到账。感谢您的耐心等待。"
| 7 | 人工转账未确认 | Procedure 长时间停在 pending(transfer_manual) | 运营未在银行网银完成转账;转账已完成但忘记确认 | 确认是否已完成转账;完成后点击"确认转账完成" |
场景 7 参考话术
"您的出金正在处理中,我们的运营团队正在通过银行完成转账操作。预计今日内完成,到账时间取决于收款银行的处理速度(通常 1-3 个工作日)。"
| 8 | 补回失败 | pending(return) 长时间未转入终态 | 风控补回接口异常 | 严重告警——用户钱"消失了"。联系技术排查补回失败原因 |
读完之后
| 我想... | 去看 |
|---|---|
| 了解 BST 银行的详细对比 | 内银系 BST 总览 |
| 看整体架构和数据流 | 系统架构与数据流 |
| 查 SBA Procedure 状态码 | 出金数据字典 |
| 看出金的冻结和限额规则 | 出金规则手册 |
| 查运营操作指南 | 人工匹配指引、出金审批指引 |
这个页面有帮助吗?