changes for cardfortress
[Business-BatchPayment.git] / BatchPayment / Processor.pm
1 =head1 NAME
2
3 Business::BatchPayment::Processor - Common interface for batch payment gateways
4
5 =head1 DESCRIPTION
6
7 Business::BatchPayment::Processor is a Moose role.  Modules implementing  
8 the protocol to talk to specific payment processing services should compose 
9 it.
10
11 There are two general schemes for interacting with a batch payment gateway:
12
13 =over 4
14
15 =item Request/Reply
16
17 In this mode, you (the merchant) assemble a batch of payment requests, 
18 including account numbers, and send it to the gateway.  At some point in
19 the future, the gateway sends back one or more reply batches indicating the
20 results of processing the payments.
21
22 When submitting a request batch, the merchant software marks each payment
23 request with a unique transaction ID (the "tid" field).  This should be 
24 stored somewhere.  When the reply batch is processed, each item will have 
25 a tid matching its request.
26
27 Note that some gateways will provide results only for approved payments,
28 or even only for declined payments.  It is then up to the merchant software
29 to follow a sensible policy for approving or declining payments whose 
30 ultimate status is unconfirmed.
31
32 =item One-Way
33
34 In this mode, the gateway transmits a batch file containing approved 
35 payments, without those payments being requested.  For example, most
36 commercial banks provide check lockbox services and periodically 
37 send the merchant a statement of payments received to the lockbox.  
38 This statement would be processed as a one-way batch.
39
40 =back
41
42 =head1 ATTRIBUTES
43
44 Most attributes for Processor objects are defined by the module.
45
46 =over 4
47
48 =item transport
49
50 See L<Business::BatchPayment::Transport>.  This must be set before calling
51 submit() or receive().  Some modules will set it themselves; others require a
52 transport to be supplied.  Check for the existence of a 'default_transport'
53 method.
54
55 =item debug
56
57 Debug level.  This may be interpreted in various ways by the module.
58
59 =item test_mode
60
61 Communicate with a test server instead of the production gateway.  Not all
62 processors support this.  Test for the L<Business::BatchPayment::TestMode>
63 role to determine if it's supported.
64
65 =item on_format_error
66
67 Callback to handle errors when formatting items.  Arguments are the Processor
68 object, the Item object, and the error thrown by C<format_item>.  The callback
69 can die to stop submitting the batch.
70
71 =item on_parse_error
72
73 Callback to handle errors when parsing items.  Arguments are the Processor
74 object, the Item object, and the error thrown by C<parse_item>.  The callback
75 can die to stop receiving the batch.
76
77 =back
78
79 =head1 OTHER PARAMETERS
80
81 =over 4
82
83 =item input FILE
84
85 =item output FILE
86
87 If either of these is passed when constructing a Processor object, the
88 transport will be replaced with a File transport with those parameters.
89 Specifying only 'input' will direct 'output' to /dev/null, and vice versa.
90
91 =back
92
93 =head1 METHODS
94
95 =over 4
96
97 =item submit BATCH
98
99 Send a batch of requests to the gateway.  BATCH must be a 
100 L<Business::BatchPayment::Batch>.
101
102 =item receive
103
104 Download/otherwise acquire the available confirmed transactions from the 
105 gateway, parse them, and return a list of L<Business::BatchPayment::Batch>
106 objects.  The items in these batches will have, at minimum, the 'approved' 
107 field and either the 'tid' or 'amount' field set.
108
109 =item format_request BATCH
110
111 Default method to serialize BATCH for submission.  Returns the formatted
112 text as a string.  By default, this calls C<format_header>, then 
113 C<format_item> on each Item in the batch, then C<format_trailer>, 
114 joining the output with no delimiters.  Override this if your processor
115 needs something different.
116
117 =item format_header BATCH
118
119 =item format_trailer BATCH
120
121 Optional methods to produce the header and trailer sections of the 
122 formatted batch.  By default these are empty strings.
123
124 =item format_item ITEM, BATCH
125
126 Required method (if using the default C<format_request>) to produce
127 the per-item part of the formatted batch.  By default this throws 
128 a fatal error.
129
130 =item parse_response DATA
131
132 Default method to deserialize a received batch.  Takes the string received
133 from the gateway, returns a L<Business::BatchPayment::Batch>.  By default,
134 calls C<parse_batch_id> on the entire batch, then splits DATA into lines 
135 and calls C<parse_item> on each line.
136
137 =item parse_batch_id DATA
138
139 Optional method to obtain the batch identifier from the received file.
140 By default this returns nothing.
141
142 =item parse_item LINE
143
144 Required method to parse a line from the received file.  Should 
145 return zero or more L<Business::BatchPayment::Item>s.
146
147 =cut
148
149 =back
150
151 =cut
152
153 package Business::BatchPayment::Processor;
154
155 use strict;
156 use Try::Tiny;
157 use Moose::Role;
158 with 'Business::BatchPayment::Debug';
159
160 has 'transport' => (
161   is      => 'rw',
162   does    => 'Business::BatchPayment::Transport',
163   # possibly this part should be a separate role
164   lazy    => 1,
165   builder => 'default_transport',
166 );
167
168 sub default_transport {
169   my $self = shift;
170   die blessed($self). " requires a transport or input/output files\n";
171 }
172
173 has 'on_format_error' => (
174   traits  => ['Code'],
175   is      => 'rw',
176   handles => { format_error => 'execute_method' },
177   default => sub { \&default_on_error },
178 );
179
180 has 'on_parse_error' => (
181   traits  => ['Code'],
182   is      => 'rw',
183   handles => { parse_error => 'execute_method' },
184   default => sub { \&default_on_error },
185 );
186
187 sub default_on_error { #re-throw it
188   my ($self, $item, $error) = @_;
189   $DB::single = 1 if defined($DB::single);
190   die $error;
191 };
192
193 # No error callbacks for other parts of this.  The per-item case 
194 # is special in that it might make sense to continue with the 
195 # other items.
196
197 around BUILDARGS => sub {
198   my ($orig, $class, %args) = @_;
199   %args = %{ $class->$orig(%args) }; #process as usual
200   # then:
201   if ( $args{input} or $args{output} ) {
202     $args{transport} = Business::BatchPayment->create( 'Transport::File',
203       input => $args{input},
204       output => $args{output},
205     );
206   }
207   \%args;
208 };
209
210 # override this if your processor produces one-way batches
211 sub incoming { 0 };
212
213 #top-level interface
214
215 sub submit {
216   my $self = shift;
217   my $batch = shift;
218   my $request = $self->format_request($batch);
219   warn $request if $self->debug >= 2;
220   $self->transport->upload($request);
221 }
222
223 sub receive {
224   my $self = shift;
225   my @responses = $self->transport->download;
226   warn join("\n\n", @responses) if $self->debug >= 2 and scalar(@responses);
227   my @batches;
228   foreach my $response (@responses) {
229     push @batches, $self->parse_response($response);
230   }
231   @batches;
232 }
233
234 # next level down
235
236 sub format_request {
237   my $self = shift;
238   my $batch = shift;
239   my $output = $self->format_header($batch);
240   $batch->num(0);
241   foreach my $item ($batch->elements) {
242     try {
243       $output .= $self->format_item($item, $batch);
244       $batch->num( $batch->num + 1 );
245     } catch {
246       $self->format_error($item, $_);
247     };
248   }
249   $output .= $self->format_trailer($batch);
250   return $output;
251 }
252
253 sub parse_response {
254   my $self = shift;
255   my $input = shift;
256   my $batch = Business::BatchPayment->create(Batch =>
257     incoming => $self->incoming,
258     batch_id => $self->parse_batch_id($input),
259     num      => 0,
260   );
261   while ( $input =~ s/(.*)\n//m ) {
262     my $row = $1;
263     try { 
264       $batch->push( $self->parse_item($row) );
265       $batch->num( $batch->num + 1 );
266     } catch {
267       $self->parse_error($row, $_);
268     };
269   }
270   $batch;
271 }
272
273 # nuts and bolts
274
275 sub format_header  { '' };
276 sub format_trailer { '' };
277 sub format_item { die "format_item unimplemented\n" }
278
279 sub parse_batch_id { '' };
280 sub parse_item { die "parse_item unimplemented\n" }
281
282 1;