简介:SWIFT报文格式手册2023年度更新版,面向银行国际结算从业者、贸易金融人员及国际贸易专业师生,用于快速查阅信用证相关报文的字段定义与操作规范。资源包共1个doc文件,约414KB,内容围绕2023年11月18日起生效的最新报文格式、屏幕格式、屏幕计算及来报解析类别展开。手册重点梳理MT7XX系列来报业务,涵盖MT700/MT701开立跟单信用证、MT705、MT707、MT710/MT711、MT720/MT721、MT740等新增、删除与修改的报文类型,并逐项说明MT700/701中M(强制)与O(可选)字段的名称、内容选项、最大长度及必填要求,涉及信用证序号、开证行参考号、适用规则、到期地点与日期、申请人及受益人信息、货币金额、费用条款、单据要求等关键要素。同时强调发报行与收报行间的密押关系,以及信用证发行、修改、通知等流程中的报文沟通规则。已有277人学习,适合作为日常操作查询与信用证制度研究的参考材料。
1. 拆开这份 2023 版 SWIFT 报文手册:MT700/701 到底改了什么
做国际结算的同行大概都有过这种体验:一笔信用证开出去,通知行回电说“域 45A 超长被截断”,或者修改报文发过去,对方回“域 34B 和 32B 币种不一致”。这类问题翻 UCP600 找不到答案,翻行内操作细则也语焉不详,最后还是要回到 SWIFT 报文格式手册里逐域对。这份 2023 年度更新手册(2023/11/18 起生效)就是干这个用的——它把 MT700/701、MT705、MT707 这几个跟单信用证核心报文的域定义、必选/可选状态、长度约束、域间组合规则全部列清楚了。适合银行国际业务部、单证中心、贸易融资岗的从业者,也适合做信用证系统开发的工程师拿来当字段字典。手册本身是文档,不是代码包,但它的价值在于:你写报文拼装逻辑、做来报解析、配屏幕格式的时候,每一个字段的M/O状态和Content/Options就是你的校验规则来源。
2. MT700/701 域结构与组合规则:从字段表到可落地的校验逻辑
2.1 强制域与可选域的边界在哪
MT700 的字段表里,Status列标M的是强制域,标O的是可选域。很多人扫一眼觉得“M 就是必须有,O 就是可以没有”,但实际拼报文时翻车往往出在“条件强制”上。手册里明确列出的强制域包括:27(报文页次)、40A(跟单信用证形式)、20(信用证号码)、40E(适用规则)、31D(到期日及地点)、50(申请人)、59(受益人)、32B(币种金额)、41a(指定银行及兑付方式)、49(保兑指示)。注意 41a 里的a表示这个域有 A 和 D 两种格式变体,选哪个取决于兑付方式。
可选域里最容易踩的是 39A 和 39B 的互斥关系。手册原文写得很直白:“报文中可以出现域 39A 或 39B,但不能同时出现。”这意味着你在做报文校验时,不能只检查单个域是否存在,还要做跨域互斥判断。同理,44C 和 44D 也是二选一。
还有一个容易被忽略的点:42C 和 42a 必须同时出现。手册域使用规则第 2 条明确写了这一条。如果你只填了 42C(汇票付款期限)却忘了 42a(付款人),报文会被对方系统拒掉。而 42M 和 42P 则是各自单独出现,不能和 42C/42a 混用。
提示:做报文模板时,建议把域使用规则单独抽成一张校验规则表,而不是散落在代码的 if-else 里。规则变了改表就行,不用重新发版。
2.2 域 45a/46a/47a 的跨报文分配逻辑
这是 MT700/701 组合里最绕的部分,也是实际业务中最容易出错的环节。当一份信用证内容超过一个 MT700 的容量时,可以用最多三个 MT701 来补充。但 45a(货物描述)、46a(单据要求)、47a(附加条件)这三个域有一个硬约束:每个域必须完整地出现在某一个报文中,不能拆分到两个报文里。
手册给了一组有效组合和无效组合的实例。有效组合比如:MT700 包含 45A,另一个 MT701 包含 46B 和 47B;或者 MT700 不含 45A/46A/47A,第一个 MT701 放 45B,第二个放 46B,第三个放 47B。无效组合的典型是:MT700 有 45A,MT701 里又出现了 45B——同一个域出现了两次,直接判无效。
这里有个编码细节:在 MT700 中,这三个域的代号是 45A、46A、47A;在 MT701 中则变成 45B、46B、47B。做解析的时候,域名的最后一位字母决定了它属于哪个报文层级。
# MT700/701 域分配校验的简化逻辑 # 输入:一个报文列表,每个元素是 dict,包含 msg_type 和 fields def validate_45_46_47_allocation(messages): """ 校验 45a/46a/47a 是否满足跨报文分配规则 messages: [{'msg_type': 'MT700', 'fields': {'45A': '...', '46A': '...'}}, ...] """ # 统计每个域出现的次数 field_count = {'45': 0, '46': 0, '47': 0} for msg in messages: for field_name in msg['fields']: # 取域号前两位判断归属 base = field_name[:2] if base in field_count: field_count[base] += 1 # 每个域最多出现一次 for base, count in field_count.items(): if count > 1: return False, f"域 {base}a 出现了 {count} 次,违反唯一性规则" return True, "分配合法" # 测试:MT700 有 45A,MT701 也有 45B -> 应返回 False msgs = [ {'msg_type': 'MT700', 'fields': {'45A': 'goods desc', '46A': 'docs'}}, {'msg_type': 'MT701', 'fields': {'45B': 'more goods'}} ] print(validate_45_46_47_allocation(msgs)) # 输出: (False, '域 45a 出现了 2 次,违反唯一性规则')这段逻辑的核心是:不管域代号是 A 还是 B,只要基础域号相同(45、46、47),就归为同一个域做计数。实际系统中还需要考虑报文顺序和 27 域的页次编号是否连续,但唯一性校验是第一道关。
2.3 域 27 的页次编号与报文长度约束
域 27 的格式是1!n/1!n,也就是“当前页/总页数”。如果信用证全部装在一个 MT700 里,填1/1;如果是一个 MT700 加一个 MT701,MT700 填1/2,MT701 填2/2。这个域看起来简单,但实际拼报文时经常出现页次不连续或者总页数对不上的情况。
手册里还提了一个关键数字:MT700 开立的跟单信用证长度不超过 10000 个字符(含报头和报尾),而收到的 MT700 报文长度可达 10600 个字符。这个差异说明发报行和收报行的长度计算口径可能不同,做系统的时候不能硬编码一个固定值,要留余量。
# 用简单脚本统计一份 MT700 报文的字符数(含报头报尾) # 假设报文存为 message.txt wc -c message.txt # 如果超过 10000,就需要考虑拆分为 MT700 + MT701参数说明:wc -c统计字节数,SWIFT 报文一般用 ASCII 字符,字节数约等于字符数。如果报文包含中文或其他多字节字符,需要按实际字符数计算。实际业务中更稳妥的做法是在拼装阶段就做长度预估,而不是等报文生成后再检查。
3. MT705 预通知与 MT707 修改:域间依赖与常见组合陷阱
3.1 MT705 的定位与域精简逻辑
MT705 是跟单信用证的预通知报文,由开证行发给通知行,用来简要告知信用证条款。它的关键特征是:预通知的信用证未生效,除非另有声明,开证行还必须及时发送有效的 MT700。
从域结构看,MT705 比 MT700 精简不少。强制域只有 40A、20、31D、50、59、32B 六个。注意 27 域在 MT705 里不是强制的,因为预通知通常不会太长。可选域里保留了 39A/39B/39C、41a、44 系列、45A、57a、79、72。
MT705 的域使用规则和 MT700 有相似之处:39A 和 39B 互斥,44C 和 44D 互斥。但它没有 42C/42a 那组付款相关的域,因为预通知阶段不需要列明汇票细节。
实际业务中,MT705 最常见的坑是:预通知发了,但后续 MT700 迟迟不到,或者 MT700 的条款和 MT705 不一致。手册里写的是“开证行还必须及时发送有效的信用证 MT700”,但“及时”这个词在系统里没法做校验。我一般建议在收到 MT705 后设一个跟踪标记,超过合理工作日数还没收到对应 MT700 的,人工介入催查。
3.2 MT707 修改报文的域依赖关系
MT707 是跟单信用证修改报文,可以由开证行发给通知行,也可以由一家通知行发给另一家通知行,或者由转让行发给通知行。它的域使用规则比 MT700 更复杂,手册里列了 7 条,其中几条特别容易翻车:
第 1 条和第 2 条是一对:如果报文中有 32B(增加信用证金额)或 33B(减少信用证金额),那么 34B(修改后信用证金额)也必须出现;反过来,如果有 34B,那么 32B 或 33B 也必须出现。这意味着你不能只发一个“新金额”而不说明是增还是减。
第 3 条:如果报文中有 23(开证行编号),那么 52a(开证行)必须同时出现。这条规则针对的是“发报行不是开证行”的场景——比如通知行转发修改时,需要用 52a 标明原始开证行。
第 7 条:32B、33B、34B 三个域如果同时出现,货币符号必须一致。这个在手工拼报文时容易忽略,比如 32B 填了 USD,34B 填了 EUR,系统直接拒。
# MT707 域依赖校验示例 def validate_mt707(fields): errors = [] # 规则1&2: 32B/33B 与 34B 的依赖 has_32B = '32B' in fields has_33B = '33B' in fields has_34B = '34B' in fields if (has_32B or has_33B) and not has_34B: errors.append("有 32B 或 33B 但缺少 34B") if has_34B and not (has_32B or has_33B): errors.append("有 34B 但缺少 32B 或 33B") # 规则3: 23 与 52a 的依赖 if '23' in fields and '52a' not in fields: errors.append("有 23 但缺少 52a") # 规则7: 币种一致性 currencies = [] for tag in ['32B', '33B', '34B']: if tag in fields: # 假设金额格式为 "USD12345,67",取前三个字符 currencies.append(fields[tag][:3]) if len(set(currencies)) > 1: errors.append(f"币种不一致: {currencies}") return errors if errors else "校验通过" # 测试 test_fields = { '32B': 'USD50000,00', '34B': 'EUR100000,00', # 币种不一致 '23': 'BANKREF123' # 缺少 52a } print(validate_mt707(test_fields)) # 输出: ['有 23 但缺少 52a', '币种不一致: [\'USD\', \'EUR\']']这段代码把手册里的文字规则翻译成了可执行的校验逻辑。实际系统中,这些规则应该在报文生成前就跑一遍,而不是等对方回拒电才发现问题。
3.3 域 79 在 MT707 中的特殊角色
MT707 的域 79(Narrative)承担了一个特殊功能:当修改涉及到期日、装运地点、金额增减等有专定域的内容时,必须用专定域;其他条款的修改则放在 79 里。手册还规定,如果 MT707 只是简述修改内容,79 里必须包含DETAILS TO FOLLOW字样,而且这个 MT707 不能被视为信用证的有效部分。
这里有个实操中的模糊地带:什么算“专定域能覆盖的修改”,什么算“其他条款”?我的经验是,凡是 31E、32B、33B、34B、44 系列、39 系列能表达的,一律走专定域;涉及条款措辞变更、新增条件、修改单据要求的,走 79。拿不准的时候,宁可放 79 也不要硬塞进专定域,因为专定域的格式约束更严,填错了更容易被拒。
4. 避坑与排查:域校验中最容易翻车的五个场景
4.1 现象:报文被对方系统自动拒绝,回电只写“INVALID FORMAT”
原因:最常见的是域间互斥规则被违反。比如同时填了 39A 和 39B,或者同时填了 44C 和 44D。手册里这些规则写得很清楚,但手工拼报文或者模板设计有缺陷时很容易忽略。
解决:在报文生成前跑一遍完整的域间规则校验。把手册里每个报文的“域使用规则”逐条翻译成校验函数,不要靠人眼检查。上面第 2 章和第 3 章给的代码片段可以直接扩展成完整的校验模块。
4.2 现象:MT700 发出后,通知行反馈“域 45A 内容不完整”
原因:45a 的内容超过了单个报文的容量,但没有正确拆分到 MT701。手册规定 45a 最多 100 行、每行 65 个字符,而且必须完整地出现在一个报文里。如果内容超长,需要把整个 45a 挪到一个 MT701 中,而不是截断。
解决:在拼装阶段就计算 45a 的实际字符数。如果 MT700 剩余容量不够,把 45a 整体移到 MT701。注意 45a 在 MT700 里叫 45A,在 MT701 里叫 45B,域代号要跟着变。
4.3 现象:MT707 修改报文被拒,回电提示“CURRENCY MISMATCH”
原因:32B、33B、34B 三个域的币种不一致。手册域使用规则第 7 条明确要求这三个域表达的货币符号必须一致。实际业务中,如果修改涉及币种变更,不能在 32B/33B/34B 里直接改币种,而是要在 79 域中表达。
解决:在 MT707 校验逻辑里加一条币种一致性检查。如果修改涉及币种变动,走 79 域,不要动 32B/33B/34B 的币种字段。
4.4 现象:MT700 的域 27 填了“1/2”,但对方只收到一个报文
原因:MT701 没有发出去,或者 MT701 的域 27 填的不是“2/2”。手册规定 MT700 填“1/2”时,必须有一个 MT701 填“2/2”来配套。如果 MT701 丢失或页次填错,对方系统会认为报文不完整。
解决:在发送环节做配对检查。每发一个 MT700 且 27 域显示多页时,确认对应的 MT701 已经生成并进入发送队列。接收侧则要检查 27 域的页次是否连续、总页数是否一致。
4.5 现象:域 42C 和 42a 只填了一个,报文被拒
原因:手册域使用规则第 2 条明确写了“域 42C 和 42a 在被使用时必须同时出现”。42C 是汇票付款期限,42a 是付款人,两者是配对关系。只填一个等于信息不完整。
解决:在模板层面把 42C 和 42a 绑定成一组,要么都填,要么都不填。如果付款方式是 BY DEF PAYMENT 或 BY MIXED PYMT,则改用 42P 或 42M,不要碰 42C/42a。
注意:以上五条只是高频场景,实际排查时建议先看对方回电的 error code,再对照手册的域使用规则逐条排除。SWIFT 的回电一般会带一个错误码,但不同银行系统的错误码映射可能不同,最可靠的还是回到手册原文。
5. 从手册到系统:把域规则做成可维护的校验表
手册读完了,代码也写了几段,但真正要让这套东西在系统里跑起来,靠散落的 if-else 是不够的。我的做法是把每个报文类型的域规则抽成一张配置表,用数据驱动校验逻辑。
先看一张简化的规则表结构:
| 报文类型 | 规则类型 | 涉及域 | 规则描述 |
|---|---|---|---|
| MT700 | 互斥 | 39A, 39B | 不能同时出现 |
| MT700 | 互斥 | 44C, 44D | 不能同时出现 |
| MT700 | 配对 | 42C, 42a | 必须同时出现 |
| MT700 | 唯一性 | 45a, 46a, 47a | 每个域在所有 MT700/701 中最多出现一次 |
| MT707 | 依赖 | 32B/33B → 34B | 有 32B 或 33B 时必须有 34B |
| MT707 | 依赖 | 34B → 32B/33B | 有 34B 时必须有 32B 或 33B |
| MT707 | 依赖 | 23 → 52a | 有 23 时必须有 52a |
| MT707 | 一致性 | 32B, 33B, 34B | 币种必须一致 |
这张表可以直接存成 JSON 或 YAML,校验引擎读表执行。规则变了改配置,不用改代码。
# 基于配置表的校验引擎骨架 import json RULES = [ {"msg_type": "MT700", "type": "mutex", "fields": ["39A", "39B"]}, {"msg_type": "MT700", "type": "mutex", "fields": ["44C", "44D"]}, {"msg_type": "MT700", "type": "pair", "fields": ["42C", "42a"]}, {"msg_type": "MT707", "type": "dependency", "trigger": ["32B", "33B"], "required": ["34B"]}, {"msg_type": "MT707", "type": "dependency", "trigger": ["34B"], "required": ["32B", "33B"]}, {"msg_type": "MT707", "type": "dependency", "trigger": ["23"], "required": ["52a"]}, {"msg_type": "MT707", "type": "consistency", "fields": ["32B", "33B", "34B"], "key": "currency"}, ] def validate(msg_type, fields): errors = [] for rule in RULES: if rule["msg_type"] != msg_type: continue if rule["type"] == "mutex": present = [f for f in rule["fields"] if f in fields] if len(present) > 1: errors.append(f"互斥域同时出现: {present}") elif rule["type"] == "pair": present = [f for f in rule["fields"] if f in fields] if len(present) == 1: errors.append(f"配对域只出现一个: {present}") elif rule["type"] == "dependency": if any(t in fields for t in rule["trigger"]): if not any(r in fields for r in rule["required"]): errors.append(f"依赖缺失: 有 {rule['trigger']} 但缺少 {rule['required']}") elif rule["type"] == "consistency": values = [fields[f][:3] for f in rule["fields"] if f in fields] if len(set(values)) > 1: errors.append(f"一致性校验失败: {rule['fields']} 的值不一致") return errors # 测试 MT700 互斥 print(validate("MT700", {"39A": "15/15", "39B": "NOT EXCEEDING USD100000"})) # 输出: ['互斥域同时出现: [\'39A\', \'39B\']'] # 测试 MT707 依赖 print(validate("MT707", {"32B": "USD50000,00", "34B": "USD100000,00"})) # 输出: [] (32B 和 34B 同时存在,满足依赖)这个引擎的扩展点在于:新增报文类型只需要加规则条目,不用动校验函数。规则表可以存在数据库里,也可以做成配置文件随版本发布。手册每年更新,规则表跟着更新就行。
还有一个实操技巧:把手册里的“域详述”部分做成字段字典,每个域的格式约束(比如3!a15d表示 3 位字母加 15 位数字)用正则表达。这样在拼报文时可以先做格式校验,再做域间规则校验,两层过滤能把大部分低级错误挡在发送之前。
import re # 域格式校验示例 FIELD_FORMATS = { "32B": r"^[A-Z]{3}\d{1,15},\d{0,2}$", # 3位字母+数字金额 "31D": r"^\d{6}[A-Za-z0-9\s]{1,29}$", # 6位日期+地点 "20": r"^[A-Za-z0-9]{1,16}$", # 1-16位字母数字 } def validate_format(field_name, value): pattern = FIELD_FORMATS.get(field_name) if pattern and not re.match(pattern, value): return f"域 {field_name} 格式不匹配: {value}" return None这套组合拳打下来,报文生成的成功率会有明显提升。从那以后我每次做新报文类型,都先把手册里的域使用规则和域格式抽成配置表,再写校验引擎,最后才写业务逻辑。顺序反了的话,后面改规则会改到怀疑人生。希望帮到你。
本文还有配套的精品资源,点击获取