深色模式
恒生 Hang Seng
本页说明
讲什么:恒生的入金流水匹配、eDDA/eDDI 入金代扣、企业网银出金的完整业务规则,以及与汇丰的关键差异 适合谁:需要深入了解恒生对接细节的产品经理 前置阅读:银行能力矩阵、汇丰 HSBC(推荐先看汇丰,恒生以差异对比为主) 预计阅读:4 分钟 负责人:入金产品经理
核心要点:恒生与汇丰共用 FPS 体系但实现差异大——恒生用 HASE Protocol(非标 JSON),汇丰用标准 FPS XML。建议先看汇丰页面,再看恒生差异。
能力总览
| 能力 | 支持情况 | 协议/通道 |
|---|---|---|
| 入金流水匹配 | ✅ | 多类型流水(ATM/GT/WY/ZP/BP) |
| eDDA 授权签约 | ✅ | eDDA API(内部 SRPC,银行侧 HTTPS REST) |
| eDDI 入金代扣 | ✅ | eDDI API(内部 SRPC,银行侧 HTTPS REST) |
| 出金 | ✅ | 企业网银(method=hase) |
| FPS | ❌ | — |
| 银证 BST | ❌ | — |
恒生是 moomoo 首个接入 eDDI 的银行——项目设计文档明确提到"首家券商接入恒生 eDDI 服务"。
与汇丰的关键差异
恒生和汇丰都支持 eDDA/eDDI,但实现细节有显著差异:
| 维度 | 汇丰 HSBC | 恒生 Hang Seng |
|---|---|---|
| eDDA 模型 | Mandate 授权 | 简化模型(直接 payer/payee) |
| 授权需要 OTP | 是(短信验证码) | 否(数字证书代替) |
| 认证方式 | Token + 基础认证 | P12 数字证书 + SHA256-RSA 签名 |
| 并发控制 | 令牌桶(乐观锁,4/秒) | 串行化(悲观锁,一次一个) |
| 银行返回状态 | ACSC/RJCT/ACCP/ACSP | S(成功)/F(失败)/P(处理中) |
| 交易时段感知 | 无 | 设计有但当前已禁用 |
| 灰度部署 | — | 支持(双 API + 双凭证) |
| 证书来源 | — | 香港邮政 e-Cert (USB 交付) |
入金:流水类型与匹配规则
恒生入金流水分为 5 种类型,每种类型有不同的匹配策略:
| 流水类型 | 含义 | 匹配条件 | 可自动入账 |
|---|---|---|---|
| WY | 网银转账 | 金额相似 + 姓名精确,日期窗口 -3~+2 天 | ✅ 唯一可自动 |
| ATM | ATM 存款 | 金额精确,按批次时间 | ❌ |
| GT | 柜台存款 | 金额精确,按批次时间 | ❌ |
| ZP | 支票存入 | 金额精确 + 姓名可选,日期窗口 -3~+2 天 | ❌ |
| BP | 账单支付 | 金额精确 + 账单账号 | ❌ |
| 默认 | 其他 | 金额相似,日期窗口 -3~+2 天 | ❌ |
WY 类型自动入账条件
只有 WY(网银转账)类型可以自动入账,需同时满足 4 个条件:
- 入金通知类型为普通入金(
NOTICE_TYPE_NORMAL) - 币种完全匹配
- 金额精确相等
- 英文姓名精确匹配
不满足任一条件,WY 流水回退到"相似匹配",需要人工审核。
ATM/GT 批次时间匹配
ATM 和 GT 类型的日期匹配不是看流水日期,而是看批次导入时间(atm_date)——同一秒导入的记录视为同一批次。这是因为 ATM/柜台存款的银行流水到达时间与实际存款时间可能有较大偏差。
金额容差
| 币种 | 容差 | 适用类型 |
|---|---|---|
| HKD | CRM - 20 ≤ 流水 ≤ CRM | WY/默认(相似匹配时) |
| USD | CRM - 3 ≤ 流水 ≤ CRM | WY/默认(相似匹配时) |
ATM、GT、ZP、BP 类型要求金额精确相等,不适用容差。
eDDA/eDDI 入金代扣
eDDA 授权:无需 OTP,数字证书代替
与汇丰不同,恒生的 eDDA 不需要用户输入 OTP 短信验证码,而是通过数字证书签名来认证请求的合法性。这意味着:
- 用户授权流程更短(无需等待和输入短信码)
- 但安全性依赖证书管理——证书过期或丢失会影响整个 eDDI 服务
数字证书详情
- 来源:香港邮政 e-Cert(ecert.gov.hk)
- 格式:P12 (PKCS#12),USB 交付
- 签名算法:SHA256 + RSA-PKCS1_v1_5
- 签名步骤:
- 对请求内容做 SHA256 哈希
- 将哈希值转为大写十六进制字符串
- 对十六进制字符串再做 SHA256 哈希
- 用 RSA 私钥签名
- Base64 编码结果放入
msgSignature请求头
eDDI 请求参数
恒生 eDDI 比汇丰更简化,不需要 mandate_identification:
| 参数 | 说明 | 示例 |
|---|---|---|
| payer_bank | 付款银行代码 | "024"(恒生) |
| payer_id | 付款人账号 | "123456789001" |
| payer_name | 付款人名称 | 用户姓名 |
| payee_bank | 收款银行代码 | "024"(恒生) |
| payee_account_number | 收款账号 | 往来账户(3-6-3)或储蓄账户(3-1-6) |
| transaction_amount | 金额(11位,含2位小数) | "00000012345" = 123.45 |
| currency | 币种 | "HKD" |
| payment_purpose_code | 用途代码 | 01=费用, 07=电商, 08=其他 |
eDDI 状态机
功能已禁用
new_blank 交易时段感知功能在代码中已被注释掉(handles.py L37-42),当前所有请求无论时段均直接进入 wait_process 状态。以下描述为原始设计,非当前运行状态。
交易时段感知(原始设计):如果在非港股交易时段(如晚上)创建 eDDI 指令,不会立即发送到银行,而是进入 new_blank 状态等待交易时段开始后再处理。这是恒生独有的特性,汇丰没有此限制。
银行返回状态码
| 状态码 | 含义 | 系统动作 |
|---|---|---|
| S | 扣款成功 | → end_ok,创建入金流水 |
| F | 扣款失败 | → end_reject,记录失败原因 |
| P | 处理中 | → pending,继续查询 |
渠道接口概览
| 维度 | 说明 |
|---|---|
| 协议类型 | HTTPS REST + P12 数字证书签名 |
| 认证方式 | 香港邮政 e-Cert (P12 PKCS#12 + SHA256-RSA 签名) |
| 银行代码 | "024" |
| 并发模型 | 串行化(悲观锁,一次一个请求) |
| 交易时段感知 | 设计有但当前已禁用(代码已注释) |
eDDI 请求字段完整表
EDDIProcedure(恒生 eDDI 格式)完整字段如下:
| 字段 | 类型 | 描述 | 取值范围/格式 |
|---|---|---|---|
payer_bank | string | 付款银行代码 | "024"=恒生,或其他银行代码 |
payer_id | string | 付款人 ID | AccountNumber(如 "123456789001")或 CustomerID(如 "012N18020900000202") |
payer_name | string | 付款人姓名 | 如 "Logistic Company A" |
debtor_reference | string | 付款方参考 | 如 "Shipping Fee" |
transaction_amount | string(11) | 金额(11 位补零,最后 2 位为分) | "00000012345" = 123.45 HKD |
payee_bank | string | 收款银行代码 | "024" |
payee_account_number | string | 收款账号 | 支票账户: 3-6-3 格式 "333666666333",储蓄账户: 3-1-6 格式 "3331666666" |
payer_type | string | 付款人类型 | "01"=AccountNumber, "02"=CustomerID |
currency | string | 币种 | "HKD" |
payment_purpose_code | string | 付款用途代码 | "01"=收费, "02"=费用, "03"=捐赠, "04"=租金, "05"=保险, "06"=公用事业, "07"=电商, "08"=其他 |
client_transaction_id | string | 客户交易 ID | 系统自动生成,如 "ABC000123456789" |
响应字段:
| 字段 | 描述 |
|---|---|
transaction_id | 恒生交易 ID |
acknowledgement_id | 恒生确认 ID |
error_code | "000"=成功,其他需查开发文档 |
error_reason | 错误描述 |
reject_code | 拒绝码 |
金额格式说明
恒生 eDDI 使用 11 位补零字符串,最后 2 位是分:
| 实际金额 | 传输格式 | 说明 |
|---|---|---|
| 123.45 HKD | "00000012345" | 123.45 × 100 = 12345,补零到 11 位 |
| 10,000.00 HKD | "00001000000" | 10000.00 × 100 = 1000000,补零到 11 位 |
| 0.50 HKD | "00000000050" | 0.50 × 100 = 50,补零到 11 位 |
转换规则:将 decimal 金额 × 100,转为整数后左侧补零到 11 位字符串。
账号格式说明
恒生银行账号有两种格式,取决于账户类型:
| 账户类型 | 格式 | 结构 | 示例 |
|---|---|---|---|
| 支票账户 (Current) | 3-6-3 | BBB-AAAAAA-CCC | "333666666333" |
| 储蓄账户 (Saving) | 3-1-6 | BBB-T-AAAAAA | "3331666666" |
系统需根据传入的账号长度自动判断账户类型。
银行错误码(完整表)
| 错误码 | 含义 | 类型 | 处理方式 |
|---|---|---|---|
000 | 成功 | — | 正常处理 |
BRC_8I1 | 余额不足 | 业务 | 通知用户充值 |
BRC_8RW | 授权问题 | 业务 | 重新授权 |
BRC_8RZ | 账户异常 | 业务 | 联系银行 |
FP2414 | 电子授权未找到 | 授权 | 重新签约 |
FP2415 | eDDA 未激活 | 授权 | 等待激活 |
FP2416 | eDDA 已过期 | 授权 | 重新签约 |
FP2417 | 超过 eDDA 限额 | 授权 | 提升限额 |
CAC_018 | 银行服务不可用 | 系统 | 自动重试 |
CAC_021 | 银行服务不可用 | 系统 | 自动重试 |
CAC_022 | 重复交易 | 幂等 | 转 pending 跟踪 |
CAC_998 | 银行超时 | 系统 | 自动重试 |
CAC_999 | 银行超时 | 系统 | 自动重试 |
BRC_8RK | 银行服务不可用 | 系统 | 自动重试 |
BRC_8RM | 银行服务不可用 | 系统 | 自动重试 |
其他 CAC_* | 其他拒绝 | 业务 | 立即拒绝 |
错误码分类
- BRC 开头:银行业务返回码(Business Return Code),分为可重试(
8RK/8RM)和不可重试(8I1/8RW/8RZ) - FP 开头:eDDA 授权相关错误,通常需要用户重新签约或等待激活
- CAC 开头:通道通信错误(Channel Access Code),大部分可自动重试,
CAC_022为重复交易需特殊处理
moomoo 匹配规则 (HangSengMatch) — 按流水类型分支
系统根据恒生流水的类型码进入不同的匹配分支,每种类型的匹配严格程度不同:
| 流水类型 | 代码 | 匹配规则(金额 / 姓名 / 日期) | 可自动入账 |
|---|---|---|---|
| 网银 (WY) | WY | 金额精确+容差,姓名精确匹配,标准窗口 (-3~+2 天) | ✅ 唯一可自动 |
| ATM | ATM | 金额精确,批次时间 (atm_date) | ❌ |
| 柜台 (GT) | GT | 金额精确,批次时间 (atm_date) | ❌ |
| 支票 (ZP) | ZP | 金额精确,日期窗口 (-3~+2 天) | ❌ |
| 账单 (BP) | BP | 金额精确,账单账号校验 | ❌ |
WY 自动入账 4 条件(全部满足才能自动入账):
notice_type=NOTICE_TYPE_NORMAL(普通入金通知)- 币种完全匹配
- 金额精确相等(比辅助匹配更严格,不走容差逻辑)
- 英文姓名精确匹配
ATM/GT "批次时间匹配"说明:不看流水日期字段,而是看 atm_date——同一秒导入的记录视为同一批次。这是因为 ATM/柜台存款的银行流水到达时间与实际存款时间可能有较大偏差,使用导入批次时间更可靠。
串行化控制
恒生要求一次只能处理一个 eDDI 请求,不能并发。系统使用 InnoDB 行锁实现串行化:
| 参数 | 说明 |
|---|---|
| 锁类型 | InnoDB 行级悲观锁(UserLock 表) |
| 锁粒度 | 一次一个请求 |
| 锁标识 | selnum=886(默认) |
| 灰度锁 | selnum_gray / selnum_formal 分别锁 |
为什么恒生需要串行化而汇丰不需要? 恒生银行的 eDDI 接口不支持并发请求——如果两个扣款同时到达银行,银行会返回错误。所以系统必须排队一个一个发。汇丰则支持一定并发度(令牌桶控制速率)。
灰度部署
恒生 eDDI 支持灰度发布,可以让部分用户走新版本接口:
| 维度 | 灰度环境 | 正式环境 |
|---|---|---|
| API 端点 | ddi_url_gray | ddi_url |
| 凭证 | gray_usrname / gray_psw | username / password |
| 证书 | gray_profile_id | profile_id |
| 串行锁 | selnum_gray | selnum_formal |
| 路由规则 | 灰度用户列表 + user_id 取模 | 默认 |
出金:企业网银
恒生出金通过企业网银批量汇款,方法码为 hase。
| 维度 | 说明 |
|---|---|
| 方法码 | TRANSFER_METHOD_HASE = 'hase' |
| 分类 | 电子银行方法(allEBankMethod) |
| 中文名 | 恒生网银出金 |
| 自动化程度 | 半自动(需企业网银操作) |
与汇丰一样,恒生出金走企业网银通道,不是 eDDI(eDDA/eDDI 仅用于入金代扣)。
需求变更指引
| 变更需求 | 改动位置 | 说明 |
|---|---|---|
| 修改 WY 自动入账条件 | HangSengMatch.php → WY 分支 | 调整姓名/金额/币种匹配条件 |
| 新增流水类型 | HangSengMatch.php → switch 分支 | 添加新 type 的匹配规则 |
| 修改金额容差 | MatchRule.php → amountSimilar() | 调整 HKD -20 / USD -3 阈值 |
| eDDI 新增用途代码 | sba_hase_eddi 请求参数 | 添加 payment_purpose_code |
| 修改串行化策略 | sba_hase_eddi → table_model.py | 调整 UserLock 逻辑 |
| 更换数字证书 | sba_hase_eddi/conf/ | 替换 P12 文件和密码 |
| 修改灰度比例 | sba_hase_eddi → gray_control.py | 调整用户列表/取模规则 |
| 新增可重试错误码 | sba_hase_eddi → handles.py | 修改 hase_bank_service_not_available_map |
| 修改出金审批 | Task.php → $stepTemplates | 调整恒生出金审批模板 |
| 证书续期 | 香港邮政 e-Cert 网站 | 申请新证书,替换 P12 文件 |
常见客诉 Top 3
| # | 用户反馈 | 原因 | 客服话术 |
|---|---|---|---|
| 1 | "eDDA 扣款失败" | BRC_8I1 余额不足 | "请确认恒生银行账户余额充足后重试" |
| 2 | "eDDA 突然不能用了" | 连续余额不足→银行自动取消授权 | "您的 eDDA 授权可能已被银行取消,请重新签署 eDDA 授权" |
| 3 | "扣的金额和申请的不一样" | 银行手续费扣减 | "跨行转账可能产生手续费,实际扣款以银行收费为准" |
监控与告警
| 告警项 | 触发条件 | 严重度 | 处理步骤 |
|---|---|---|---|
| eDDI 并发锁冲突 | InnoDB 行锁等待超时 | 🟡 中 | 检查 sba_hase_eddi 并发配置,降低同时扣款数 |
| BRC_8I1 连续告警 | 同一用户多次余额不足 | 🟡 中 | 提前提醒用户充值,连续多次可能导致授权自动取消 |
| Web 通知接收异常 | 恒生 Web 通知回调失败 | 🔴 高 | 检查回调接口可用性,查看 HTTP 状态码 |
| 银行服务不可用 | CAC_018/021/998/999 | 🟡 中 | 系统自动延迟重试,持续异常联系恒生技术支持 |
读完之后
| 我想... | 去看 |
|---|---|
| 对比汇丰的 eDDA/eDDI 差异 | 汇丰 HSBC |
| 了解匹配引擎的完整逻辑 | 匹配与自动入账 |
| 查 eDDA/eDDI 错误码 | 统一错误码中心 |
| 看恒生在各银行中的位置 | 银行能力矩阵 |
| 了解 eDDA/eDDI 在架构中的角色 | 系统架构与数据流 |
这个页面有帮助吗?