ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SWIFT报文格式解析:MT103与MT202字段规则及自动化校验实战

SWIFT报文格式解析:MT103与MT202字段规则及自动化校验实战 简介《SWIFT报文格式手册.doc》是一份面向银行国际结算人员、外贸单证操作者及金融从业者的2023年度SWIFT报文格式指南重点梳理自2023年11月18日生效的报文更新覆盖MT7XX系列新增、删除和修改情况并围绕MT700/701开立跟单信用证逐一讲解字段含义、M/O强制与可选标识、格式要求和操作准则。包含1个doc文档压缩包大小约414KB便于查阅和打印。已有275人学习/下载适合作为信用证处理、国际结算报文撰写和教学培训的基础参考。内容包括报文格式、屏幕格式、屏幕计算、来报解析类别以及信用证开立、修改、通知流程中发报行与收报行的密押关系和安全传递要点帮助读者快速掌握2023年SWIFT报文变化并规范实务操作。1. SWIFT报文格式手册.doc不是一份可读文档而是一套字段级接口契约做跨境支付、信用证或者银行间头寸调拨多半见过这份SWIFT报文格式手册.doc。它不像普通说明书更像一本字段级接口契约业务场景对应哪条报文字段放在哪、什么格式、选填还是必填全在表格里。真正用到它是联调、构造测试报文、解析上游来报和上线前回归。没它一段MT103就像黑匣子照着拆才能还原业务语义。这篇笔记按实际接SWIFT报文的顺序来怎么把手册读薄怎么写解析器最常翻车的五个细节以及怎么把手册变成自动化校验规则。适合正在啃报文格式的工程师。2. 读懂SWIFT报文格式手册先把MT报文骨架拆清楚拿到手册先别急着翻字段定义页先看目录。SWIFT报文格式手册.doc通常是按报文类型组织的MT103和MT202各自成章。同一套字段号会反复出现但含义跟着报文类型走不能全局套用。先把目录里和你相关的MT圈出来再进入单条报文的字段细节。2.1 报文头、正文与尾部先看{1:}到{5:}的物理结构SWIFT MT报文长的样子不是一行平铺数据而是五个花括号块组成的结构。用MT103举例最直观{1:F01BANKCNBJAXXX0000000000} {2:I103BANKGB2LXXXXN3000} {3:{108:SAMPLE001}} {4: :20:REF20240617001 :23B:CRED :32A:240617USD100000,00 :50K:/12345678 ABC TRADING CO. LTD :59:/GB33BARCLAY2020 JOHN SMITH :70:/INV/INV-2024-0088 :71A:SHA -} {5:{CHK:123456789ABC}}{1:}是基础头固定以F01开头后面跟发报行标识。{2:}是应用头最要紧的是第二位的字母I代表输入到SWIFT网络的报文O代表SWIFT网络下发的报文这个差异直到联调阶段才容易暴露。{3:}是用户头里面常见{108:}存放交易参考号很多系统靠它做对账和去重。{4:}是正文字块字段都在这里手册里的“字段定义”也绝大多数针对这个块。{5:}是尾部常见{CHK:}校验和解析时无需关心。手册的核心内容就是对{4:}块里每条字段的格式描述。理解了物理结构之后看任何MT报文都不会再被“一堆花括号”吓住所有业务信息都在{4:}里排队等待解析。2.2 MT103、MT202、MT199、MT940对接前先分清四类报文同一个字段标签在不同MT里含义可能完全不同。最典型的例子是50a和59aMT103里50a是付款人、59a是收款人但MT202里更多时候只关心58a50a根本不存在。如果一开始就指望“同一套字段解析逻辑通吃”很快会被退报信得晕头转向。报文中文名典型业务关键字段MT103单笔客户汇款跨境电汇、贸易货款20、32A、50a、59a、70、71AMT202金融机构转账代理行清算、头寸调拨20、21、32A、57a、58aMT199自由格式报文查询、退报说明、人工处理20、21、79MT940客户对账单余额对账、入账流水20、25、28、60F、61、62F一个支付项目通常先支持MT103再接MT202MT199只用于异常场景和联调救援。MT940表面上跟支付无关但清结算核对往往靠它如果你负责的是完整资金链路建议顺手把MT940的字段也列进支持清单。读手册时先确认自己负责的是哪一类再决定要不要精读后面那些字段页。2.3 字段标签命名F20、32A、50a这些代号意味着什么手册里字段标签有约定两位数字是字段号紧跟着的字母表示格式变体。比如32A固定是“起息日币种金额”32C在别的报文里可能另有含义。同一个标签出现在不同MT中业务含义由所在报文的字段页决定不存在全局统一解释。还有一个容易绕晕的约定标签里大写和小写意义不同。32A的大写A是字段格式的一部分50a的小写a表示“A/F/K三个变体任选其一”具体用哪个看报文内容。常见做法是先把50A、50F、50K的字符集和行长都查出来再决定用哪个变体避免在50F里塞进50K才能放的中文公司名。格式写法上手册沿用SWIFT的格式简写n是数字a是字母x是SWIFT字符集内任意字符d是可带小数分隔符的数字。比如32A写成6n3a15d含义是日期6位数字、币种3个字母、金额最多15位。多行字段用“4*35x”表示最多4行、每行35个字符。把这张简写表记熟读手册的速度能快一倍。3. 写一个能跑通的SWIFT报文解析器从.doc规范到代码从手册到代码有个不能跳过的中间步骤先把.doc里的表格捞出来变成结构化的字段规则。我一般按这个顺序走转文本、扫目录、切块、建规则表最后再写解析循环。顺序反了容易在报文样例上浪费时间。3.1 先把.doc手册转成可搜索的文本乱码坑一次说清银行里的“SWIFT报文格式手册.doc”大多是内部从官方PDF再整理的带着批注和历史痕迹。直接拿Word打开看没问题想程序化检索就要先转纯文本。我的做法是用LibreOffice做批量化转换mkdir -p ./swift_manual_txt libreoffice --headless --convert-to txt:Text ./SWIFT报文格式手册.doc -o ./swift_manual_txt/转换完第一件事不是看内容而是检查编码。老.doc文件常以GBK存中文字符转出来的txt在你的终端里可能是一堆“锟斤拷”。解决方法是先转一次码或者用grep按字段号定位grep -n 32A ./swift_manual_txt/SWIFT报文格式手册.txt | head -40如果grep结果全是乱码再用iconv转成UTF-8iconv -f GBK -t UTF-8 ./swift_manual_txt/SWIFT报文格式手册.txt swift_manual_utf8.txt注意转换后的txt里表格线会变成一串加号和横线字段名可能和格式描述串行。此时别直接拿整段文本去做自动抽取先按字段号定位逐条人工核对格式列再进规则表。另外如果能拿到官方PDF版建议直接以PDF为准。内部.doc经过多人编辑容易出现“字段变了但备注没改”的情况解析代码的注释里应该标注依据的手册版本以免后续对接方拿着新版质问。3.2 最小实现用Python按{4:}块切出所有字段拿到手册里的标签清单后第一段代码建议做这件事把{4:}正文块切成(tag, value)的列表。有了这个列表后续所有格式校验、必填判断、入库映射都建立在统一的数据结构上。import re def split_swift_text(block_text: str) - list[tuple[str, str]]: 把 {4: 开头} 结尾的正文块切成字段列表。 if block_text.startswith({4:): block_text block_text[len({4:):] if block_text.endswith(}): block_text block_text[:-1] fields [] lines block_text.replace(\r\n, \n).split(\n) current_tag None current_value: list[str] [] for line in lines: line line.strip() if not line: continue if line -: # SWIFT正文块以单独一行 - 结束 break m re.match(r^:([0-9]{2}[A-Z]?):(.*)$, line) if m: if current_tag is not None: fields.append((current_tag, \n.join(current_value))) current_tag m.group(1) current_value [m.group(2).strip()] else: if current_tag is not None: current_value.append(line) if current_tag is not None: fields.append((current_tag, \n.join(current_value))) return fields这段代码有三个关键点。第一先把CRLF统一成LF因为不同模拟器对行尾的处理不一致统一后再切分才能稳定。第二单独处理结束符“-”否则它会被当成72或79字段的续行污染整个字段表。第三字段值是多行时要一直追加到下一个“:标签:”出现才收口这就是50a和59a能跨4行的原因。跑一遍最简单的样例if __name__ __main__: sample {4:\n:20:REF001\n:32A:240617USD1000,00\n:50K:/ACC12345\nACME INC\n-} for tag, value in split_swift_text(sample): print(tag, , value[:40])期望输出是20对应REF00132A对应240617USD1000,0050K对应两行文本。如果输出里混入了“-”或空行说明结束符处理或strip逻辑写错了位置。3.3 解析32A日期、币种、金额的字段格式校验先把最常用的字段解析出来32A是最典型的6位日期、3位币种、最多15位金额金额里用逗号当小数点。这就是手册里“6n3a15d”的直接落地def parse_32a(value: str) - dict: 解析 32A格式固定为 6n3a15d。 示例240617USD1234,56 m re.fullmatch(r(\d{6})([A-Z]{3})([0-9,]), value) if not m: raise ValueError(f32A 格式无法解析: {value}) date_part, currency, amount m.groups() return { date: f20{date_part[0:2]}-{date_part[2:4]}-{date_part[4:6]}, currency: currency, amount: amount.replace(,, .), }参数说明年份只有两位代码里默认补20xx。如果你的系统会处理跨世纪报文建议把它做成可配置参数不要写死。金额里的逗号必须转成小数点后再入库否则落到Oracle或PostgreSQL里会被当成字符串对账时按数字比较就会出错。3.4 字段边界与转义规则三个容易解析错的位置第一是冒号。标签以“:32A:”分隔而x类型字段本身允许半角冒号。如果字段值里出现“:59:”这种片段一个简单的全文split会把它当成新字段导致后面的内容全部错位。按行解析能避开大多数情况因为正常报文不会在续行开头放冒号但对方如果手工拼报文就可能在换行处踩中所以解析出异常长字段时要打日志。第二是换行。50a、59a这类多行字段手册写成4*35x。切分时如果见到换行就当作新字段多行地址会被拆碎反过来直接把整块文本按行切又会被续行干扰。只有“等下一个冒号标签出现才收口”的策略最稳。第三是花括号。{4:}里的字段值理论上不能出现裸花括号因为物理格式把它当块分隔符。联调时经常见到有人把地址里的全角括号写成半角结果{5:}识别失败整条报文被对端当作格式错误退回。这个坑在模拟器里未必会暴露因为模拟器对括号的校验经常放水。4. 对接SWIFT报文格式手册五个常见坑与排查这一章是从真实联调里攒出来的问题清单。每一条都按现象、原因、解决三个步骤拆开方便直接对照排查。4.1 坑一MT与MX混用字段和报文整个对不上现象合作行说“报文已经发出”你按MT103去解析结果拿到一串以FIToFicstmrCdtTrf开头的XML一个冒号标签都见不到。原因SWIFT有MT和MX两套体系。MT基于ISO 15022MX基于ISO 20022两者表达同一笔汇款的方式完全不同。手册封面写着MT但上游通道实际给的是MX解析逻辑自然全部落空。解决对接前期先确认渠道给的是MT还是MX。解析入口处加一个前置判断报文以{2:开头还是以XML命名空间开头后续走不同解析分支。不要试图让一个解析器同时兼容两种格式那会让字段映射到处是if-else。4.2 坑二字段可选性误判必填校验放错位置现象本地系统校验MT103全部通过发到对端却被退报原因是50a缺失。本地库里50a明明允许为空。原因手册里每个字段标了M、O、C三种属性M是必选O是可选C是条件必选。很多工程师看到“50a是付款人”下意识把它当成全局必填但MT202根本不含50a。反过来MT103里50a是必选自己的系统却用全局可选配置漏掉了。解决必填校验必须绑定到MT级别不能做全局规则。我维护字段规则表时用三个键字段标签、适用MT、条件表达式。条件表达式单独写函数比如“挡板是付款人字段当且仅当报文类型是MT103且存在50F/50A/50K之一时必填”。这样写出来的规则每个MT都有自己那一份。4.3 坑三字符集与长度中文和超长字段直接翻车现象50F字段放了中文公司名对端解析出来全是问号59a第四行被截断收款人账号缺了两位。原因MT报文标准字符集里没有中文x类型字段只接受SWIFT基本字符集。中文进入网络前需要做字符集过滤或全角转半角处理。另一个隐蔽原因是长度单位手册里的35x是按字符数计算但中间件经常按UTF-8字节数截断。一个中文占3字节自然截错位置。解决对外发送的报文字段先做全角转半角剔除不在标准字符集里的字符对内存储统一按字符数截断并保留截断前后的日志。字符集问题在模拟器里很难暴露因为模拟器校验宽松所以上线前一定要拿真实网络样本跑一遍。4.4 坑四报文头方向差异I/O开头不一样现象解析器按输入的{2:I103...}读取块内容真实网络回执里却是{2:O103...}字段对不上还报了时间字段非法。原因{2:}块里I开头表示发送到SWIFT网络的输入报文O开头表示SWIFT网络下发的输出报文二者后边携带的地址、时间字段排列不同。手册多数情况下按O展示部分模拟器却按I生成解析时一旦忽略方向位字段会全部错位。解决解析{2:}时先读出第二位是I还是O再决定后续字段怎么切。路由、去重、审计都得用完整头信息不要只截{4:}正文。建议在代码里把“方向”和“报文类型”存成两个独立字段排查问题时能直接定位。4.5 坑五手册版本更新字段定义被悄悄改掉现象去年跑得好好的校验规则今年同一段报文过不了报错指向20字段格式。原因SWIFT每年发布技术更新可能调整可选性、新增代码值、甚至废弃某个字段。网盘里流传的旧版.doc不会自动更新线上规则表还停在上一版新旧版本一对照就出问题。遇到过字段标签直接作废、换成新标签的情况。解决手册要标版本号规则表也要标“依据手册版本”。每年新规生效前从官方网站拉差异清单对照规则表逐条修改再跑一遍历史报文回归。不要轻信“这份手册一直没变”报文格式是每年有变化的。5. 进阶用法把手册做成自动化校验器用“黄金报文”夹住回归5.1 字段规则表从手册描述到可执行的JSON手册里“20”那一行写的是“16x必选”翻译成规则表就是一个JSON条目。我习惯把规则表放在项目仓库里跟着代码一起走版本控制这样手册更新时能直接看到diff{ 20: {required: true, max_length: 16}, 32A: {required: true, regex: ^\\d{6}[A-Z]{3}[0-9,]$}, 50a: {required: true, variants: [50A, 50F, 50K]} }提示JSON里写正则时反斜杠要转义如果规则多建议单独建一个rules文件不要硬塞进主代码文件里。5.2 用规则表做批量校验把前面写好的split_swift_text和这张规则表接在一起就是一个最小可用的校验器def validate_swift(payload: str, rules: dict) - list[str]: fields split_swift_text(payload) errors [] for tag, value in fields: rule rules.get(tag) if rule is None: errors.append(f未知字段: {tag}) continue if regex in rule and re.search(rule[regex], value) is None: errors.append(f{tag} 不符合格式: {value[:40]}) if max_length in rule and len(value) rule[max_length]: errors.append(f{tag} 超长: {len(value)} 字符) return errors逻辑说明先把未知字段找出来这是最容易被忽略的坑再做格式和长度校验。通用循环只做“能确定的事”字段之间的联动校验比如C类条件必选需要单独写函数。不要让通用循环变成一堆业务if-else。5.3 生产环境验证习惯维护一组“黄金报文”我从老同事那里学来的习惯每个报文类型建一个samples目录里面分“正例”和“反例”。正例从对端或模拟器拿真实报文反例是故意缺字段、超长、坏格式的样本。每次改动解析器或更新规则表跑一遍测试pytest test_swift_parser.py -q所有反例必须有断言断言的是“一定会报错”不是“应该没事”。这个习惯帮我拦下过两次版本更新引发的回归。如果你用C#维护同样规则把校验逻辑写成DataAnnotations也能达到相同效果关键是规则和报文样例一起进版本库而不是只存代码。这些年做报文对接我最大的感受是报文格式不难难的是所有细节都来自手册而不是经验。每次对不上先怀疑版本和方向再怀疑字符集最后才怀疑自己标签写错。按这个顺序排查能少走很多弯路。希望这篇笔记能帮到你也祝你那头联调一次过。本文还有配套的精品资源点击获取
返回列表