深色模式
新人导读:一笔钱的旅程
本页说明
讲什么:以 FPS 入金、eDDA 入金、银证出金三个核心场景,端到端走一遍完整链路,串联全书知识点 适合谁:刚接手出入金业务、需要快速建立全局认知的产品经理 前置阅读:无,这是起点 预计阅读:8 分钟 负责人:出入金产品团队
核心要点:三个核心场景串联全书知识点——FPS 入金走 Push 匹配链路,eDDA 入金走 Pull 代扣链路,BST 出金走自动审批+银证转账链路。理解这三条链路就掌握了系统全貌。
30 秒全景
用户发起申请 → 银行流水/指令对接 → 自动匹配/审批 → 资金到账/汇出 → 对账
出入金系统是 moomoo 证券交易平台的资金通道层——连接用户银行账户与证券账户的桥梁。
| 维度 | 数量 | 说明 |
|---|---|---|
| 入金方式 | 10 种 | BST、FPS、eDDA(恒生/汇丰)、网银、ATM、支票、缴费、海外、天星 |
| 出金通道 | 12 种 | 银证、中银银企/FPS、恒生/汇丰网银、渣打/广发 FPS、CHATS、电汇、支票 |
| 支持银行 | 16 家 | 中银、汇丰、恒生、工银、渣打、广发、招行、民生、天星、EWB 等 |
| 货币 | 5 种 | HKD、USD、CNH、JPY、SGD |
| 边界 | 系统负责 | 系统不负责 |
|---|---|---|
| 入口 | 接收用户入金/出金请求 | 用户注册、KYC、开户 |
| 银行对接 | 流水解析、汇款指令、eDDA/eDDI | 银行内部处理、SWIFT 中转 |
| 资金操作 | 通过 SBA 创建 procedure 操作账本 | 账本余额管理本身(SBA 负责) |
| 风控 | 调用黑白名单、高风险检查 | 风控规则引擎本身 |
| 通知 | 触发入金/出金结果通知 | 推送通道管理 |
入金和出金是两套完全独立的服务——入金核心是"匹配"(钱到了,找到是谁的),出金核心是"审批"(钱要走,能不能放行)。
按角色选择阅读路径
不同角色关注的重点不同。先看完下面三个场景建立全局认知,然后按你的角色深入:
下面用三个真实场景,带你走完入金和出金的每一步。FPS 和 eDDA 是两种截然不同的入金模式——前者是用户主动转账(Push),后者是系统从用户银行账户扣款(Pull)。理解这个区别是理解整个入金体系的关键。
入金之旅:一笔 HKD 50,000 的 FPS 入金
场景:用户在 moomoo App 选择「FPS 转数快」,向证券账户转入 HKD 50,000。
Step 1: 用户发起申请
用户在 App 点击「入金」,选择入金方式 fps(方式代码 3),填入金额 50,000,选择币种 HKD。
系统调用入金服务的 ApplyController.applyAction(),经过以下检查:
| 检查项 | 说明 |
|---|---|
| 用户权限 | 验证 WEB/OA 签名,确认用户身份 |
| 入金限制 | 调用 AccountLockBusiness::isDepositRestricted() 检查账户是否被限制入金 |
检查通过后,创建一条 Apply 记录,写入 applys_{uid % 100} 分表:
| 字段 | 值 | 说明 |
|---|---|---|
status | 0(PENDING) | 用户已申请,等待匹配 |
deposit_method | fps | FPS 转数快 |
amount | 50000 | 申请金额 |
currency | HKD | 港币 |
export_bank_id | 用户选择的银行 | 付款银行(用户侧) |
import_bank_id | 系统分配 | 收款银行(公司侧) |
深入了解
全部 10 种入金方式 → 入金方式总览 Apply 表每个字段的含义 → 入金规则速查 § 核心字段
Step 2: 用户完成 FPS 转账
用户切换到银行 App,通过 FPS(转数快)完成 HKD 50,000 的转账。
FPS 是即时到账的支付系统,钱立刻从用户银行扣出,进入公司在渣打(SCB)或广发(CGB)的子账户。
Step 3: 银行流水到达系统
渣打/广发的 FPS 网关将交易流水推送到系统。流水写入 flows_{bankType}_{YYYYMM} 分表,关键信息:
| 字段 | 值 | 说明 |
|---|---|---|
| 流水类型 | FPS 轉賬 | FPS transfer 标识 |
| 备注 | FPS... | FPS 交易参考号 |
| 金额 | 50000.00 | 实际到账金额 |
| 币种 | HKD | 港币 |
不同银行的流水采集方式差异很大:
| 银行 | 方式 | 时效 |
|---|---|---|
| 中银 | B2E 主动拉取 | 每日 3 次 + 2 小时转换 |
| 汇丰 | MT910 实时推送 | 秒级 |
| 渣打/广发 | FPS API | 实时 |
| 招行/民生 | 银证双向链路 | 事件驱动,实时 |
| EWB | CSV + BAI2 文件 | 按需手工上传 |
Step 4: 自动匹配引擎启动
匹配引擎是一组定时任务,每家银行一个,每 3 分钟运行一次(如 php artisan match:boc)。
引擎拿到所有 status = PROCESSING 的未处理流水和 status = PENDING 的待匹配申请,执行五维比对:
| 维度 | 匹配字段 | 匹配逻辑 |
|---|---|---|
| 1. 币种 | currency | 必须完全一致 |
| 2. 金额 | amount | 容差范围内匹配(HKD/CNH/JPY: 0~20 差额;USD/SGD: 0~3 差额) |
| 3. 姓名 | en_name / cn_name | 精确匹配或模糊匹配 |
| 4. 日期 | date | 申请日期 ±15 天内 |
| 5. 卡号 | bank_card_number | 银行账号匹配 |
匹配结果有三种:
| 结果码 | 含义 | 后续 |
|---|---|---|
| 2 (RESULT_DEPOSIT) | 完全匹配,可自动入账 | → 进入自动入账判定 |
| 1 (RESULT_NORMAL) | 部分匹配,需人工确认 | → 创建匹配记录,等运营审核 |
| 0 (RESULT_NOT) | 不匹配 | → 不操作 |
我们这笔 FPS 入金:币种 HKD 一致、金额 50,000 精确匹配、姓名一致、日期当天、卡号匹配 → RESULT_DEPOSIT(完全匹配)。
Step 5: 自动入账判定
匹配成功不等于自动入账。系统还需要检查 5 个自动入账条件:
| # | 条件 | 本笔情况 |
|---|---|---|
| 1 | 金额未超过币种限额 | HKD 50,000 < HKD 2,000,000 限额 → 通过 |
| 2 | 流水未被人工拒绝过 | reject == 0 → 通过 |
| 3 | 在允许的自动处理时间窗口内 | 工作时间内 → 通过 |
| 4 | 一条流水只对应一个用户 | count(uids) == 1 → 通过 |
| 5 | 当日自动入账次数未超 10 笔 | 首笔 → 通过 |
各币种自动入账限额:
| 币种 | 最大自动入账金额 |
|---|---|
| HKD | 2,000,000 |
| USD | 300,000 |
| CNH | 2,000,000 |
| JPY | 40,000,000 |
| SGD | 350,000 |
5 个条件全部通过 → 系统判定为 NORMAL(正常模式) 入金类型。
Step 6: SBA 编排执行
系统向 SBA(Server Bank Account)发起入金编排请求,创建一个 Procedure:
入金 Procedure(NORMAL 模式)执行序列:
1. 检查冻结 →
2. 资金入账(加到证券账户余额)→
3. 解除冻结 →
4. 完成SBA 是系统的"内部银行"——所有涉及资金余额变动的操作都通过 SBA Procedure 完成,确保原子性和一致性。
深入了解
SBA 是什么 → SBA 概念与数据模型 6 种入金编排模式的触发条件 → SBA 资金编排
Step 7: 资金到账,用户收到通知
Procedure 执行成功,Apply 状态更新为 2(DONE)。
系统发送推送通知(NOTICE_TYPE_NORMAL),用户在 App 看到"入金成功"。
从用户提交到到账,这笔 FPS 入金的全链路时间:FPS 流水实时到达 + 匹配引擎 3 分钟周期 + SBA 执行秒级 ≈ 3~5 分钟。
入金之旅 II:一笔 HKD 20,000 的 eDDA 入金
场景:用户已绑定汇丰银行卡并完成 eDDA 授权,在 moomoo App 选择「eDDA 电子直接扣账」,入金 HKD 20,000。
eDDA 与 FPS 的根本区别
FPS 是用户主动转账(Push)——用户在银行 App 操作,钱到达后系统需要匹配引擎识别"这笔钱是谁的"。 eDDA 是系统主动扣款(Pull)——用户在 moomoo App 提交后,系统直接从用户银行账户扣钱。不需要匹配引擎,因为系统自己发起的扣款,天然知道钱属于谁。
Step 1: 前提——eDDA 授权
在发起 eDDA 入金之前,用户必须先完成一次性的 eDDA 授权,允许富途从其银行账户直接扣款。
授权状态在 setup_eddis 表中跟踪:
| 状态码 | 含义 | 说明 |
|---|---|---|
| 3 | WAITING | 等待发起授权 |
| 1 | PENDING | 授权进行中,等待银行确认 |
| 2 | EFFECT | 授权成功,可以入金 |
| 0 | FAIL | 授权失败 |
系统通过 SetupEddaCheckAuthorizationJob 定时检查授权进度(每 600 秒轮询一次),最长等待约 10 天(3000 次重试)。
常见授权失败原因:
| 错误码 | 含义 |
|---|---|
MFISAC01 | 银行账号错误 |
MPP01005~01008 | 身份证/手机号/姓名不匹配 |
MPP06001 | 银行账户状态异常 |
深入了解
汇丰 eDDA 与恒生 eDDA 的区别 → 汇丰 HSBC / 恒生 Hang Seng 哪些银行卡可以用 eDDA → eDDA 支持范围(汇丰通道 15 家、恒生通道 12 家)
Step 2: 用户发起 eDDA 入金
用户在 App 点击「入金」,选择入金方式 eddaHSBC(方式代码 9),输入金额 20,000 HKD。
系统创建 Apply 记录后,验证 eDDA 授权状态:
验证逻辑(verifyEddaBankCard):
恒生 eDDA → 必须已预授权(status = EFFECT)
汇丰 eDDA(同行)→ 必须已预授权
汇丰 eDDA(跨行 + 线上开户)→ 允许后置授权我们的场景:汇丰同行,授权已生效 → 验证通过。
Apply 记录:
| 字段 | 值 | 与 FPS 的区别 |
|---|---|---|
status | 0(PENDING) | 同 |
deposit_method | eddaHSBC | FPS 是 fps |
amount | 20000 | — |
currency | HKD | 同 |
Step 3: 异步任务队列启动扣款
这是 eDDA 与 FPS 最大的不同——用户不需要去银行 App 操作。
Apply 创建后,系统自动触发任务链:
ApplyFollowJob(检测到 method = eddaHSBC)
→ HsbcEddiCreateJob(发起扣款)
→ HsbcEddiResultJob(轮询扣款结果)HsbcEddiCreateJob 调用 Eddi::applyHsbcEddi(),构建扣款请求:
| 参数 | 说明 |
|---|---|
debtor_bank_code | 用户的银行代码(汇丰 3 位) |
debtor_account_identification | 用户银行账号 |
debtor_name | 用户姓名 |
creditor_bank_code | 富途收款银行代码 |
creditor_account_identification | 富途收款账号 |
instructed_amount | 20000(扣款金额) |
instructed_amount_currency | HKD |
系统通过 SBA 服务将扣款指令发往汇丰。同时写入 hsbc_eddis 表,用 apply_id 的唯一索引防止重复扣款。
深入了解
入金方式对比 → 入金方式总览
Step 4: 银行执行扣款
汇丰收到 eDDI(Electronic Direct Debit Instruction)指令后:
- 验证用户账户余额
- 从用户账户扣除 HKD 20,000
- 将资金转入富途在汇丰的账户
- 通过 eDDA Report 回传执行结果
hsbc_edda_report 服务接收汇丰的执行报告,更新 Procedure 状态:
| 报告状态 | 含义 | 系统处理 |
|---|---|---|
| finished | 扣款成功 | 更新 Apply real_amount,标记入账 |
| rejected | 扣款被拒 | 记录 reject_reason_code,通知用户 |
Step 5: SBA 入账(跳过匹配引擎!)
关键区别:eDDA 入金完全跳过匹配引擎。
在匹配服务的代码中,eDDA 被显式排除:
HsbcMatch 匹配逻辑:
if (deposit_method == 'eddaHSBC') → return 不匹配(跳过)
HangSengMatch 匹配逻辑:
if (deposit_method == 'edda') → return 不匹配(跳过)因为系统自己发起的扣款,天然知道资金属于哪个用户、对应哪笔申请,不需要五维比对。
扣款成功后,SBA 直接创建入金 Procedure 完成入账。
Step 6: 资金到账,用户收到通知
HsbcEddiResultJob 轮询到扣款成功 → Apply 状态更新为 2(DONE) → 推送通知。
从用户提交到到账时间:扣款指令发送秒级 + 银行处理通常 几分钟到几小时(取决于银行处理速度)。
FPS vs eDDA:两种入金模式对比
| 维度 | FPS(Push) | eDDA(Pull) |
|---|---|---|
| 资金方向 | 用户主动转入 | 系统从用户账户扣款 |
| 用户操作 | 需切换到银行 App 操作 | 在 moomoo App 内一键完成 |
| 前置条件 | 无 | 需先完成 eDDA 授权 |
| 匹配引擎 | 需要五维匹配 | 完全跳过 |
| 到账确定性 | 依赖匹配结果 | 扣款即确认 |
| 支持银行 | 渣打、广发、中银等 | 汇丰通道 15 家、恒生通道 12 家(完整列表) |
| 用户体验 | 步骤多,需跨 App | 体验最好,一键入金 |
| 数据表 | applys + flows + matches | applys + hsbc_eddis/hs_eddis + setup_eddis |
| 异步任务 | 匹配引擎定时扫描(3 分钟) | Job 队列主动推进 |
| 失败处理 | 流水到了但匹配不上 → 人工 | 银行拒绝扣款 → 通知用户 |
为什么 eDDA 是核心流程?
eDDA 实现了"用户在 App 内一键入金"的最佳体验——不需要切换银行 App、不需要等待匹配。eDDA 占入金总量约 78%(汇丰 eDDA 单通道占 72%,恒生 eDDA 约 6%),是绝对主力。汇丰通道支持 15 家银行的个人账户,恒生通道支持 12 家——并非只有汇丰和恒生自家银行卡才能用。理解 eDDA 的授权-扣款-入账链路,是理解入金产品设计的关键。详见 eDDA 支持范围。
出金之旅:一笔 HKD 30,000 的银证出金
场景:用户在 moomoo App 发起出金,通过招行银证通道提取 HKD 30,000 到招行银行卡。
Step 1: 用户发起出金请求
用户在 App 点击「出金」,选择招行银行卡,输入金额 30,000 HKD。
系统调用出金服务的 WithdrawCreator.apply(),进行一系列前置检查:
| 检查项 | 说明 |
|---|---|
| 出金限制 | 检查用户是否被限制出金 |
| 黑名单 | 调用 hk-withdraw-blacklist-go 检查出金黑名单 |
| 风控状态 | 验证用户风控状态 |
| 币种-市场一致性 | HKD 属于 HK 市场,一致 |
| 可用出金方式 | 检查用户银行卡支持的出金方式 |
| 手续费 | 计算出金手续费 |
| 银行卡信息 | 验证银行卡有效 |
检查通过后,系统执行原子事务,一次性完成:
- 创建 SBA List 记录
- 创建 Task 记录(出金任务)
- 创建 Apply 记录
- 添加 Flow 操作日志
深入了解
出金任务每个字段的含义 → 出金数据字典 § 核心字段 全部 12 种出金通道 → 出金方式总览
Step 2: 通道路由——系统选择出金方式
系统通过 calcMethod() 自动判断出金通道:
决策树:
1. 用户银行卡支持 BST?
├─ 是 → 招行(CMBHK)支持银证 → method = auto_bs
└─ 否 → 继续判断
2. 银行在香港?
├─ 否 → method = tele_transfer(跨境电汇)
└─ 是 → method = null(需人工选择)招行银行卡支持 BST(银证转账),所以系统自动设置 method = auto_bs。
Step 3: 确定审批流程模板
出金任务有三步审批,但并非每笔都要走全部三步:
| 步骤 | 名称 | 何时需要 |
|---|---|---|
| Step 1: Audit | 高危审核 | 仅高风险/OM 账户需要 |
| Step 2: Confirm | 确认指示 | 所有出金都需要 |
| Step 3: Remittance | 汇出资金 | 所有出金都需要 |
这笔不是高风险,流程模板为 [Confirm, Remittance],跳过 Audit。
Step 4: Confirm——确认指示
运营人员(或自动系统)在 Confirm 步骤:
- 验证银行卡 — 确认招行银行卡状态正常
- 确认出金方式 —
auto_bs(银证转账)已自动设置
对于 auto_bs 通道,Confirm 步骤不会调用 SBA startTransfer()(这个动作留给 Remittance)。
操作:点击「提交到下一步」(NEXT) → 进入 Remittance。
Step 5: Remittance——汇出资金
这是关键步骤。对于 auto_bs(银证)通道,系统检查自动出金条件:
| # | 条件 | 说明 | 本笔情况 |
|---|---|---|---|
| 1 | 通道是 auto_bs | 只有银证通道才支持自动出金 | 是 → 通过 |
| 2 | 非迁移数据 | 排除从旧系统迁移的历史数据 | 新数据 → 通过 |
| 3 | 自动出金开关已开启 | auto_settings 表中对应币种 status = OPEN | HKD 已开启 → 通过 |
| 4 | 金额在限额内 | amount ≤ max_amount | 30,000 ≤ 配置上限 → 通过 |
| 5 | 余额充足 | amount ≤ available_balance | 余额足够 → 通过 |
| 6 | 每日次数未超限 | 当日同用户出金 < 10 笔 | 首笔 → 通过 |
全部通过 → 系统调用 SBA startTransfer()(异步),创建出金 Procedure。
深入了解
三步审批完整流程 → 出金生命周期 出金通道路由决策树 → 出金方式总览 § 通道怎么选
Step 6: 银证指令发往招行
SBA 出金编排向招行银证服务(cmb_stock_trans)发送出金指令:
出金指令处理链路:
1. 出金服务 → SBA 出金编排 → 招行银证服务
2. 招行银证服务用 SM2 国密算法加密请求
3. 通过 Socket 发送到招行的 exit_server
4. 招行处理后返回结果SM2 加密流程:
- 加载富途私钥
- 加载招行公钥证书
- 签名 + 加密请求数据
- Base64 编码后发送
Step 7: 银行回调 & 任务完成
招行处理完成后,通过银证双向链路回调系统:
| 回调结果 | 处理 |
|---|---|
result = 0(成功) | Task 状态 → 2(DONE) |
result = -5(超时) | 自动切换到备用 exit_server 重试 |
result = -6(银行拒绝) | Task 标记失败,需人工处理 |
我们这笔正常成功。Task 状态更新为 DONE,用户收到"出金成功"推送。
从提交到到账时间:银证通道是事件驱动的,通常 几秒到几分钟。
入金 vs 出金:关键差异一览
| 维度 | 入金 | 出金 |
|---|---|---|
| 方向 | 用户银行 → 证券账户 | 证券账户 → 用户银行 |
| 方式数 | 10 种 | 12 种 |
| 核心机制 | 自动匹配引擎(五维比对) | 三步审批工作流 |
| 自动化条件 | 5 个自动入账条件 | 6 个银证自动出金条件 |
| 每日限制 | 10 笔/用户(自动入账) | 10 笔/用户(银证自动) |
| 状态数 | 6 个(PENDING→DONE) | 6 个(PENDING→DONE) |
| SBA 模式 | 6 种入金编排模式 | 统一出金编排 |
| 风控检查 | 入金黑名单 + 白名单 | 出金黑名单 + 高风险检测 |
| 量级分布 | eDDA 占 ~78%,工银 ~12%,其他 ~10% | — |
读完之后,去哪里?
| 我想... | 去看 |
|---|---|
| 查一个术语是什么意思 | 术语表 |
| 看 32 个服务的分层架构 | 架构与数据流 |
| 查入金的 10 种方式 | 入金方式总览 |
| 查出金的 12 种通道 | 出金方式总览 |
| 看某家银行的完整规则 | 银行能力矩阵 |
| 理解风控怎么拦截 | 入金排障、出金排障 |
| 了解退款和冲正机制 | 退款与冲正 |
| 理解 SBA 编排 | SBA 概念与数据模型 |
这个页面有帮助吗?