moov-io/iso8583
Moov的使命是为开发者提供一种简单的方式来创建和集成银行处理到他们自己的软件产品中。我们的开源项目每一个都专注于解决金融服务中的单一职责,并围绕着性能、可扩展性和易用性进行设计。
ISO8583 在 Go 中实现了 ISO 8583 消息读取和写入。ISO 8583 是一种用于银行卡交易消息的国际标准,它定义了消息格式和通信流程。它被全球主要的银行卡网络(如Visa、Mastercard和Verve)所使用。该标准支持银行卡购买、取款、存款、退款、冲正、余额查询、账户之间转账、管理消息、安全密钥交换等各种交易。
目录
项目状态
Moov ISO8583 是一个 Go 包,经过了 彻底的现实世界测试和信任。该项目在真实世界的高风险场景中已经证明了其可靠性和健壮性。如果您发现任何缺失的功能/错误/文档不清晰,请通过 提交 issue 告知我们。谢谢!
Go 模块
本项目使用 Go Modules。请参考 Golang 的安装说明 来设置 Go 环境。您可以下载源代码,我们也提供了 已标记和发布的版本。我们强烈建议您在生产环境中使用已发布的标记版本。
Go 版本支持政策
永远保持最新,绝不落下
尽管我们努力拥抱最新的语言增强功能,但我们也理解需要一定程度的向后兼容性。我们知道并非所有人都可以立即升级到最新版本。我们的理念是向前发展,拥抱新事物,但不会让任何人立即落下。
我们现在支持哪些版本?
截至目前,我们正在支持以下版本,如 setup-go action step 所引用:
stable
(指向当前 Go 版本)oldstable
(指向上一个 Go 版本)
setup-go action 自动管理版本控制,使我们能够始终与最新和前一个 Go 版本保持一致。
这对您意味着什么?
每当 Go 的新版本发布时,我们都会更新我们的系统,并确保我们的项目完全兼容。与此同时,我们将继续支持前一个版本。但是,一旦新版本发布,"前前一个"版本将不再受官方支持。
持续集成
为了确保我们对这些版本的支持承诺,我们已经配置了 GitHub CI 操作,使用当前版本和前一版本的 Go 来测试我们的代码。这意味着,如果您正在使用这两个版本中的任何一个,您都可以放心地使用该项目。
安装
go get github.com/moov-io/iso8583
如何做
定义您的规范
目前,我们已经定义了以下 ISO 8583 规范:
- Spec87ASCII - 1987 版本的规范,使用 ASCII 编码
- Spec87Hex - 1987 版本的规范,使用十六进制编码
Spec87ASCII 适用于大多数用例。只需使用 specs.Spec87ASCII
实例化一个新消息:
isomessage := iso8583.NewMessage(specs.Spec87ASCII)
如果此规范不满足您的需求,我们鼓励您修改它或使用以下信息创建自己的规范。 首先,您需要定义在ISO8583规范中描述的消息字段的格式。每个数据字段都有一种类型及其自身的规范。您可以创建一个"NewBitmap"、"NewString"或"NewNumeric"字段。每个单独的字段规范由几个元素组成:
元素 | 注释 | 示例 |
---|---|---|
长度 | 最大字段长度(字节、字符或数字),对于定长和变长都适用。 | 10 |
描述 | 描述数据字段的内容。 | "主账号" |
编码 | 设置编码类型(ASCII , Hex , Binary , BCD , LBCD , EBCDIC )。 | encoding.ASCII |
前缀 | 设置字段长度和类型的编码(ASCII , Hex , Binary , BCD , EBCDIC )为定长或变长(Fixed , L , LL , LLL , LLLL )。'L'的数量对应于变长中的数字位数。 | prefix.ASCII.Fixed |
填充 (可选) | 设置填充方向和类型。 | padding.Left('0') |
某些ISO8583规范可能没有0号和1号字段,但我们会使用它们来表示MTI和Bitmap。因为从技术上讲,它们只是普通的字段。我们也使用字段规范来描述MTI和Bitmap。我们目前使用String
字段来表示MTI,同时我们有一个单独的Bitmap
字段来表示位图。
以下示例创建了一个包含三个单独字段(不包括MTI和Bitmap)的完整规范:
spec := &iso8583.MessageSpec{
Fields: map[int]field.Field{
0: field.NewString(&field.Spec{
Length: 4,
Description: "消息类型标识符",
Enc: encoding.ASCII,
Pref: prefix.ASCII.Fixed,
}),
1: field.NewBitmap(&field.Spec{
Description: "位图",
Enc: encoding.Hex,
Pref: prefix.Hex.Fixed,
}),
// 消息字段:
2: field.NewString(&field.Spec{
Length: 19,
Description: "主账号",
Enc: encoding.ASCII,
Pref: prefix.ASCII.LL,
}),
3: field.NewNumeric(&field.Spec{
Length: 6,
Description: "处理码",
Enc: encoding.ASCII,
Pref: prefix.ASCII.Fixed,
Pad: padding.Left('0'),
}),
4: field.NewString(&field.Spec{
Length: 12,
Description: "交易金额",
Enc: encoding.ASCII,
Pref: prefix.ASCII.Fixed,
Pad: padding.Left('0'),
}),
},
}
以下示例创建了一个包含三个单独字段(不包括MTI和Bitmap)的完整规范。它与上面的示例不同,通过展示位图字段的可扩展性。这对于定义主位图和次位图的规范很有用。
spec := &iso8583.MessageSpec{
Fields: map[int]field.Field{
0: field.NewString(&field.Spec{
Length: 4,
Description: "消息类型标识符",
Enc: encoding.ASCII,
Pref: prefix.ASCII.Fixed,
}),
1: field.NewBitmap(&field.Spec{
Description: "位图",
Enc: encoding.Hex,
Pref: prefix.Hex.Fixed,
}),
// 消息字段:
2: field.NewString(&field.Spec{
Length: 19,
Description: "主账号",
Enc: encoding.ASCII,
Pref: prefix.ASCII.LL,
}),
3: field.NewNumeric(&field.Spec{
Length: 6,
Description: "处理码",
Enc: encoding.ASCII,
Pref: prefix.ASCII.Fixed,
Pad: padding.Left('0'),
}),
4: field.NewString(&field.Spec{
Length: 12,
Description: "交易金额",
Enc: encoding.ASCII,
Pref: prefix.ASCII.Fixed,
Pad: padding.Left('0'),
}),
// 从1993年的规范中提取
67: field.NewNumeric(&field.Spec{
Length: 2,
Description: "扩展付款数据",
Enc: encoding.ASCII,
Pref: prefix.ASCII.Fixed,
Pad: padding.Left('0'),
}),
},
}
构建和打包消息
定义了规范后,您可以构建一条消息。根据提供的规范,将消息的二进制表示打包,可以直接将其发送到支付系统!
请注意,在下面的示例中,您不需要手动设置位图值,因为它会在打包过程中自动生成。
设置单个字段的值
如果您只需要设置几个字段,可以使用message.Field(id, string)
或message.BinaryField(id, []byte)
轻松设置,如下所示:
// 使用定义的规范创建消息
message := NewMessage(spec)
// 在字段0设置消息类型标识符
message.MTI("0100")
// 根据需要将所有消息字段设置为字符串
err := message.Field(2, "4242424242424242")
// 处理错误
err = message.Field(3, "123456")
// 处理错误
err = message.Field(4, "100")
// 处理错误
// 生成消息的二进制表示rawMessage
rawMessage, err := message.Pack()
// 现在您可以通过网络发送rawMessage
使用单个字段的工作方式仅限于两种类型:string
或[]byte
。底层字段会将输入转换为自己的类型。如果失败,则会返回错误。
使用数据结构设置值
当您需要访问很多字段,并且想要使用字段类型工作时,使用带有message.Marshal(data)
的结构体会更加方便。
首先,您需要定义一个包含要设置的字段的结构体。字段应该对应于规范字段类型。以下是一个示例:
// 列出您想要设置的字段,使用`index`标签添加字段索引或标签(对于复合子字段)
// 使用与消息规范相同的类型
type NetworkManagementRequest struct {
MTI *field.String `index:"0"`
TransmissionDateTime *field.String `index:"7"`
STAN *field.String `index:"11"`
InformationCode *field.String `index:"70"`
}
message := NewMessage(spec)
现在, 将有字段的数据传递到消息中 err := message.Marshal(&NetworkManagementRequest{ MTI: field.NewStringValue("0800"), TransmissionDateTime: field.NewStringValue(time.Now().UTC().Format("060102150405")), STAN: field.NewStringValue("000001"), InformationCode: field.NewStringValue("001"), })
// 对消息进行打包并发送给您的提供商 requestMessage, err := message.Pack()
解析消息并访问数据
当您有一个二进制(打包)消息并知道它遵循的规范时, 您可以解包它并访问数据。 再次, 您有两种数据访问选择: 访问单个字段或使用消息字段值填充结构体。
获取单个字段的值
您可以使用message.GetString(id)
, message.GetBytes(id)
来访问单个字段的值, 如下所示:
message := NewMessage(spec)
message.Unpack(rawMessage)
mti, err := message.GetMTI() // MTI: 0100
// 处理错误
pan, err := message.GetString(2) // 卡号: 4242424242424242
// 处理错误
processingCode, err := message.GetString(3) // 处理码: 123456
// 处理错误
amount, err := message.GetString(4) // 交易金额: 100
// 处理错误
当您获取单个字段的值时, 您仍然限于string
或[]byte
类型。
使用数据结构获取值
要获取多个字段的值及其类型, 只需将指向您想要的数据的结构体指针传递给message.Unmarshal(data)
即可, 如下所示:
// 列出您想要设置的字段, 添加带有字段索引或标签的`index`标签(对于复合子字段)
// 使用与消息规范相同的类型
type NetworkManagementRequest struct {
MTI *field.String `index:"0"`
TransmissionDateTime *field.String `index:"7"`
STAN *field.String `index:"11"`
InformationCode *field.String `index:"70"`
}
message := NewMessage(spec)
// 让我们解包二进制消息
err := message.Unpack(rawMessage)
// 处理错误
// 创建指向空结构体的指针
data := &NetworkManagementRequest{}
// 将字段值获取到数据结构中
err = message.Unmarshal(data)
// 处理错误
// 现在你可以访问字段值
data.MTI.Value() // "0100"
data.TransmissionDateTime.Value() // "220102103212"
data.STAN.Value() // "000001"
data.InformationCode.Value() // "001"
有关完整的代码示例, 请查看./message_test.go。
检查消息字段
该软件包中有一个Describe
函数, 它以人类可读的方式显示所有消息字段。 以下是如何将消息字段及其值打印到标准输出的示例:
// 将消息打印到os.Stdout
iso8583.Describe(message, os.Stdout)
它将产生以下输出:
MTI........................................: 0100
Bitmap.....................................: 000000000000000000000000000000000000000000000000
Bitmap bits................................: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
F000 Message Type Indicator................: 0100
F002 Primary Account Number................: 4242****4242
F003 Processing Code.......................: 123456
F004 Transaction Amount....................: 100
F020 PAN Extended Country Code.............: 4242****4242
F035 Track 2 Data..........................: 4000****0506=2512111123400001230
F036 Track 3 Data..........................: 011234****3445=724724000000000****00300XXXX020200099010=********************==1=100000000000000000**
F045 Track 1 Data..........................: B4815****1896^YATES/EUGENE L^^^356858 00998000000
F052 PIN Data..............................: 12****78
F055 ICC Data – EMV Having Multiple Tags...: ICC ... Tags
默认情况下, 我们应用iso8583.DefaultFilters
来掩盖具有敏感数据的字段的值。 您可以定义自己的过滤器函数并屏蔽特定字段, 如下所示:
filterAll = func(in string, data field.Field) string {
runesInString := utf8.RuneCountInString(in)
return strings.Repeat("*", runesInString)
}
// 仅过滤字段2的值
iso8583.Describe(message, os.Stdout, filterAll(2, filterAll))
// 输出:
// F002 Primary Account Number................: ************
如果您想查看未过滤的值, 可以使用我们定义的无操作过滤器iso8583.DoNotFilterFields
:
// 显示未过滤的字段值
iso8583.Describe(message, os.Stdout, DoNotFilterFields()...)
JSON编码
您可以将消息序列化为JSON格式:
message := iso8583.NewMessage(spec)
message.MTI("0100")
message.Field(2, "4242424242424242")
message.Field(3, "123456")
message.Field(4, "100")
jsonMessage, err := json.Marshal(message)
它将产生以下JSON(位图未包括, 因为它只用于从二进制表示中解包消息):
{
"0":"0100",
"2":"4242424242424242",
"3":123456,
"4":"100"
}
您也可以将JSON解组到iso8583.Message
中:
input := `{"0":"0100","2":"4242424242424242","4":"100"}`
message := NewMessage(spec)
if err := json.Unmarshal([]byte(input), message); err != nil {
// 处理错误
}
// 访问单个字段或使用结构体
网络头
客户端/服务器(ISO主机和端点)之间的所有消息都有一个消息长度头。它可以是4字节ASCII或2字节BCD编码的长度。我们提供一个network.Header
接口来简化网络头的读写。
支持以下网络头:
- Binary2Bytes - 消息长度编码为2个字节, 例如, {0x00 0x73}表示115个字节的消息
- ASCII4Bytes - 消息长度编码为4个字节ASCII, 例如, 0115表示115个字节的消息
- BCD2Bytes - 消息长度编码为2个字节BCD, 例如, {0x01, 0x15}表示115个字节的消息
- VMLH (Visa Message Length Header) - 消息长度编码为2个字节+2个保留字节
您可以从网络连接中读取网络头, 如下所示:
header := network.NewBCD2BytesHeader()
_, err := header.ReadFrom(conn)
if err != nil {
// 处理错误
}
以下是英文到中文的翻译:
// 创建一个缓冲区来保存消息 buf := make([]byte, header.Length()) // 将传入的消息读取到缓冲区中。 read, err := io.ReadFull(conn, buf) if err != nil { // 处理错误 } if reqLen != header.Length() { // 处理错误 }
message := iso8583.NewMessage(specs.Spec87ASCII) message.Unpack(buf)
以下是如何将网络报头写入网络连接的示例:
header := network.NewBCD2BytesHeader() packed, err := message.Pack() if err != nil { // 处理错误 } header.SetLength(len(packed)) _, err = header.WriteTo(conn) if err != nil { // 处理错误 } n, err := conn.Write(packed) if err != nil { // 处理错误 }
CLI
CLI支持以下命令:
display
以人类可读的格式显示ISO8583消息
安装
iso8583
CLI可以从发布页面下载MacOS、Windows和Linux的可执行文件。
以下是安装MacOS版本的示例:
wget -O ./iso8583 https://github.com/moov-io/iso8583/releases/download/v0.4.6/iso8583_0.4.6_darwin_amd64 && chmod +x ./iso8583
现在您可以运行CLI:
➜ ./iso8583
从命令行无缝使用ISO 8583。
使用方法:
iso8583 <命令> [标志]
可用命令:
describe: 以人类可读的格式显示ISO 8583文件
显示
以人类可读的格式显示ISO8583消息
示例:
➜ ./bin/iso8583 describe msg.bin
MTI........................................: 0100
Bitmap.....................................: 000000000000000000000000000000000000000000000000
Bitmap bits................................: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
F000 Message Type Indicator................: 0100
F002 Primary Account Number................: 4242****4242
F003 Processing Code.......................: 123456
F004 Transaction Amount....................: 100
F020 PAN Extended Country Code.............: 4242****4242
F035 Track 2 Data..........................: 4000****0506=2512111123400001230
F036 Track 3 Data..........................: 011234****3445=724724000000000****00300XXXX020200099010=********************==1=100000000000000000**
F045 Track 1 Data..........................: B4815****1896^YATES/EUGENE L^^^356858 00998000000
F052 PIN Data..............................: 12****78
F055 ICC Data – EMV Having Multiple Tags...: ICC ... Tags
您可以通过spec
标志指定使用哪个内置规范来描述消息:
➜ ./bin/iso8583 describe -spec spec87ascii msg.bin
您还可以以JSON格式定义您自己的规范,并使用spec-file
标志使用该规范文件来描述消息:
➜ ./bin/iso8583 describe -spec-file ./examples/specs/spec87ascii.json msg.bin
请查看JSON规范文件spec87ascii.json的示例。
- Moov Metro2提供了一种简单的方法来读取、创建和验证Metro 2格式,这是美国信用局用于消费者信用历史报告的格式。
许可证
Apache 许可证 2.0 - 详见 许可证。