Skip to content

Commit f1e1329

Browse files
committed
Add new binary, llbinary and lllbinary iso fields and add MasterCard communication struct template, which is used to replace the struct used currently in the readme
1 parent d963118 commit f1e1329

19 files changed

+693
-236
lines changed

README.md

Lines changed: 35 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -35,30 +35,44 @@ import "github.com/jattento/go-iso8583/pkg/iso8583"
3535

3636
## Quick start
3737

38+
The API of this package is inspired in the go native json package
39+
therefore it's pretty intuitive to use. Take a look at this!
40+
3841
```go
3942
import "github.com/jattento/go-iso8583/pkg/iso8583"
4043

41-
type PurchaseRequest struct {
42-
MTI iso8583.MTI `iso8583:"mti"`
43-
FirstBitmap iso8583.BITMAP `iso8583:"bitmap,length:64"` // length is the maximum amount of represented elements.
44-
SecondBitmap iso8583.BITMAP `iso8583:"1,length:64"` // length is the maximum amount of represented elements.
45-
PAN iso8583.LLVAR `iso8583:"2"`
46-
ProcessingCode iso8583.VAR `iso8583:"3"`
47-
Amount iso8583.VAR `iso8583:"4,encoding:ebcdic"` // By default ASCII is assumed but dont limit yourself!
48-
DateTime iso8583.VAR `iso8583:"7"`
49-
SystemTraceAuditNumber iso8583.VAR `iso8583:"11,omitempty"` // omitempty is supported!
50-
LocalTransactionTime iso8583.VAR `iso8583:"12"`
51-
LocalTransactionDate iso8583.VAR `iso8583:"-"` // You can explicitly ignore a field.
52-
ExpirationDate iso8583.VAR `iso8583:"14"`
53-
MerchantType iso8583.VAR `iso8583:"18"`
54-
ICC iso8583.LLLVAR `iso8583:"55"`
55-
SettlementCode iso8583.VAR `iso8583:"66"`
56-
MessageNumber iso8583.VAR `iso8583:"71"`
57-
TransactionDescriptor iso8583.VAR `iso8583:"104"`
44+
type exampleMessage struct {
45+
MessageTypeIdentifier iso8583.MTI `iso8583:"mti,length:4,encoding:ebcdic"`
46+
Bitmap iso8583.BITMAP `iso8583:"bitmap,length:64"`
47+
SecondaryBitmap iso8583.BITMAP `iso8583:"1,length:64,encoding:ebcdic,omitempty"`
48+
PrimaryAccountNumber iso8583.LLVAR `iso8583:"2,length:64,encoding:ebcdic,omitempty"`
49+
ProcessingCode iso8583.VAR `iso8583:"3,length:6,encoding:ebcdic,omitempty"`
50+
AmountTransaction iso8583.VAR `iso8583:"4,length:12,encoding:ebcdic,omitempty"`
51+
AmountSettlement iso8583.VAR `iso8583:"5,length:12,encoding:ebcdic,omitempty"`
52+
AmountCardholderBilling iso8583.VAR `iso8583:"6,length:12,encoding:ebcdic,omitempty"`
53+
AcquiringInstitutionIDCode iso8583.LLVAR `iso8583:"32,length:11,encoding:ebcdic,omitempty"`
54+
ForwardingInstitutionIDCode iso8583.LLVAR `iso8583:"33,length:11,encoding:ebcdic,omitempty"`
55+
PrimaryAccountNumberExtended iso8583.LLVAR `iso8583:"34,length:28,encoding:ebcdic,omitempty"`
56+
Track2Data iso8583.LLVAR `iso8583:"35,length:37,encoding:ebcdic,omitempty"`
57+
Track3Data iso8583.LLLVAR `iso8583:"36,length:104,encoding:ebcdic,omitempty"`
58+
AdditionalResponseData iso8583.LLVAR `iso8583:"44,length:25,encoding:ebcdic,omitempty"`
59+
Track1Data iso8583.LLVAR `iso8583:"45,length:76,encoding:ebcdic,omitempty"`
60+
ExpandedAdditionalAmounts iso8583.LLLVAR `iso8583:"46,length:999,encoding:ebcdic,omitempty"`
61+
AdditionalDataNationalUse iso8583.LLLVAR `iso8583:"47,length:999,encoding:ebcdic,omitempty"`
62+
AdditionalDataPrivateUse iso8583.LLLVAR `iso8583:"48,length:999,encoding:ebcdic,omitempty"`
63+
CurrencyCodeTransaction iso8583.VAR `iso8583:"49,length:3,encoding:ebcdic,omitempty"`
64+
CurrencyCodeSettlement iso8583.VAR `iso8583:"50,length:3,encoding:ebcdic,omitempty"`
65+
CurrencyCodeCardholderBilling iso8583.VAR `iso8583:"51,length:3,encoding:ebcdic,omitempty"`
66+
PersonalIDNumberData iso8583.BINARY `iso8583:"52,length:8,omitempty"`
67+
SecurityRelatedControlInformation iso8583.VAR `iso8583:"53,length:16,encoding:ebcdic,omitempty"`
68+
AdditionalAmounts iso8583.LLLVAR `iso8583:"54,length:120,encoding:ebcdic,omitempty"`
69+
IntegratedCircuitCardSystemRelatedData iso8583.LLLBINARY `iso8583:"55,length:999,encoding:ebcdic,omitempty"`
5870
}
71+
```
5972

