ISO组织推出ISO 8583 :通过所有主要卡网络在收单机构和发卡机构之间传递实时消息的标准。
每当您在销售点设备上刷卡或在线点击“购买”时,它很可能最终会成为一条 ISO 8583 消息,在商户的收单处理器、卡网络和您银行的发卡机构处理器之间发送。
早期,销售点设备或 ATM 可能已经构建并直接将 ISO 8583 消息发送给收单机构,但在当今的电子商务环境中,消息通常以更高级的格式(如 JSON)从商户传递到支付处理器,然后再转换为卡网络的基于 ISO 8583 的格式。
这种方法通过将 ISO 8583 格式的复杂性从其余支付生态系统中抽象出来,简化了流程。
当该标准于 1987 年首次定义时,它包括了消息规范的总体结构以及核心字段的名称和长度,例如字段 2 中的卡号(“主帐号”)和字段 4 中的交易金额。
- 消息以 4 位消息类型指示符代码开始,表示它是授权消息、撤销消息还是其他消息类型。
- 随后是一个位图,告诉接收者存在哪些字段。
它为网络可以使用的几个字段留出了空间,以包含特定于网络的信息,因此各种卡网络规范通过一系列完全不重叠的嵌套字段迅速分化。
该标准的后续版本大大增加了字段数量,减少了新实现对特定于网络的行为的需求。
然而,像 Visa的Base I这样的规范有成千上万的客户端在从大型机到 ATM 等设备的所有设备上运行,因此几乎不可能进行彻底的向后不兼容更改。
因此,这些规范仍然在很大程度上遵循 1987 年原始标准所规定的规则。
该标准还允许在每个字段的序列化方式上具有灵活性:例如,网络可以选择对所有字段使用EBCDIC(大型机青睐的 8 位编码方案),或者选择打包的BCD以尽可能节省数字字段的空间。这使得每个网络(如 Visa、Mastercard 和 Discover)定义的规范最终演变为差异多于相似之处。
基本格式
ISO 8583 消息只能由具有共享规范的各方发送和接收,该规范详细说明了哪些字段存在以及位于什么位置。ISO 8583 消息与其他存储效率高的格式类似,与 JSON 等更详细的格式不同,它只包含值而不包含字段名称。基本消息包含一个“消息类型指示器”,描述发送的消息类型,一个解释哪些字段存在的位图,以及字段本身。
消息报文类型指示器
报文类型指示器是一个四位数代码,用于告知接收方发送的报文类型,如授权报文或撤销报文。 它告诉接收者哪些字段会出现在信息中,哪些字段不会出现在信息中。 虽然规范定义了一组标准值(如 0100 表示授权请求,0110 表示授权响应),但有些网络偏离了这些值,只保留了一般概念。 指示符的序列化方式也因网络而异。 有些网络使用打包 BCD 将其大小减小到 2 字节,而其他网络则使用更简单的格式,如 ASCII 或 EBCDIC 4 字节值。
位图Bitmap
ISO 8583 报文中的大多数字段都是可选的,这就要求发件人传达哪些字段存在,哪些字段缺失。 这可以通过位图来实现,如果字段存在,则每个位设为 1,如果不存在,则设为 0。 例如,位图第一个字节中的 0110 1100 表示字段 2、3、5 和 6 存在。 第一个字节中的第一位是预留位,用于通信是否包含第二个 8 字节位图,当字段超过 64 个时需要使用。 与信息类型指示符类似,位图本身的序列化方式也因网络而异:十六进制、二进制、ASCII 或 EBCDIC 都是可能的选择。
数据元素
发送者按照位图顺序对每个现有字段进行序列化。字段可以是原始字段,包含单个值(例如字符串或整数),也可以是复杂字段,其中包含嵌套字段。原始字段的序列化通常涉及以下列举的因素的组合。编码即使对于更喜欢使用 ASCII 作为自由文本字段的网络,序列化的 ISO 8583 消息通常也不是完全可读的纯文本,因为每个字段的编码因字段类型而异。通常有几种选择:
- EBCDIC 或 ASCII:通常用于自由格式的文本,但有时也适用于所有字段,无论内容如何。一些网络同时支持 EBCDIC 和 ASCII,并根据参与者的偏好在两种格式之间进行转换。主要网络通常默认使用 EBCDIC,因为这是 IBM 大型机在最初构建网络时选择的编码。
- 压缩BCD:通常用于整数,每个数字占用 4 位。此编码与十六进制的 0-9 子集对齐,使其对数值数据节省空间。
- 二进制:偶尔用于固定长度的整数,根据系统的要求,以低端或大端格式编码为 1 或 2 个字节值。
嵌套消息
虽然标准定义了“交易金额”和“商户识别码”等核心字段,但原始标准中的预定义字段列表最终成为卡网络功能开发的限制因素。为了解决这个问题,标准保留了某些“私人用途”字段,卡网络可以利用这些字段根据需要序列化自定义数据。这就是不同网络之间的规范真正开始不同的地方,不仅在它们选择通信的数据方面,而且在序列化每个嵌套字段的方式方面。原始标准对这个主题提供了有限的指导,后来的版本试图解决这一疏忽。序列化嵌套消息通常有三种主要方式:
- 表格:每个字段通常具有固定长度,并且始终包含在内,要么包含其实际值,要么如果为空则替换为默认占位符值。
- 嵌套位图消息:仅序列化存在的字段,使用简化的固定长度版本的顶级位图来指示字段存在。
- 标记长度值 (TLV) 消息:每个字段都序列化为一个元组,其中包含字段编号(“标记”)、字段长度和字段值。此格式由单独的标准ISO 8825定义,该标准概述了[url=https://en.wikipedia.org/wiki/ASN.1]ASN.1[/url]的编码规则。
每种嵌套消息类型的普遍程度因网络而异。您可能会看到美国运通大量使用表格,而只有 Visa 和中国银联使用嵌套位图消息。万事达卡主要坚持使用标签长度值消息,这是大多数卡网络在其所有新子字段中慢慢转向的方向。
表格
原始嵌套消息元素既最简单,又最复杂。在其最简单的形式中,每个子字段按顺序序列化,没有省略任何字段,始终产生固定数量的字节。这对于字段数量较少的基本表来说很好,但对于具有大量可选字段的表来说,由于必须为很少出现的字段发送不必要的填充字符,导致空间效率低下。尝试调整格式以更好地支持这种情况和其他情况会带来很大的复杂性,而实施者最好使用完全不同的子消息类型。这种演变反映了软件开发中的一种常见模式,即实现从简单开始,但随着扩展的添加而逐渐变得复杂。最终,您得到的解决方案对于客户端来说比一个全新的独立概念更复杂。
框架
ISO 8583 消息通常通过长寿命 TCP 套接字发送,如“Visa:半个世纪的高可用性”中所述。因此,需要在消息周围设置一层框架,以便接收者知道在 TCP 数据包流中一条 ISO 8583 消息在哪里结束,另一条消息在哪里开始。这通常通过一个简单的长度指示器来实现,在 ISO 8583 消息前面放置一个 4 字节指示器,告知接收者他们应该为 ISO 8583 消息本身读取多少字节。
构建解析器
解析基本 ISO 8583 消息非常简单,通常只需要实现位图解析器和每个字段的长度定义,以便能够处理顶层的原始元素。很多复杂性来自于正确处理各种类型的嵌套子消息以及每个卡网络实现之间的细微差别。解决这种复杂性的一个有用技术是定义声明性编写消息所需的核心构建块,而不是命令式地单独实现每个不同的字段类型。
在 Increase,我们编写 Ruby 并大量使用Sorbet类型系统。因此,我们使用Sorbet 中的类来定义我们的 ISO 8583 解析器,解析后会为我们提供一个类型安全的消息类 T::Struct
class Message < T::Struct |
错误处理
上述标签长度值消息中的字段missing_tags存储了我们尚未实现的任何标签的哈希图,并确保消息仍然正确往返,而不会因新标签而崩溃。这种面向未来的方式通常仅适用于标签长度值消息,但优雅地处理子消息中的本地错误也是一个有用的概念,可以应用于其余的解析器。虽然卡网络确实会验证消息的整体格式,但仍有很多细微错误不会导致网络级别被拒绝。
这使得优雅的错误处理对于发卡机构处理器尤为重要,因为他们会收到来自世界各地大量收单机构的授权请求。无法解析其中一条消息意味着您的持卡人将被拒绝,当您尝试亲自支付账单时,这是一种特别令人沮丧的体验。然而,对于收单处理器来说,这也是一个有用的概念,因为您经常会在消息响应中看到不合规的子字段。
为了妥善处理嵌套子字段中的错误,您需要对正在解析的字节流进行分区,以便如果之前的字段发生错误,您可以继续处理下一个字段。以之前的附加销售点信息表为例:每个字段都是枚举,您可能希望将它们映射到编程语言中的枚举,而不是处理原始网络值(通常是整数)。如果这样做,您最终会看到一个尚未处理的枚举,此时您的解析器应该能够将其记录为部分错误并继续处理下一个字段。