Skip to content

Commit 1293e63

Browse files
authored
Merge pull request #23 from jattento/feature/add-mastercard-struct-template
Add new binary, llbinary and lllbinary iso fields and add MasterCard …
2 parents d963118 + f1e1329 commit 1293e63

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)