73+
```go
6074
func GenerateStaticReqBytes() ([]byte, error) {
61-
req := PurchaseRequest{
75+
req := exampleMessage{
6276
MTI: "0100",
6377
// FirstBitmap is generated by library
6478
// SecondBitmap is generated by library
@@ -80,32 +94,12 @@ func GenerateStaticReqBytes() ([]byte, error) {
8094
```go
8195
import "github.com/jattento/go-iso8583/pkg/iso8583"
8296

83-
type PurchaseResponse struct {
84-
MTI iso8583.MTI `iso8583:"mti,length:4"`
85-
FirstBitmap iso8583.BITMAP `iso8583:"bitmap,length:64"` // length is the maximum amount of represented elements.
86-
SecondBitmap iso8583.BITMAP `iso8583:"1,length:64"` // length is the maximum amount of represented elements.
87-
PAN iso8583.LLVAR `iso8583:"2,length:2"` // length is the amount of bytes of the LL part.
88-
ProcessingCode iso8583.VAR `iso8583:"3,length:6"`
89-
Amount iso8583.VAR `iso8583:"4,length:12"`
90-
DateTime iso8583.VAR `iso8583:"7,length:10"`
91-
SystemTraceAuditNumber iso8583.VAR `iso8583:"11,length:6"`
92-
LocalTransactionTime iso8583.VAR `iso8583:"12,length:6"`
93-
LocalTransactionDate iso8583.VAR `iso8583:"13,length:4"`
94-
ExpirationDate iso8583.VAR `iso8583:"14,length:4"`
95-
MerchantType iso8583.VAR `iso8583:"18,length:4"`
96-
ResponseCode iso8583.VAR `iso8583:"39,length:2"`
97-
ICC iso8583.LLLVAR `iso8583:"55,length:3,encoding:ebcdic/ascii"` // LLL and VAR part use different encoding? Use a / to indicate both
98-
SettlementCode iso8583.VAR `iso8583:"66,length:1"`
99-
MessageNumber iso8583.VAR `iso8583:"71,length:4"`
100-
TransactionDescriptor iso8583.VAR `iso8583:"104,length:100"`
101-
}
102-
103-
func ReadResp(byt []byte) (PurchaseResponse,error){
104-
var resp PurchaseResponse
97+
func ReadResp(byt []byte) (exampleMessage,error){
98+
var resp exampleMessage
10599

106100
_, err :=iso8583.Unmarshal(byt,&resp)
107101
if err != nil{
108-
return PurchaseResponse{}, err
102+
return exampleMessage{}, err
109103
}
110104

111105
return resp,nil

pkg/iso8583/binary.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package iso8583
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
)
7+
8+
// BINARY is a []byte implementation of a field,
9+
// it does not contain any special behaviour more than unload all bytes on marshaling and
10+
// reading the specified length on unmarshaling.
11+
type BINARY []byte
12+
13+
// MarshalISO8583 returns a copy of binary content. Encoding and length input are ignored.
14+
func (binary BINARY) MarshalISO8583(length int, enc string) ([]byte, error) {
15+
binaryCopy := make([]byte, len(binary))
16+
copy(binaryCopy, binary)
17+
18+
return binaryCopy, nil
19+
}
20+
21+
// UnmarshalISO8583 reads the length indicated amount of bytes from b and load the BINARY field with it.
22+
// Encoding is ignored.
23+
func (binary *BINARY) UnmarshalISO8583(b []byte, length int, enc string) (int, error) {
24+
if b == nil {
25+
return 0, errors.New("bytes input is nil")
26+
}
27+
28+
if len(b) < length {
29+
return 0, fmt.Errorf("message remain (%v bytes) is shorter than indicated length: %v",
30+
len(b), length)
31+
}
32+
33+
*binary = make([]byte, length)
34+
copy(*binary, b[:length])
35+
36+
return length, nil
37+
}

pkg/iso8583/binary_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package iso8583_test
2+
3+
import (
4+
"github.com/jattento/go-iso8583/pkg/iso8583"
5+
"github.com/stretchr/testify/assert"
6+
"testing"
7+
)
8+
9+
func TestBINARY_UnmarshalISO8583_too_short_input(t *testing.T) {
10+
var binary iso8583.BINARY
11+
12+
n, bmapErr := binary.UnmarshalISO8583([]byte{1, 1, 1}, 64, "ascii")
13+
14+
assert.Equal(t, 0, n)
15+
if assert.NotNil(t, bmapErr) {
16+
assert.Equal(t, bmapErr.Error(), "message remain (3 bytes) is shorter than indicated length: 64")
17+
}
18+
}
19+
20+
func TestBINARY_UnmarshalISO8583_nil_input(t *testing.T) {
21+
var binary iso8583.BINARY
22+
23+
n, bmapErr := binary.UnmarshalISO8583(nil, 64, "ascii")
24+
25+
assert.Equal(t, 0, n)
26+
if assert.NotNil(t, bmapErr) {
27+
assert.Equal(t, bmapErr.Error(), "bytes input is nil")
28+
}
29+
}

pkg/iso8583/bitmap_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func TestBITMAP_MarshalISO8583(t *testing.T) {
2424
assert.Nil(t, bmapErr)
2525
}
2626

27-
func TestBITMAP_UnmarshalISO8583_nil_input(t *testing.T) {
27+
func TestBITMAP_UnmarshalISO8583_too_short_input(t *testing.T) {
2828
var bmap iso8583.BITMAP
2929

3030
n, bmapErr := bmap.UnmarshalISO8583([]byte{1, 1, 1}, 64, "ascii")
@@ -35,7 +35,7 @@ func TestBITMAP_UnmarshalISO8583_nil_input(t *testing.T) {
3535
}
3636
}
3737

38-
func TestBITMAP_UnmarshalISO8583_too_short_input(t *testing.T) {
38+
func TestBITMAP_UnmarshalISO8583_nil_input(t *testing.T) {
3939
var bmap iso8583.BITMAP
4040

4141
n, bmapErr := bmap.UnmarshalISO8583(nil, 64, "ascii")

pkg/iso8583/decode_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func TestUnmarshal(t *testing.T) {
104104
FirstBitmap iso8583.BITMAP `iso8583:"bitmap,length:64"`
105105
SecondBitmap iso8583.BITMAP `iso8583:"1,length:64"`
106106
PAN iso8583.LLVAR `iso8583:"2,length:2"`
107-
ProcessingCode iso8583.VAR `iso8583:"3,length:4"`
107+
ProcessingCode iso8583.BINARY `iso8583:"3,length:4"`
108108
Amount iso8583.VAR `iso8583:"4,length:7"`
109109
ICC iso8583.LLLVAR `iso8583:"55,length:3"`
110110
SettlementCode iso8583.VAR `iso8583:"66,length:1"`
@@ -116,7 +116,7 @@ func TestUnmarshal(t *testing.T) {
116116
FirstBitmap iso8583.BITMAP `iso8583:"bitmap,length:64"`
117117
SecondBitmap iso8583.BITMAP `iso8583:"1,length:64"`
118118
PAN iso8583.LLVAR `iso8583:"2,length:2"`
119-
ProcessingCode iso8583.VAR `iso8583:"3,length:4"`
119+
ProcessingCode iso8583.BINARY `iso8583:"3,length:4"`
120120
Amount iso8583.VAR `iso8583:"4,length:7"`
121121
ICC iso8583.LLLVAR `iso8583:"55,length:3"`
122122
SettlementCode iso8583.VAR `iso8583:"66,length:1"`
@@ -141,7 +141,7 @@ func TestUnmarshal(t *testing.T) {
141141
50: false, 51: false, 52: false, 53: false, 54: false, 55: false, 56: false, 57: false, 58: false,
142142
59: false, 60: false, 61: false, 62: false, 63: false, 64: false}},
143143
PAN: iso8583.LLVAR("1234567891234567"),
144-
ProcessingCode: iso8583.VAR("1000"),
144+
ProcessingCode: iso8583.BINARY("1000"),
145145
Amount: iso8583.VAR("0001000"),
146146
ICC: iso8583.LLLVAR("ABCDEFGH123456789"),
147147
SettlementCode: iso8583.VAR("8"),

pkg/iso8583/doc.go

Lines changed: 0 additions & 76 deletions
This file was deleted.

pkg/iso8583/encode.go

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -124,28 +124,19 @@ func Marshal(v interface{}) ([]byte, error) {
124124
// resolveMarshalFieldValue resolves Marshal return value of a field that must not necessary be a marshaler.
125125
func resolveMarshalFieldValue(v reflect.Value, tag tags) ([]byte, error) {
126126
marshaler, isMarshaler := v.Interface().(Marshaler)
127-
isBytes := v.Kind() == reflect.Slice && v.Type() == reflect.TypeOf([]byte(nil))
128-
isString := v.Kind() == reflect.String
129127

130128
// Priority of marshaling order is marshaler -> bytes -> string
131-
switch {
132-
case isMarshaler:
133-
b, err := marshaler.MarshalISO8583(tag.Length, tag.Encoding)
134-
if err != nil {
135-
return nil, fmt.Errorf("iso8583.marshal: field %s cant be marshaled: %w", tag.Field, err)
136-
}
137-
return b, nil
138-
case isBytes:
139-
return v.Bytes(), nil
140-
case isString:
141-
// ASCII assumed.
142-
return []byte(v.String()), nil
129+
if !isMarshaler {
130+
return nil, fmt.Errorf("iso8583.marshal: field %s does not implement Marshaler interface "+
131+
"but does have iso8583 tags", tag.Field)
143132
}
144133

145-
// value does not implement marshal interface nether is a byte slice
146-
return nil, fmt.Errorf("iso8583.marshal: field %s does not implement Marshaler interface, "+
147-
"is a string or slice of bytes but does have iso8583 tags", tag.Field)
134+
b, err := marshaler.MarshalISO8583(tag.Length, tag.Encoding)
135+
if err != nil {
136+
return nil, fmt.Errorf("iso8583.marshal: field %s cant be marshaled: %w", tag.Field, err)
137+
}
148138

139+
return b, nil
149140
}
150141

151142
// isNil Checks the kind of the value since reflect.IsNil method could panic at some.

pkg/iso8583/encode_test.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ func TestMarshal(t *testing.T) {
5353
Name: "simple_one_field_string_one_bytes",
5454
Run: true,
5555
Input: struct {
56-
MTI string `iso8583:"mti"`
56+
MTI iso8583.BINARY `iso8583:"mti"`
5757
Bitmap iso8583.BITMAP `iso8583:"bitmap,length:64"`
58-
Field2 []byte `iso8583:"2"`
58+
Field2 iso8583.BINARY `iso8583:"2"`
5959
}{
60-
MTI: "field1",
61-
Field2: []byte("field2"),
60+
MTI: iso8583.BINARY("field1"),
61+
Field2: iso8583.BINARY("field2"),
6262
},
6363
OutputError: "",
6464
OutputBytes: appendBytes([]byte("field1"), []byte{0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, []byte("field2")),
@@ -67,12 +67,12 @@ func TestMarshal(t *testing.T) {
6767
Name: "simple_one_field_nil",
6868
Run: true,
6969
Input: struct {
70-
MTI string `iso8583:"mti"`
70+
MTI iso8583.BINARY `iso8583:"mti"`
7171
Bitmap iso8583.BITMAP `iso8583:"bitmap,length:64"`
7272
Field1 *iso8583.VAR `iso8583:"1"`
7373
Field2 iso8583.VAR `iso8583:"2"`
7474
}{
75-
MTI: "1000",
75+
MTI: iso8583.BINARY("1000"),
7676
Field1: nil,
7777
Field2: "1000",
7878
},
@@ -400,8 +400,7 @@ func TestMarshal(t *testing.T) {
400400
}{
401401
Field1: 'q',
402402
},
403-
OutputError: "iso8583.marshal: field mti does not implement Marshaler interface, is a string or slice of " +
404-
"bytes but does have iso8583 tags",
403+
OutputError: "iso8583.marshal: field mti does not implement Marshaler interface but does have iso8583 tags",
405404
OutputBytes: nil,
406405
},
407406
{

0 commit comments

Comments
 (0)