Added processor_id functionality
[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>.  No defined return value,
101 but processors may optionally set the 'processor_id' field
102 on the input batch, which should then be stored and passed
103 to receive.
104
105 =item receive
106
107 Download/otherwise acquire the available confirmed transactions from the 
108 gateway, parse them, and return a list of L<Business::BatchPayment::Batch>
109 objects.  The items in these batches will have, at minimum, the 'approved' 
110 field and either the 'tid' or 'amount' field set.  Accepts an optional
111 list of processor_id strings, if required by your processor.
112
113 =item format_request BATCH
114
115 Default method to serialize BATCH for submission.  Returns the formatted
116 text as a string.  By default, this calls C<format_header>, then 
117 C<format_item> on each Item in the batch, then C<format_trailer>, 
118 joining the output with no delimiters.  Override this if your processor
119 needs something different.
120
121 =item format_header BATCH
122
123 =item format_trailer BATCH
124
125 Optional methods to produce the header and trailer sections of the 
126 formatted batch.  By default these are empty strings.
127
128 =item format_item ITEM, BATCH
129
130 Required method (if using the default C<format_request>) to produce
131 the per-item part of the formatted batch.  By default this throws 
132 a fatal error.
133
134 =item parse_response DATA
135
136 Default method to deserialize a received batch.  Takes the string received
137 from the gateway, returns a L<Business::BatchPayment::Batch>.  By default,
138 calls C<parse_batch_id> on the entire batch, then splits DATA into lines 
139 and calls C<parse_item> on each line.
140
141 =item parse_batch_id DATA
142
143 Optional method to obtain the batch identifier from the received file.
144 By default this returns nothing.
145
146 =item parse_item LINE
147
148 Required method to parse a line from the received file.  Should 
149 return zero or more L<Business::BatchPayment::Item>s.
150
151 =cut
152
153 =back
154
155 =cut
156
157 package Business::BatchPayment::Processor;
158
159 use strict;
160 use Try::Tiny;
161 use Moose::Role;
162 with 'Business::BatchPayment::Debug';
163
164 has 'transport' => (
165   is      => 'rw',
166   does    => 'Business::BatchPayment::Transport',
167   # possibly this part should be a separate role
168   lazy    => 1,
169   builder => 'default_transport',
170 );
171
172 sub default_transport {
173   my $self = shift;
174   die blessed($self). " requires a transport or input/output files\n";
175 }
176
177 has 'on_format_error' => (
178   traits  => ['Code'],
179   is      => 'rw',
180   handles => { format_error => 'execute_method' },
181   default => sub { \&default_on_error },
182 );
183
184 has 'on_parse_error' => (
185   traits  => ['Code'],
186   is      => 'rw',
187   handles => { parse_error => 'execute_method' },
188   default => sub { \&default_on_error },
189 );
190
191 sub default_on_error { #re-throw it
192   my ($self, $item, $error) = @_;
193   $DB::single = 1 if defined($DB::single);
194   die $error;
195 };
196
197 # No error callbacks for other parts of this.  The per-item case 
198 # is special in that it might make sense to continue with the 
199 # other items.
200
201 around BUILDARGS => sub {
202   my ($orig, $class, %args) = @_;
203   %args = %{ $class->$orig(%args) }; #process as usual
204   # then:
205   if ( $args{input} or $args{output} ) {
206     $args{transport} = Business::BatchPayment->create( 'Transport::File',
207       input => $args{input},
208       output => $args{output},
209     );
210   }
211   \%args;
212 };
213
214 # override this if your processor produces one-way batches
215 sub incoming { 0 };
216
217 #top-level interface
218
219 sub submit {
220   my $self = shift;
221   my $batch = shift;
222   my $request = $self->format_request($batch);
223   warn $request if $self->debug >= 2;
224   $self->transport->upload($request);
225 }
226
227 sub receive {
228   my $self = shift;
229   my @responses = $self->transport->download;
230   warn join("\n\n", @responses) if $self->debug >= 2 and scalar(@responses);
231   my @batches;
232   foreach my $response (@responses) {
233     push @batches, $self->parse_response($response);
234   }
235   @batches;
236 }
237
238 # next level down
239
240 sub format_request {
241   my $self = shift;
242   my $batch = shift;
243   my $output = $self->format_header($batch);
244   $batch->num(0);
245   foreach my $item ($batch->elements) {
246     try {
247       $output .= $self->format_item($item, $batch);
248       $batch->num( $batch->num + 1 );
249     } catch {
250       $self->format_error($item, $_);
251     };
252   }
253   $output .= $self->format_trailer($batch);
254   return $output;
255 }
256
257 sub parse_response {
258   my $self = shift;
259   my $input = shift;
260   my $batch = Business::BatchPayment->create(Batch =>
261     incoming => $self->incoming,
262     batch_id => $self->parse_batch_id($input),
263     num      => 0,
264   );
265   while ( $input =~ s/(.*)\n//m ) {
266     my $row = $1;
267     try { 
268       $batch->push( $self->parse_item($row) );
269       $batch->num( $batch->num + 1 );
270     } catch {
271       $self->parse_error($row, $_);
272     };
273   }
274   $batch;
275 }
276
277 # nuts and bolts
278
279 sub format_header  { '' };
280 sub format_trailer { '' };
281 sub format_item { die "format_item unimplemented\n" }
282
283 sub parse_batch_id { '' };
284 sub parse_item { die "parse_item unimplemented\n" }
285
286 1;