Ipmp Money Transfer
ODTS-53: IPMP 资金划拨平台——XML 报文的银行间转账
目标读者:想理解 ODTS 资金如何真实转出、XML 报文结构、失败处理机制的 BA/PM
相关文档:ODTS-14 (结算), ODTS-52 (外部系统可视化), ODTS-43 (通信架构)
为什么 BA/PM 需要理解资金划拨
场外衍生品交易中,所有 P&L 最终要变成资金流动:票息支付、margin call 催缴、到期结算、敲出结算。这些资金通过 IPMP 从交易台的银行账户转到客户账户(或反过来)。IPMP 出问题 = 交易台无法支付 = 违约。
资金划拨失败的真实代价:
- Margin call 延迟: 如果一笔 IM 催缴的转账因为 XML 报文格式错误被拒,客户当天收不到保证金。第二天市场大幅波动,交易台暴露在无抵押风险中。5000 万名义本金的交易,隔夜波动 3% = 150 万未覆盖风险敞口。
- 票息支付延迟: 雪球产品的月度票息如果因转账失败延后 1 天支付,根据 SAC 主协议,交易台需要支付违约金(通常按日万分之五计算)。1000 万票息 × 0.05% = 每天 5000 元。金额本身不大,但客户关系受损——客户会质疑交易台的运营能力。
- 期初结算延迟: 一笔新交易需要客户将初始保证金打入交易台账户。如果 IPMP 转账失败导致期初结算延迟 1 天,交易无法起息 (value date),交易台损失 1 天的对冲收益。对于 1 亿名义本金的雪球,首日 Delta 约 0.3 = 3000 万对冲头寸。3000 万 × 1 天资金成本(年化 2%)= 1644 元。这事不大,但客户和销售都会追问”为什么还没开始?”
- UNKNOWN 状态——最可怕的情况: IPMP 返回 status=4(不确定),系统不知道银行是否已扣款。运营需要联系银行人工确认。这个过程可能耗时 2-4 小时,甚至跨天。如果这时市场剧烈波动,交易台处于”不知道钱在哪”的状态,风险敞口无法准确衡量。
运营团队专门有 0.5–1 个 FTE 处理转账失败和异常——每天检查失败转账、联系银行、重发、人工对账。
概述
IPMP (Inter-bank Payment Management Platform) 是 ODTS 对接的银行间资金划拨平台。当一笔 CashTransfer 被运营审批通过后,Odyssey cash-manager-service 将转账指令组装为 XML 报文发送至 IPMP,由 IPMP 通过 CNAPS (中国现代化支付系统) 执行实际资金划拨。
转账协议
报文格式
IPMP 使用 XML over HTTP 通信。所有请求和响应包裹在 <ipmp><head>...</head><body>...</body></ipmp> 结构中。
转账指令 (TransMoneyRequest)
<ipmp>
<head>
<reference>CT20250716001</reference> <!-- CashTransfer.id -->
<busiCode>1001</busiCode> <!-- 业务类型: 1001=普通转账 -->
</head>
<body>
<ftOrderNo>CT20250716001</ftOrderNo> <!-- 转账单号(幂等键) -->
<urgent>02</urgent> <!-- 01=加急, 02=普通 -->
<settledAmount>
<date>20250716</date> <!-- 结算日期 YYYYMMDD -->
<currency>CNY</currency> <!-- 币种 -->
<amount>1230456.78</amount> <!-- 金额 -->
</settledAmount>
<orderingCustomer>
<account>3100xxxxxx</account> <!-- 付款方账号(交易台) -->
<bankCode>ICBKCNBJ</bankCode> <!-- 付款方银行SWIFT -->
<name>中金公司</name> <!-- 付款方名称 -->
<publicBankCode>102100000004</publicBankCode> <!-- 付款行联行号 -->
</orderingCustomer>
<beneficiaryCustomer>
<account>6200xxxxxx</account> <!-- 收款方账号(客户) -->
<bankCode>COMMCNSH</bankCode> <!-- 收款方银行SWIFT -->
<name>某某投资有限公司</name> <!-- 收款方名称 -->
<publicBankCode></publicBankCode> <!-- 收款行联行号(可选) -->
</beneficiaryCustomer>
<summary>期权权利金-SNOWBALL202507001</summary> <!-- 转账摘要 -->
</body>
</ipmp>
数据来源: TransMoneyRequestMessage.java (XML VO), MoneyTransfer.java (DTO)
转账结果 (TransMoneyResult)
<ipmp>
<head>
<reference>CT20250716001</reference>
<busiCode>1001</busiCode>
</head>
<body>
<relatedReference>IPMP2025071600001</relatedReference> <!-- IPMP方流水号 -->
<queryReference>QR2025071600001</queryReference> <!-- 查询用引用号 -->
<ftOrderNo>CT20250716001</ftOrderNo>
<instructionStatus>PROCESSING</instructionStatus> <!-- 初始状态 -->
<successAmount>0</successAmount> <!-- 成功金额 -->
<failureAmount>0</failureAmount> <!-- 失败金额 -->
<unknownAmount>0</unknownAmount> <!-- 不确定金额 -->
</body>
</ipmp>
StatusBody — 状态查询响应
轮询状态时, IPMP 返回 StatusBody:
| 字段 | 类型 | 说明 |
|---|---|---|
requestBusiCode | String | 请求业务编码 |
relatedReference | String | IPMP 流水号 |
status | int | 1=处理中, 2=成功, 3=失败, 4=不确定 |
code | String | 结果代码 (SUCCESS/FAILED/UNKNOWN) |
remark | String | 备注 (失败时含原因) |
转账生命周期
CashTransfer PENDING
│ 运营审核批准
▼
CashTransfer CONFIRMED
│
▼
cash-manager-service 组装 XML
│
▼
POST /ipmp/message (转账指令)
├── 成功 → queryReference + instructionStatus=PROCESSING
└── 失败 → 标记 FAILED, 运营重新审批
│
▼
轮询状态 (配置: statusPollIntervalSeconds)
├── SUCCESS → CashTransfer SETTLED
├── FAILED → CashTransfer FAILED (人工干预)
└── UNKNOWN → 报警, 人工核实
│
▼
转账完成 → JUMS 通知客户+运营
转账失败模式
| 失败场景 | IPMP 返回 | 业务影响 |
|---|---|---|
| 余额不足 | status=3, remark=“账户余额不足” | 交易台补充资金后重发。如果当天 16:00 前未能补足,可能构成技术性违约 (technical default) |
| 收款行号错误 | status=3, remark=“收款行不存在” | 修正后重发。但资金可能已进入银行处理流程,需向银行追回——平均耗时 1-3 个工作日 |
| 金额超限 | status=3, remark=“大额转账需授权” | 大额转账(>5000 万)需提前向银行报备,当天处理可能来不及 |
| 银行系统异常 | status=4 (UNKNOWN) | 运营只能打电话问银行柜员。每个 UNKNOWN 状态平均消耗运营 2 小时,月均 5-10 次 = 每月 10-20 小时浪费。 |
| 部分成功 | successAmount < 总金额, failureAmount > 0 | 运营需决定是否补发差额或等退款重发。决策错误可能重复支付——某交易台曾因 UNKNOWN + 银行重复入账,多付 2000 万,耗时两个月追回 |
银行编码映射
IPMP 需要银行 SWIFT 代码和 CNAPS 联行号。系统维护两张映射表:
EdsBank — 银行信息
| 字段 | 说明 | 示例 |
|---|---|---|
bankid | 内部银行 ID | 1001 |
bankname | 银行全称 | 中国工商银行 |
bankabbr | 银行缩写 | ICBC |
ipmpbankcode | IPMP 银行编码 | ICBKCNBJ |
EdsCnaps — CNAPS 联行号
| 字段 | 说明 | 示例 |
|---|---|---|
cnapsPrefix | 联行号前缀 | 10210000 |
cnapsCode | 完整联行号 | 102100000004 |
branchNam | 支行名称 | 中国工商银行北京市分行营业部 |
ipmpBankCode | 对应 IPMP 银行编码 | ICBKCNBJ |
为什么需要两张表: EdsBank 解决”选哪家银行”,EdsCnaps 解决”选哪个支行”。一笔转账需要同时指定银行(通过 SWIFT)和支行(通过 CNAPS)。如果 EdsCnaps 表维护不及时(支行合并、撤销),错误的联行号会导致转账失败——这也是运营团队每季度需要花 2-3 天维护银行信息的业务原因。
关键代码目录
odyssey/cash-manager-service/src/main/java/.../ipmp/
├── money-transfer/
│ ├── TransMoneyRequestMessage.java ← 转账请求 XML VO
│ ├── TransMoneyResultBody.java ← 转账结果 XML VO
│ ├── StatusBody.java ← 状态查询 XML VO
│ ├── MoneyTransfer.java ← 转账 DTO
│ └── IPMPClient.java ← HTTP 客户端封装
├── config/
│ └── IPMPConfig.java ← IPMP 连接配置
├── query/
│ └── QueryStatementBody.java ← 对账单查询 VO
└── polling/
└── TransferStatusPoller.java ← 转账状态轮询