How-To

Create Own/Proprietary Specifications

iso8583 comes with specification samples in iso8583.specs.py. Feel free to copy sample specification and modify it to your needs.

Multiple specifications can be supported at the same time. Simply declare additional specification dictionary.

Refer to iso8583.specs for configuration details.

Create ISO8583 Message

iso8583 converts a regular Python dict into a bytearray. This dictionary must consist of str keys and str values.

At the minimum it must have key 't' which is the message type (e.g. '0200').

>>> import pprint
>>> import iso8583
>>> from iso8583.specs import default_ascii as spec
>>> decoded = {'t': '0200'}
>>> encoded_raw, encoded = iso8583.encode(decoded, spec)
>>> encoded_raw
bytearray(b'02000000000000000000')
>>> pprint.pp(encoded)
{'t': {'len': b'', 'data': b'0200'},
 'p': {'len': b'', 'data': b'0000000000000000'}}
>>> pprint.pp(decoded)
{'t': '0200', 'p': '0000000000000000'}

Note that primary bitmap was generated automatically.

An ISO8583 message without any fields is not very useful. To add fields to the message simply add '2'-'128' keys with str data and re-encode.

>>> decoded['2'] = 'cardholder PAN'
>>> decoded['3'] = '111111'
>>> decoded['21'] = '021'
>>>
>>> encoded_raw, encoded = iso8583.encode(decoded, spec)
>>> encoded_raw
bytearray(b'0200600008000000000014cardholder PAN111111021')
>>> pprint.pp(encoded)
{'t': {'len': b'', 'data': b'0200'},
 'p': {'len': b'', 'data': b'6000080000000000'},
 '2': {'len': b'14', 'data': b'cardholder PAN'},
 '3': {'len': b'', 'data': b'111111'},
 '21': {'len': b'', 'data': b'021'}}
>>> pprint.pp(decoded)
{'t': '0200',
 'p': '6000080000000000',
 '2': 'cardholder PAN',
 '3': '111111',
 '21': '021'}

Let’s remove some fields and re-encode.

>>> decoded.pop('2', None)
'cardholder PAN'
>>> decoded.pop('3', None)
'111111'
>>>
>>> encoded_raw, encoded = iso8583.encode(decoded, spec)
>>> encoded_raw
bytearray(b'02000000080000000000021')
>>> pprint.pp(encoded)
{'t': {'len': b'', 'data': b'0200'},
 'p': {'len': b'', 'data': b'0000080000000000'},
 '21': {'len': b'', 'data': b'021'}}
>>> pprint.pp(decoded)
{'t': '0200', 'p': '0000080000000000', '21': '021'}

Add Secondary Bitmap

There is no need to explicitly add or remove secondary bitmap. It’s auto generated when at least one '65'-'128' fields is present.

>>> import pprint
>>> import iso8583
>>> from iso8583.specs import default_ascii as spec
>>> decoded = {
...     't': '0200',
...     '102': '111111'}
>>> encoded_raw, encoded = iso8583.encode(decoded, spec)
>>> encoded_raw
bytearray(b'02008000000000000000000000000400000006111111')
>>> pprint.pp(encoded)
{'t': {'len': b'', 'data': b'0200'},
 'p': {'len': b'', 'data': b'8000000000000000'},
 '1': {'len': b'', 'data': b'0000000004000000'},
 '102': {'len': b'06', 'data': b'111111'}}
>>> pprint.pp(decoded)
{'t': '0200', '102': '111111', 'p': '8000000000000000', '1': '0000000004000000'}

Even if secondary (or primary) bitmap is specified it’s overwritten with correct value.

>>> decoded = {
...     't': '0200',
...     'p': 'spam',
...     '1': 'eggs',
...     '102': '111111'}
>>> encoded_raw, encoded = iso8583.encode(decoded, spec)
>>> encoded_raw
bytearray(b'02008000000000000000000000000400000006111111')
>>> pprint.pp(encoded)
{'t': {'len': b'', 'data': b'0200'},
 'p': {'len': b'', 'data': b'8000000000000000'},
 '1': {'len': b'', 'data': b'0000000004000000'},
 '102': {'len': b'06', 'data': b'111111'}}
>>> pprint.pp(decoded)
{'t': '0200', 'p': '8000000000000000', '102': '111111', '1': '0000000004000000'}

Secondary bitmap is removed if it’s not required.

>>> decoded = {
...     't': '0200',
...     'p': 'spam',
...     '1': 'eggs',
...     '21': '051'}
>>> encoded_raw, encoded = iso8583.encode(decoded, spec)
>>> encoded_raw
bytearray(b'02000000080000000000051')
>>> pprint.pp(encoded)
{'t': {'len': b'', 'data': b'0200'},
 'p': {'len': b'', 'data': b'0000080000000000'},
 '21': {'len': b'', 'data': b'051'}}
