summaryrefslogtreecommitdiff
path: root/FS/FS/pay_batch/nacha.pm
blob: 23dda4c1c2eba63c1ed30a58fefb11eb24b671ac (plain)
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package FS::pay_batch::nacha;

use strict;
use vars qw( %import_info %export_info $name $conf $entry_hash $DEBUG );
use Date::Format;
#use Time::Local 'timelocal';
#use FS::Conf;

$name = 'NACHA';
my $i;
$DEBUG = 0;

%import_info = (
  #XXX stub finish me
  'filetype' => 'CSV',
  'fields' => [
  ],
  'hook' => sub {
    my $hash = shift;
  },
  'approved' => sub { 1 },
  'declined' => sub { 0 },
);

%export_info = (

  #optional
  init => sub {
    $conf = shift;
  },

  delimiter => '',


  header => sub {
    my( $pay_batch, $cust_pay_batch_arrayref ) = @_;

    $conf->config('batchconfig-nacha-destination') =~ /^\s*(\d{9})\s*$/
      or die 'illegal NACHA Destination';
    my $dest = $1;

    my $dest_name = $conf->config('batchconfig-nacha-destination_name');
    $dest_name = substr( $dest_name. (' 'x23), 0, 23);

    $conf->config('batchconfig-nacha-origin') =~ /^\s*(\d{10})\s*$/
      or die 'illegal NACHA Origin';
    my $origin = $1;

    my $company = $conf->config('company_name', $pay_batch->agentnum);

    my $origin_name =  $conf->config('batchconfig-nacha-origin_name')
                    || $company;
    $origin_name = substr(uc($origin_name). (' 'x23), 0, 23);

    $company = substr(uc($company). (' 'x16), 0, 16);

    my $now = time;

    #haha don't want to break after a quarter million years of a batch a day
    #or 54 years for 5000 agent-virtualized hosted companies batching daily
    my $refcode = substr( (' 'x8). $pay_batch->batchnum, -8);

    #or only 25,000 years or 5.4 for 5000 companies :)
    #though they would probably want them numbered per company
    my $batchnum = substr( ('0'x7). $pay_batch->batchnum, -7);

    $entry_hash = 0;

    warn "building File & Batch Header Records\n" if $DEBUG;

    ##
    # File Header Record
    ##

    '1'.                      #Record Type Code
    '01'.                     #Priority Code
    ' '. $dest.               #Immediate Destination / 9-digit transit routing #
    $origin.                  #Immediate Origin / 10 digit company number
    time2str('%y%m%d', $now). #File Creation Date
    time2str('%H%M',   $now). #File Creation Time
    'A'.                 #XXX file ID modifier, mult. files in transit? [A-Z0-9]
    '094'.                    #94 character records
    '10'.                     #Blocking Factor
    '1'.                      #Format code
    $dest_name.               #Immediate Destination Name / 23 char bank name
    $origin_name.             #Immediate Origin Name / 23 char company name
    $refcode. "\n".           #Reference Code (internal/optional)

    ###
    # Batch Header Record
    ###

    '5'.                     #Record Type Code
    '225'.                   #Service Class Code (220 credits only,
                             #                    200 mixed debits & credits)
    $company.                #on cust. statements
    (' 'x20 ).               #20 char "company internal use if desired"
    $origin.                 #Company Identification (Immediate Origin)
    'PPD'. #others?
           #PPD "Prearranged Payments and Deposit entries" for consumer items
           #CCD (Cash Concentration and Disbursement)
           #CTX (Corporate Trade Exchange)
           #TEL (Telephone initiated entires)
           #WEB (Authorization received via the Internet)
    'InterntSvc'. #XXX from conf 10 char txn desc, printed on cust. statements

    #6 char "Descriptive date" printed on customer statements
    #XXX now? or use a separate post date?
    time2str('%y%m%d', $now).

    #6 char date transactions are to be posted
    #XXX now? or do we need a future banking day date like eft_canada trainwreck
    time2str('%y%m%d', $now).

    (' 'x3).                 #Settlement Date / Reserved
    '1'.                     #Originator Status Code
    substr($dest, 0, 8).     #Originating Financial Institution
    $batchnum. "\n"          #Batch Number ("number batches sequentially")

  },

  'row' => sub {
    my( $cust_pay_batch, $pay_batch, $batchcount, $batchtotal ) = @_;

    my ($account, $aba) = split('@', $cust_pay_batch->payinfo);

    $conf->config('batchconfig-nacha-destination') =~ /^\s*(\d{9})\s*$/
      or die 'illegal NACHA Destination';
    my $dest = $1;

    # "Total of all positions 4-11 on each 6 record"
    $entry_hash += substr($aba,0,8); 

    my $cust_main = $cust_pay_batch->cust_main;
    my $cust_identifier = substr($cust_main->display_custnum. (' 'x15), 0, 15);

    #XXX paytype should actually be in the batch, but this will do for now
    #27 checking debit, 37 savings debit
    my $transaction_code = ( $cust_pay_batch->paytype =~ /savings/i ? '37' : '27' );

    # not $self->payname?
    my $cust_name = substr($cust_main->name. (' 'x22), 0, 22);
    $i++;
    my $tracenum = $dest. substr(('0'x7). $i, -6);
    #non-PPD transactions?  future

    warn "building PPD Record\n" if $DEBUG;

    ###
    # PPD Entry Detail Record
    ###

    '6'.                              #Record Type Code
    $transaction_code.                #Transaction Code
    $aba.                             #Receiving DFI Identification, check digit
    substr($account.(' 'x17), 0, 17). #DFI Account number (Left justify)
    sprintf('%010.0f', $cust_pay_batch->amount * 100). #Amount
    $cust_identifier.                 #Individual Identification Number, 15 char
    $cust_name.                       #Individual name (22-char)
    '  '.                             #2 char "company internal use if desired"
    '0'.                              #Addenda Record Indicator
#    (' 'x15).                         #15 digit "bank will assign trace number"
     $tracenum.
    "\n"                              # (00000?)
  },

  'footer' => sub {
    my( $pay_batch, $batchcount, $batchtotal ) = @_;

    #Only use the final 10 positions in the entry
    $entry_hash = substr( '00'.$entry_hash, -10); 

    $conf->config('batchconfig-nacha-destination') =~ /^\s*(\d{9})\s*$/
      or die 'illegal NACHA Destination';
    my $dest = $1;

    $conf->config('batchconfig-nacha-origin') =~ /^\s*(\d{10})\s*$/
      or die 'illegal NACHA Origin';
    my $origin = $1;

    my $batchnum = substr( ('0'x7). $pay_batch->batchnum, -7);

    my $lines = $batchcount + 4;
    my $blocks = int($lines/10);
    my $fill = '';

    if ( my $remainder = $lines % 10 ) {
      $blocks++;
      $fill = ("\n".('9'x94))x( 10 - $remainder );
    }

    warn "building Batch & File Control Records\n" if $DEBUG;

    ###
    # Batch Control Record
    ###

    '8'.                          #Record Type Code
    '225'.                        #Service Class Code (220 credits only,
                                  #                    200 mixed debits&credits)
    sprintf('%06d', $batchcount). #Entry / Addenda Count
    $entry_hash.
    sprintf('%012.0f', $batchtotal * 100). #Debit total
    '000000000000'.               #Credit total
    $origin.                      #Company Identification (Immediate Origin)
    (' 'x19).                     #Message Authentication Code (19 char blank)
    (' 'x6).                      #Federal Reserve Use (6 char blank)
    substr($dest, 0, 8).          #Originating Financial Institution
    $batchnum. "\n".              #Batch Number ("number batches sequentially")

    ###
    # File Control Record
    ###

    '9'.                                 #Record Type Code
    '000001'.                            #Batch Counter (# of batch header recs)
    sprintf('%06d', $blocks).            #num of physical blocks on the file
    sprintf('%08d', $batchcount).        #total # of entry detail and addenda
    $entry_hash.
    sprintf('%012.0f', $batchtotal * 100). #Debit total
    '000000000000'.                      #Credit total
    ( ' 'x39 ).                          #Reserved / blank

    ###
    # Pad with 9999 records to blocks of 10
    ###

    $fill

  },

);

1;