-
Notifications
You must be signed in to change notification settings - Fork 1
/
reader.go
134 lines (125 loc) · 4.25 KB
/
reader.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package cadeft
import (
"bufio"
"fmt"
"io"
)
type Reader struct {
File File
scanner *bufio.Scanner
}
func NewReader(in io.Reader) *Reader {
return &Reader{
scanner: bufio.NewScanner(in),
}
}
// ReadFile will attempt to read the whole EFT file according to the 005 spec from payments canada.
// If no errors are encountered a populated File object is returned that contains the Header, Transactions and Footer.
// Use the FileStreamer object to be able ignore errors and proceed parsing the file.
func (r *Reader) ReadFile() (File, error) {
for r.scanner.Scan() {
line, err := normalize(r.scanner.Text())
if err != nil {
return File{}, fmt.Errorf("failed to read line: %w", err)
}
recordType := line[:1]
if recordType == string(HeaderRecord) {
if err := r.parseARecord(line); err != nil {
return File{}, fmt.Errorf("failed to parse header: %w", err)
}
} else if isTxnRecord(recordType) {
if err := r.parseTxnRecord(line); err != nil {
return File{}, fmt.Errorf("failed to parse txn: %w", err)
}
} else if recordType == string(FooterRecord) {
if err := r.parseZRecord(line); err != nil {
return File{}, fmt.Errorf("failed to parse footer: %w", err)
}
}
}
return r.File, nil
}
func (r *Reader) parseARecord(data string) error {
if len(data) < aRecordMinLength {
return fmt.Errorf("record type A is not required length")
}
fHeader := &FileHeader{}
if err := fHeader.parse(data); err != nil {
return fmt.Errorf("failed to parse file header: %w", err)
}
r.File.Header = fHeader
return nil
}
func (r *Reader) parseTxnRecord(data string) error {
if len(data[commonRecordDataLength:])%segmentLength != 0 {
return fmt.Errorf("record length is not valid multiple of 260, partial txn: %d", len(data[commonRecordDataLength:]))
}
rawTxnSegment := data[commonRecordDataLength:]
numSegments := len(rawTxnSegment) / segmentLength
// make this into a function
recType, err := parseRecordType(data[:1])
if err != nil {
return fmt.Errorf("failed to parse transaction: %w", err)
}
startIdx := 0
endIdx := segmentLength
for i := 0; i < numSegments; i++ {
if isFillerString(rawTxnSegment[startIdx:endIdx]) {
continue
}
switch recType {
case DebitRecord:
debit := Debit{}
if err := debit.Parse(rawTxnSegment[startIdx:endIdx]); err != nil {
return fmt.Errorf("failed to parse debit transaction: %w", err)
}
r.File.Txns = append(r.File.Txns, &debit)
case CreditRecord:
credit := Credit{}
if err := credit.Parse(rawTxnSegment[startIdx:endIdx]); err != nil {
return fmt.Errorf("failed to parse credit transaction: %w", err)
}
r.File.Txns = append(r.File.Txns, &credit)
case ReturnDebitRecord:
debitReturn := DebitReturn{}
if err := debitReturn.Parse(rawTxnSegment[startIdx:endIdx]); err != nil {
return fmt.Errorf("failed to parse debit return transaction: %w", err)
}
r.File.Txns = append(r.File.Txns, &debitReturn)
case ReturnCreditRecord:
creditReturns := CreditReturn{}
if err := creditReturns.Parse(rawTxnSegment[startIdx:endIdx]); err != nil {
return fmt.Errorf("failed to parse credit return transaction: %w", err)
}
r.File.Txns = append(r.File.Txns, &creditReturns)
case CreditReverseRecord:
creditReverse := CreditReverse{}
if err := creditReverse.Parse(rawTxnSegment[startIdx:endIdx]); err != nil {
return fmt.Errorf("failed to parse credit reverse transaction: %w", err)
}
r.File.Txns = append(r.File.Txns, &creditReverse)
case DebitReverseRecord:
debitReverseRecord := DebitReverse{}
if err := debitReverseRecord.Parse(rawTxnSegment[startIdx:endIdx]); err != nil {
return fmt.Errorf("failed to parse debit reverse transaction: %w", err)
}
r.File.Txns = append(r.File.Txns, &debitReverseRecord)
case HeaderRecord, FooterRecord, NoticeOfChangeRecord, NoticeOfChangeHeader, NoticeOfChangeFooter:
return fmt.Errorf("unexpected %s record", recType)
}
startIdx = endIdx
endIdx += segmentLength
}
return nil
}
func (r *Reader) parseZRecord(data string) error {
if len(data) < zRecordMinLength {
return fmt.Errorf("z record does not contain minimum amount of data")
}
footer := &FileFooter{}
if err := footer.Parse(data); err != nil {
return fmt.Errorf("failed to parse file footer: %w", err)
}
r.File.Footer = footer
return nil
}