>>> pprint.pp(decoded)
{'t': '0200', 'p': '0000080000000000', '21': '051'}

Check for Mandatory Fields

Many ISO8583 implementations need to check if all mandatory fields are received. It’s easy to do this using a set of mandatory fields and checking if it’s a subset of the received fields.

>>> import pprint
>>> import iso8583
>>> from iso8583.specs import default_ascii as spec
>>> encoded_raw = b'02008000080000000000000000000400000005106111111'
>>> decoded, encoded = iso8583.decode(encoded_raw, spec)
>>> pprint.pp(decoded)
{'t': '0200',
 'p': '8000080000000000',
 '1': '0000000004000000',
 '21': '051',
 '102': '111111'}
>>> mandatory_fields = frozenset(['2', '21', '102'])
>>> mandatory_fields.issubset(decoded.keys())
False
>>> mandatory_fields = frozenset(['21', '102'])
>>> mandatory_fields.issubset(decoded.keys())
True

Convert to and from JSON

iso8583 output is JSON compatible.

>>> import json
>>> import pprint
>>> import iso8583
>>> from iso8583.specs import default_ascii as spec
>>> encoded_raw = b'0200600008000000000014cardholder PAN111111021'
>>> decoded, encoded = iso8583.decode(encoded_raw, spec)
>>> pprint.pp(decoded)
{'t': '0200',
 'p': '6000080000000000',
 '2': 'cardholder PAN',
 '3': '111111',
 '21': '021'}
>>> decoded_json = json.dumps(decoded)
>>> decoded_json
'{"t": "0200", "p": "6000080000000000", "2": "cardholder PAN", "3": "111111", "21": "021"}'

And back.

>>> encoded_raw, encoded = iso8583.encode(json.loads(decoded_json), spec)
>>> encoded_raw
bytearray(b'0200600008000000000014cardholder PAN111111021')

iso8583.specs specifications are also JSON compatible.

>>> import json
>>> import pprint
>>> import iso8583
>>> from iso8583.specs import default_ascii as spec
>>> spec_json = json.dumps(spec)
>>> decoded = {
...     't': '0200',
...     '2': 'PAN'}
>>> encoded_raw, encoded = iso8583.encode(decoded, json.loads(spec_json))
>>> encoded_raw
bytearray(b'0200400000000000000003PAN')
>>> pprint.pp(encoded)
{'t': {'len': b'', 'data': b'0200'},
 'p': {'len': b'', 'data': b'4000000000000000'},
 '2': {'len': b'03', 'data': b'PAN'}}
>>> pprint.pp(decoded)
{'t': '0200', '2': 'PAN', 'p': '4000000000000000'}

Sending Binary Data

Sometimes data needs to be sent in binary. Let’s add some EMV data as a hexchar string.

>>> import iso8583
>>> from iso8583.specs import default_ascii as spec
>>> decoded = {
...     't': '0200',
...     '55': '9F02060000000123455F340102'}

Let’s define field 55 as an LLLVAR binary field with ascii length. Normally, this should be defined in the specification itself. But for demonstration purposes specification can be changed on the fly.

>>> spec['55']['data_enc'] = 'b'
>>> spec['55']['len_enc'] = 'ascii'
>>> spec['55']['len_type'] = 3
>>> spec['55']['max_len'] = 999

And encode the data.

>>> encoded_raw, encoded = iso8583.encode(decoded, spec)
>>> encoded_raw
bytearray(b'02000000000000000200013\x9f\x02\x06\x00\x00\x00\x01#E_4\x01\x02')

Sending Binary Coded Decimal Data

Some ancient systems require data to be sent in BCD. Let’s send an odd length PAN as an LLVAR BCD field with BCD length. Since, this field has to handle odd data, let’s also define a PAD digit.

>>> import iso8583
>>> from iso8583.specs import default_ascii as spec
>>> decoded = {
...     't': '0200',
...     '2': '121200000002121'}

Let’s define field 2 as an LLVAR BCD field with BCD length measured in nibbles. The field is padded on the right with a '0' because half a byte cannot be sent by itself.

>>> spec['2']['data_enc'] = 'b'
>>> spec['2']['len_enc'] = 'b'
>>> spec['2']['len_type'] = 1
>>> spec['2']['max_len'] = 19
>>> spec['2']['len_count'] = 'nibbles'
>>> spec['2']['right_pad'] = '0'

And encode the data.

>>> encoded_raw, encoded = iso8583.encode(decoded, spec)
>>> encoded_raw
bytearray(b'02004000000000000000\x15\x12\x12\x00\x00\x00\x02\x12\x10')