ach 0.4.7 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +8 -8
- data/README.md +2 -0
- data/lib/ach/ach_file.rb +13 -4
- data/lib/ach/ach_file.rb.orig +150 -0
- data/lib/ach/batch.rb +1 -1
- data/lib/ach/records/batch_control.rb +1 -2
- data/lib/ach/records/batch_header.rb +1 -1
- data/lib/ach/records/entry_detail.rb +25 -24
- data/lib/ach/version.rb +1 -1
- metadata +13 -12
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NzUzNDkyMzc0ZDk4ODk2MzhjZTY0MGE5Y2ZlMmFkNmE0NjZiNjVjNw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YzQ4ZGQ1MTAwNGYwOTAxZTFlZDk1OGFkMjJhNDgzNGNiNzBjNWJiYQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MjUwMTUwM2U4NmFkZDI3ZGVjMTgzMDRhZWZkNWMzY2IwYTJiNjU4MjA3YjU2
|
10
|
+
MzYwYjVhYzdkMDVmZDVlMzEyZDEwNWM0YmY4YTU3ODhiMjhmZTgxMWYwOGQ5
|
11
|
+
NmE0NjQxN2RjNTNhODIyYTg4ZWJjYTE5N2NlZTJiNmRhYjYyOGI=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
ODFmY2MzYmM3NTlkMWMwNWU2OTAzYmU1ZTRlZDgyMGMzNzVlZmE5YjE0YzBj
|
14
|
+
NmZjNjhlN2VlNTI0MjNiYzU5NDZhZDU3YWNmYWU1MDFlODBmMGQzMmE4MGQ0
|
15
|
+
ZWQxZjI0ZWQxNjkwNTJlOWUyMjEwZDg5MmQ2OTViYzJiNDEzODY=
|
data/README.md
CHANGED
@@ -70,6 +70,8 @@ ach.batches.first.entries.first.addenda.first.payment_data
|
|
70
70
|
=> "C05992222220280489 1211403932 1211"
|
71
71
|
```
|
72
72
|
|
73
|
+
**Note:** When adding an amount to your ach file, it needs to be in cents. So you'll want to multiply any dollar amounts by 100
|
74
|
+
|
73
75
|
##Copyright
|
74
76
|
|
75
77
|
Copyright (c) 2008-2009 Jared E Morgan, released under the MIT license
|
data/lib/ach/ach_file.rb
CHANGED
@@ -14,7 +14,11 @@ module ACH
|
|
14
14
|
@control = Records::FileControl.new
|
15
15
|
|
16
16
|
if data
|
17
|
-
|
17
|
+
if (data.encode(Encoding.find('ASCII'),ENCODING_OPTIONS) =~ /\n|\r\n/).nil?
|
18
|
+
parse_fixed(data)
|
19
|
+
else
|
20
|
+
parse(data)
|
21
|
+
end
|
18
22
|
end
|
19
23
|
end
|
20
24
|
|
@@ -37,13 +41,12 @@ module ACH
|
|
37
41
|
@control.entry_hash = 0
|
38
42
|
|
39
43
|
@batches.each do | batch |
|
40
|
-
@control.entry_count += batch.entries.
|
44
|
+
@control.entry_count += batch.entries.inject(0) { |total, entry| total + entry.records_count }
|
41
45
|
@control.debit_total += batch.control.debit_total
|
42
46
|
@control.credit_total += batch.control.credit_total
|
43
47
|
@control.entry_hash += batch.control.entry_hash
|
44
48
|
end
|
45
49
|
|
46
|
-
|
47
50
|
records.collect { |r| r.to_ach }.join("\r\n") + "\r\n"
|
48
51
|
end
|
49
52
|
|
@@ -66,6 +69,12 @@ module ACH
|
|
66
69
|
lines.join("\r\n")
|
67
70
|
end
|
68
71
|
|
72
|
+
def parse_fixed data
|
73
|
+
# replace with a space to preserve the record-lengths
|
74
|
+
encoded_data = data.encode(Encoding.find('ASCII'),{:invalid => :replace, :undef => :replace, :replace => ' '})
|
75
|
+
parse encoded_data.scan(/.{94}/).join("\n")
|
76
|
+
end
|
77
|
+
|
69
78
|
def parse data
|
70
79
|
trace_number = 0
|
71
80
|
fh = self.header
|
@@ -89,7 +98,7 @@ module ACH
|
|
89
98
|
batch = ACH::Batch.new
|
90
99
|
bh = batch.header
|
91
100
|
bh.company_name = line[4..19].strip
|
92
|
-
bh.company_identification = line[40..49].
|
101
|
+
bh.company_identification = line[40..49].gsub(/\A1/, '')
|
93
102
|
bh.standard_entry_class_code = line[50..52].strip
|
94
103
|
bh.company_entry_description = line[53..62].strip
|
95
104
|
bh.company_descriptive_date = Date.parse(line[63..68]) rescue nil # this can be various formats
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module ACH
|
4
|
+
class ACHFile
|
5
|
+
include FieldIdentifiers
|
6
|
+
|
7
|
+
attr_reader :batches
|
8
|
+
attr_reader :header
|
9
|
+
attr_reader :control
|
10
|
+
|
11
|
+
def initialize data=nil
|
12
|
+
@batches = []
|
13
|
+
@header = Records::FileHeader.new
|
14
|
+
@control = Records::FileControl.new
|
15
|
+
|
16
|
+
if data
|
17
|
+
if (data =~ /\n|\r\n/).nil?
|
18
|
+
parse_fixed(data)
|
19
|
+
else
|
20
|
+
parse(data)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
records = []
|
27
|
+
records << @header
|
28
|
+
@batches.each { |b| records += b.to_ach }
|
29
|
+
records << @control
|
30
|
+
|
31
|
+
nines_needed = 10 - (records.length % 10)
|
32
|
+
nines_needed = nines_needed % 10
|
33
|
+
nines_needed.times { records << Records::Nines.new() }
|
34
|
+
|
35
|
+
@control.batch_count = @batches.length
|
36
|
+
@control.block_count = (records.length / 10).ceil
|
37
|
+
|
38
|
+
@control.entry_count = 0
|
39
|
+
@control.debit_total = 0
|
40
|
+
@control.credit_total = 0
|
41
|
+
@control.entry_hash = 0
|
42
|
+
|
43
|
+
@batches.each do | batch |
|
44
|
+
@control.entry_count += batch.entries.length
|
45
|
+
@control.debit_total += batch.control.debit_total
|
46
|
+
@control.credit_total += batch.control.credit_total
|
47
|
+
@control.entry_hash += batch.control.entry_hash
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
records.collect { |r| r.to_ach }.join("\r\n") + "\r\n"
|
52
|
+
end
|
53
|
+
|
54
|
+
def report
|
55
|
+
to_s # To ensure correct records
|
56
|
+
lines = []
|
57
|
+
|
58
|
+
@batches.each do | batch |
|
59
|
+
batch.entries.each do | entry |
|
60
|
+
lines << left_justify(entry.individual_name + ": ", 25) +
|
61
|
+
sprintf("% 7d.%02d", entry.amount / 100, entry.amount % 100)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
lines << ""
|
65
|
+
lines << left_justify("Debit Total: ", 25) +
|
66
|
+
sprintf("% 7d.%02d", @control.debit_total / 100, @control.debit_total % 100)
|
67
|
+
lines << left_justify("Credit Total: ", 25) +
|
68
|
+
sprintf("% 7d.%02d", @control.credit_total / 100, @control.credit_total % 100)
|
69
|
+
|
70
|
+
lines.join("\r\n")
|
71
|
+
end
|
72
|
+
|
73
|
+
def parse_fixed data
|
74
|
+
parse data.scan(/.{94}/).join("\n")
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse data
|
78
|
+
trace_number = 0
|
79
|
+
fh = self.header
|
80
|
+
batch = nil
|
81
|
+
bh = nil
|
82
|
+
ed = nil
|
83
|
+
|
84
|
+
data.strip.split(/\n|\r\n/).each do |line|
|
85
|
+
type = line[0].chr
|
86
|
+
case type
|
87
|
+
when '1'
|
88
|
+
fh.immediate_destination = line[03..12].strip
|
89
|
+
fh.immediate_origin = line[13..22].strip
|
90
|
+
fh.transmission_datetime = Time.utc('20'+line[23..24], line[25..26], line[27..28], line[29..30], line[31..32])
|
91
|
+
fh.file_id_modifier = line[33..33]
|
92
|
+
fh.immediate_destination_name = line[40..62].strip
|
93
|
+
fh.immediate_origin_name = line[63..85].strip
|
94
|
+
fh.reference_code = line[86..93].strip
|
95
|
+
when '5'
|
96
|
+
self.batches << batch unless batch.nil?
|
97
|
+
batch = ACH::Batch.new
|
98
|
+
bh = batch.header
|
99
|
+
bh.company_name = line[4..19].strip
|
100
|
+
<<<<<<< HEAD
|
101
|
+
bh.company_identification = line[40..49].strip.gsub(/\A1/, '')
|
102
|
+
=======
|
103
|
+
bh.company_identification = line[41..49]# .strip - do not strip need to preserve trailing spaces
|
104
|
+
>>>>>>> 2dd27ff1c45679b6544a088c4a21f2689123ddf8
|
105
|
+
bh.standard_entry_class_code = line[50..52].strip
|
106
|
+
bh.company_entry_description = line[53..62].strip
|
107
|
+
bh.company_descriptive_date = Date.parse(line[63..68]) rescue nil # this can be various formats
|
108
|
+
bh.effective_entry_date = Date.parse(line[69..74])
|
109
|
+
bh.originating_dfi_identification = line[79..86].strip
|
110
|
+
when '6'
|
111
|
+
ed = ACH::CtxEntryDetail.new
|
112
|
+
ed.transaction_code = line[1..2]
|
113
|
+
ed.routing_number = line[3..11]
|
114
|
+
ed.account_number = line[12..28].strip
|
115
|
+
ed.amount = line[29..38].to_i # cents
|
116
|
+
ed.individual_id_number = line[39..53].strip
|
117
|
+
ed.individual_name = line[54..75].strip
|
118
|
+
ed.originating_dfi_identification = line[79..86]
|
119
|
+
ed.trace_number = line[87..93].to_i
|
120
|
+
batch.entries << ed
|
121
|
+
when '7'
|
122
|
+
type_code = line[1..2]
|
123
|
+
ad = case type_code
|
124
|
+
when '98'
|
125
|
+
ACH::Addendum::NotificationOfChange.new
|
126
|
+
when '99'
|
127
|
+
ACH::Addendum::Return.new
|
128
|
+
else
|
129
|
+
ACH::Addendum.new
|
130
|
+
end
|
131
|
+
ad.type_code = type_code
|
132
|
+
ad.payment_data = line[3..82].strip
|
133
|
+
ad.sequence_number = line[83..86].strip.to_i
|
134
|
+
ad.entry_detail_sequence_number = line[87..93].to_i
|
135
|
+
ed.addenda << ad
|
136
|
+
when '8'
|
137
|
+
# skip
|
138
|
+
when '9'
|
139
|
+
# skip
|
140
|
+
else
|
141
|
+
raise "Didn't recognize type code #{type} for this line:\n#{line}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
self.batches << batch unless batch.nil?
|
146
|
+
batch.entries.each{ |entry| entry.trace_number = (trace_number += 1) }
|
147
|
+
to_s
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
data/lib/ach/batch.rb
CHANGED
@@ -14,8 +14,7 @@ module ACH::Records
|
|
14
14
|
field :debit_total, Integer, lambda { |f| sprintf('%012d', f)}
|
15
15
|
field :credit_total, Integer, lambda { |f| sprintf('%012d', f)}
|
16
16
|
field :company_identification, String,
|
17
|
-
lambda { |f| f.length == 10 ? f : "1#{f}" }, nil, /\A
|
18
|
-
|
17
|
+
lambda { |f| f.length == 10 ? f : "1#{f}" }, nil, /\A.{9,10}\z/
|
19
18
|
field :message_authentication_code, String,
|
20
19
|
lambda { |f| left_justify(f, 19)}, ''
|
21
20
|
|
@@ -12,7 +12,7 @@ module ACH::Records
|
|
12
12
|
field :company_discretionary_data, String,
|
13
13
|
lambda { |f| left_justify(f, 20)}, ''
|
14
14
|
field :company_identification, String,
|
15
|
-
lambda { |f| f.length == 10 ? f : "1#{f}" }, nil, /\A
|
15
|
+
lambda { |f| f.length == 10 ? f : "1#{f}" }, nil, /\A.{9,10}\z/
|
16
16
|
# TODO This should be used to determine whether other records are valid for
|
17
17
|
# this code. Should there be a Class for each code?
|
18
18
|
# The default of PPD is purely for my benefit (Jared Morgan)
|
@@ -23,6 +23,12 @@ module ACH::Records
|
|
23
23
|
field :trace_number, Integer, lambda { |f| sprintf('%07d', f)}, nil,
|
24
24
|
lambda { |n| n.to_s.length <= 7 }
|
25
25
|
|
26
|
+
attr_reader :addenda
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@addenda = []
|
30
|
+
end
|
31
|
+
|
26
32
|
def credit?
|
27
33
|
CREDIT_RECORD_TRANSACTION_CODE_ENDING_DIGITS.include?(@transaction_code[1..1])
|
28
34
|
end
|
@@ -35,35 +41,13 @@ module ACH::Records
|
|
35
41
|
return self.amount
|
36
42
|
end
|
37
43
|
|
38
|
-
end
|
39
|
-
|
40
|
-
class CtxEntryDetail < EntryDetail
|
41
|
-
|
42
|
-
@fields = EntryDetail.fields.slice(0, 6)
|
43
|
-
field :number_of_addenda_records, Integer, lambda { |f| sprintf('%04d', f)}, 0
|
44
|
-
field :individual_name, String, lambda { |f| left_justify(f, 16)}
|
45
|
-
const_field :reserved, ' '
|
46
|
-
field :discretionary_data, String, lambda { |f| left_justify(f, 2)}, ' '
|
47
|
-
field :addenda_record_indicator, Integer,
|
48
|
-
lambda { |f| sprintf('%01d', f)}
|
49
|
-
field :originating_dfi_identification, String,
|
50
|
-
nil, nil, /\A\d{8}\z/
|
51
|
-
field :trace_number, Integer, lambda { |f| sprintf('%07d', f)}
|
52
|
-
|
53
|
-
|
54
|
-
attr_reader :addenda
|
55
|
-
|
56
|
-
def initialize
|
57
|
-
@addenda = []
|
58
|
-
end
|
59
|
-
|
60
44
|
def addenda_records?
|
61
45
|
return !self.addenda.empty?
|
62
46
|
end
|
63
47
|
|
64
48
|
def to_ach
|
65
|
-
self.addenda_record_indicator = (self.addenda.empty? ? 0 : 1)
|
66
|
-
self.number_of_addenda_records = self.addenda.length
|
49
|
+
self.addenda_record_indicator = (self.addenda.empty? ? 0 : 1) if self.respond_to?(:addenda_record_indicator)
|
50
|
+
self.number_of_addenda_records = self.addenda.length if self.respond_to?(:number_of_addenda_records)
|
67
51
|
|
68
52
|
ach_string = super
|
69
53
|
|
@@ -74,5 +58,22 @@ module ACH::Records
|
|
74
58
|
return ach_string
|
75
59
|
end
|
76
60
|
|
61
|
+
# @return [Integer] Length of addenda plus 1, used by Batch#entry_count
|
62
|
+
def records_count
|
63
|
+
1 + self.addenda.length
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class CtxEntryDetail < EntryDetail
|
68
|
+
@fields = EntryDetail.fields.slice(0, 6)
|
69
|
+
field :number_of_addenda_records, Integer, lambda { |f| sprintf('%04d', f)}, 0
|
70
|
+
field :individual_name, String, lambda { |f| left_justify(f, 16)}
|
71
|
+
const_field :reserved, ' '
|
72
|
+
field :discretionary_data, String, lambda { |f| left_justify(f, 2)}, ' '
|
73
|
+
field :addenda_record_indicator, Integer,
|
74
|
+
lambda { |f| sprintf('%01d', f)}
|
75
|
+
field :originating_dfi_identification, String,
|
76
|
+
nil, nil, /\A\d{8}\z/
|
77
|
+
field :trace_number, Integer, lambda { |f| sprintf('%07d', f)}
|
77
78
|
end
|
78
79
|
end
|
data/lib/ach/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ach
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jared Morgan
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-04-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -47,21 +47,22 @@ extensions: []
|
|
47
47
|
extra_rdoc_files:
|
48
48
|
- README.md
|
49
49
|
files:
|
50
|
+
- MIT-LICENSE
|
51
|
+
- README.md
|
50
52
|
- lib/ach.rb
|
51
|
-
- lib/ach/
|
53
|
+
- lib/ach/ach_file.rb
|
54
|
+
- lib/ach/ach_file.rb.orig
|
52
55
|
- lib/ach/batch.rb
|
53
|
-
- lib/ach/
|
54
|
-
- lib/ach/records/
|
55
|
-
- lib/ach/records/file_control.rb
|
56
|
+
- lib/ach/field_identifiers.rb
|
57
|
+
- lib/ach/records/addendum.rb
|
56
58
|
- lib/ach/records/batch_control.rb
|
59
|
+
- lib/ach/records/batch_header.rb
|
57
60
|
- lib/ach/records/entry_detail.rb
|
61
|
+
- lib/ach/records/file_control.rb
|
62
|
+
- lib/ach/records/file_header.rb
|
58
63
|
- lib/ach/records/nines.rb
|
59
|
-
- lib/ach/records/addendum.rb
|
60
64
|
- lib/ach/records/record.rb
|
61
|
-
- lib/ach/
|
62
|
-
- lib/ach/ach_file.rb
|
63
|
-
- MIT-LICENSE
|
64
|
-
- README.md
|
65
|
+
- lib/ach/version.rb
|
65
66
|
homepage: https://github.com/jm81/ach
|
66
67
|
licenses: []
|
67
68
|
metadata: {}
|
@@ -81,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
82
|
version: '0'
|
82
83
|
requirements: []
|
83
84
|
rubyforge_project:
|
84
|
-
rubygems_version: 2.
|
85
|
+
rubygems_version: 2.2.0
|
85
86
|
signing_key:
|
86
87
|
specification_version: 4
|
87
88
|
summary: Helper for building ACH files
|