Project Documentation
·
Community
·
Blog
Moov's mission is to give developers an easy way to create and integrate bank processing into their own software products. Our open source projects are each focused on solving a single responsibility in financial services and designed around performance, scalability, and ease of use.
ISO8583 implements an ISO 8583 message reader and writer in Go. ISO 8583 is an international standard for card-originated financial transaction messages that defines both message format and communication flow. It's used by major card networks around the globe including Visa, Mastercard, and Verve. The standard supports card purchases, withdrawals, deposits, refunds, reversals, balance inquiries, inter-account transfers, administrative messages, secure key exchanges, and more.
- Project status
- Go module
- ISO8583 CLI
- Learn about ISO 8583
- Getting help
- Contributing
- Related projects
Moov ISO8583 currently offers a Go package with plans for an API in the near future. Please star the project if you are interested in its progress. The project supports generating and parsing ISO8583 messages. Feedback on this early version of the project is appreciated and vital to its success. Please let us know if you encounter any bugs/unclear documentation or have feature suggestions by opening up an issue. Thanks!
This project uses Go Modules and Go v1.18 or newer. See Golang's install instructions for help in setting up Go. You can download the source code and we offer tagged and released versions as well. We highly recommend you use a tagged release for production.
go get github.com/mercadolibre/iso8583
Currently, we support following ISO 8583 specifications:
- Spec87ASCII - 1987 version of the spec with ASCII encoding
- Spec87Hex - 1987 version of the spec with Hex encoding
Spec87ASCII is suitable for the majority of use cases. Simply instantiate a new message using specs.Spec87ASCII
:
isomessage := iso8583.NewMessage(specs.Spec87ASCII)
If this spec does not meet your needs, we encourage you to modify it or create your own using the information below.
First, you need to define the format of the message fields that are described in your ISO8583 specification. Each data field has a type and its own spec. You can create a NewBitmap
, NewString
, or NewNumeric
field. Each individual field spec consists of a few elements:
Element | Notes | Example |
---|---|---|
Length |
Maximum length of field (bytes, characters or digits), for both fixed and variable lengths. | 10 |
Description |
Describes what the data field holds. | "Primary Account Number" |
Enc |
Sets the encoding type (ASCII , Hex , Binary , BCD , LBCD , EBCDIC ). |
encoding.ASCII |
Pref |
Sets the encoding (ASCII , Hex , Binary , BCD , EBCDIC ) of the field length and its type as fixed or variable (Fixed , L , LL , LLL , LLLL ). The number of 'L's corresponds to the number of digits in a variable length. |
prefix.ASCII.Fixed |
Pad (optional) |
Sets padding direction and type. | padding.Left('0') |
While some ISO8583 specifications do not have field 0 and field 1, we use them for MTI and Bitmap. Because technically speaking, they are just regular fields. We use field specs to describe MTI and Bitmap too. We currently use the String
field for MTI, while we have a separate Bitmap
field for the bitmap.
The following example creates a full specification with three individual fields (excluding MTI and Bitmap):
spec := &iso8583.MessageSpec{
Fields: map[int]field.Field{
0: field.NewString(&field.Spec{
Length: 4,
Description: "Message Type Indicator",
Enc: encoding.ASCII,
Pref: prefix.ASCII.Fixed,
}),
1: field.NewBitmap(&field.Spec{
Description: "Bitmap",
Enc: encoding.Hex,
Pref: prefix.Hex.Fixed,
}),
// Message fields:
2: field.NewString(&field.Spec{
Length: 19,
Description: "Primary Account Number",
Enc: encoding.ASCII,
Pref: prefix.ASCII.LL,
}),
3: field.NewNumeric(&field.Spec{
Length: 6,
Description: "Processing Code",
Enc: encoding.ASCII,
Pref: prefix.ASCII.Fixed,
Pad: padding.Left('0'),
}),
4: field.NewString(&field.Spec{
Length: 12,
Description: "Transaction Amount",
Enc: encoding.ASCII,
Pref: prefix.ASCII.Fixed,
Pad: padding.Left('0'),
}),
},
}
After the specification is defined, you can build a message. Having a binary representation of your message that's packed according to the provided spec lets you send it directly to a payment system!
Notice in the examples below, you do not need to set the bitmap value manually, as it is automatically generated for you during packing.
If you need to set few fields, you can easily set them using message.Field(id, string)
or message.BinaryField(id, []byte)
like this:
// create message with defined spec
message := NewMessage(spec)
// set message type indicator at field 0
message.MTI("0100")
// set all message fields you need as strings
err := message.Field(2, "4242424242424242")
// handle error
err = message.Field(3, "123456")
// handle error
err = message.Field(4, "100")
// handle error
// generate binary representation of the message into rawMessage
rawMessage, err := message.Pack()
// now you can send rawMessage over the wire
Working with individual fields is limited to two types: string
or []byte
. Underlying field converts the input into its own type. If it fails, then error is returned.
Accessing individual fields is handy when you want to get value of one or two fields. When you need to access a lot of them and you want to work with field types, using structs with message.Marshal(data)
is more convenient.
First, you need to define a struct with fields you want to set. Fields should correspond to the spec field types. Here is an example:
// list fields you want to set, add `index` tag with field index or tag (for
// composite subfields) use the same types from message specification
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)
// now, pass data with fields into the message
err := message.Marshal(&NetworkManagementRequest{
MTI: field.NewStringValue("0800"),
TransmissionDateTime: field.NewStringValue(time.Now().UTC().Format("060102150405")),
STAN: field.NewStringValue("000001"),
InformationCode: field.NewStringValue("001"),
})
// pack the message and send it to your provider
requestMessage, err := message.Pack()
When you have a binary (packed) message and you know the specification it follows, you can unpack it and access the data. Again, you have two options for data access: access individual fields or populate struct with message field values.
You can access values of individual fields using message.GetString(id)
, message.GetBytes(id)
like this:
message := NewMessage(spec)
message.Unpack(rawMessage)
mti, err := message.GetMTI() // MTI: 0100
// handle error
pan, err := message.GetString(2) // Card number: 4242424242424242
// handle error
processingCode, err := message.GetString(3) // Processing code: 123456
// handle error
amount, err := message.GetString(4) // Transaction amount: 100
// handle error
Again, you are limited to a string
or a []byte
types when you get values of individual fields.
To get values of multiple fields with their types just pass a pointer to a struct for the data you want into message.Unmarshal(data)
like this:
// list fields you want to set, add `index` tag with field index or tag (for
// composite subfields) use the same types from message specification
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)
// let's unpack binary message
err := message.Unpack(rawMessage)
// handle error
// create pointer to empty struct
data := &NetworkManagementRequest{}
// get field values into data struct
err = message.Unmarshal(data)
// handle error
// now you can access field values
data.MTI.Value() // "0100"
data.TransmissionDateTime.Value() // "220102103212"
data.STAN.Value() // "000001"
data.InformationCode.Value() // "001"
For complete code samples please check ./message_test.go.
There is a Describe
function in the package that displays all message fields
in a human-readable way. Here is an example of how you can print message fields
with their values to STDOUT:
// print message to os.Stdout
iso8583.Describe(message, os.Stdout)
and it will produce the following output:
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
by default, we apply iso8583.DefaultFilters
to mask the values of the fields
with sensitive data. You can define your filter functions and redact specific
fields like this:
filterAll = func(in string, data field.Field) string {
runesInString := utf8.RuneCountInString(in)
return strings.Repeat("*", runesInString)
}
// filter only value of the field 2
iso8583.Describe(message, os.Stdout, filterAll(2, filterAll))
// outputs:
// F002 Primary Account Number................: ************
If you want to view unfiltered values, you can use no-op filters iso8583.DoNotFilterFields
that we defined:
// display unfiltered field values
iso8583.Describe(message, os.Stdout, DoNotFilterFields()...)
You can serialize message into JSON format:
message := iso8583.NewMessage(spec)
message.MTI("0100")
message.Field(2, "4242424242424242")
message.Field(3, "123456")
message.Field(4, "100")
jsonMessage, err := json.Marshal(message)
it will produce following JSON:
{
"0":"0100",
"1":"700000000000000000000000000000000000000000000000",
"2":"4242424242424242",
"3":123456,
"4":"100"
}
Also, you can unmarshal JSON into iso8583.Message
:
input := `{"0":"0100","1":"500000000000000000000000000000000000000000000000","2":"4242424242424242","4":"100"}`
message := NewMessage(spec)
if err := json.Unmarshal([]byte(input), message); err != nil {
// handle err
}
// access indidual fields or using struct
All messages between the client/server (ISO host and endpoint) have a message
length header. It can be a 4 bytes ASCII or 2 bytes BCD encoded length. We
provide a network.Header
interface to simplify the reading and writing of the
network header.
Following network headers are supported:
- Binary2Bytes - message length encoded in 2 bytes, e.g, {0x00 0x73} for 115 bytes of the message
- ASCII4Bytes - message length encoded in 4 bytes ASCII, e.g., 0115 for 115 bytes of the message
- BCD2Bytes - message length encoded in 2 bytes BCD, e.g, {0x01, 0x15} for 115 bytes of the message
- VMLH (Visa Message Length Header) - message length encoded in 2 bytes + 2 reserved bytes
You can read network header from the network connection like this:
header := network.NewBCD2BytesHeader()
_, err := header.ReadFrom(conn)
if err != nil {
// handle error
}
// Make a buffer to hold message
buf := make([]byte, header.Length())
// Read the incoming message into the buffer.
read, err := io.ReadFull(conn, buf)
if err != nil {
// handle error
}
if reqLen != header.Length() {
// handle error
}
message := iso8583.NewMessage(specs.Spec87ASCII)
message.Unpack(buf)
Here is an example of how to write network header into network connection:
header := network.NewBCD2BytesHeader()
packed, err := message.Pack()
if err != nil {
// handle error
}
header.SetLength(len(packed))
_, err = header.WriteTo(conn)
if err != nil {
// handle error
}
n, err := conn.Write(packed)
if err != nil {
// handle error
}
CLI suports following command:
display
to display ISO8583 message in a human-readable format
iso8583
CLI is available as downloadable binaries from the releases page for MacOS, Windows and Linux.
Here is an example how to install MacOS version:
wget -O ./iso8583 https://github.com/mercadolibre/iso8583/releases/download/v0.4.6/iso8583_0.4.6_darwin_amd64 && chmod +x ./iso8583
Now you can run CLI:
➜ ./iso8583
Work seamlessly with ISO 8583 from the command line.
Usage:
iso8583 <command> [flags]
Available commands:
describe: display ISO 8583 file in a human-readable format
To display ISO8583 message in a human-readable format
Example:
➜ ./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
You can specify which of the built-in specs to use to the describe message via
the spec
flag:
➜ ./bin/iso8583 describe -spec spec87ascii msg.bin
You can also define your spec in JSON format and describe message using the spec file with spec-file
flag:
➜ ./bin/iso8583 describe -spec-file ./examples/specs/spec87ascii.json msg.bin
Please, check the example of the JSON spec file spec87ascii.json.
channel | info |
---|---|
Project Documentation | Our project documentation available online. |
Twitter @moov | You can follow Moov.io's Twitter feed to get updates on our project(s). You can also tweet us questions or just share blogs or stories. |
GitHub Issue | If you are able to reproduce a problem please open a GitHub Issue under the specific project that caused the error. |
moov-io slack | Join our slack channel (#iso8583 ) to have an interactive discussion about the development of the project. |
While Spec87ASCII is appropriate for most users, we hope to see improvements and variations of this specification for different systems by the community. Please do not hesitate to contribute issues, questions, or PRs to cover new use cases. Tests are also appreciated if possible!
Please review our Contributing guide and Code of Conduct to get started! Check out our issues for first time contributors for something to help out with.
This project uses Go Modules and Go v1.18 or newer. See Golang's install instructions for help setting up Go. You can download the source code and we offer tagged and released versions as well. We highly recommend you use a tagged release for production.
As part of Moov's initiative to offer open source fintech infrastructure, we have a large collection of active projects you may find useful:
-
Moov ACH provides ACH file generation and parsing, supporting all Standard Entry Codes for the primary method of money movement throughout the United States.
-
Moov Watchman offers search functions over numerous trade sanction lists from the United States and European Union.
-
Moov Fed implements utility services for searching the United States Federal Reserve System such as ABA routing numbers, financial institution name lookup, and FedACH and Fedwire routing information.
-
Moov Wire implements an interface to write files for the Fedwire Funds Service, a real-time gross settlement funds transfer system operated by the United States Federal Reserve Banks.
-
Moov ImageCashLetter implements Image Cash Letter (ICL) files used for Check21, X.9 or check truncation files for exchange and remote deposit in the U.S.
-
Moov Metro2 provides a way to easily read, create, and validate Metro 2 format, which is used for consumer credit history reporting by the United States credit bureaus.
Apache License 2.0 - See LICENSE for details.