1 package Business::BatchPayment::Paymentech;
10 Business::BatchPayment::Paymentech - Chase Paymentech XML batch format.
18 See L<Business::BatchPayment> for general usage notes.
22 use Business::BatchPayment;
24 my @items = Business::BatchPayment::Item->new( ... );
26 my $processor = Business::BatchPayment->processor('Paymentech',
27 merchantID => '123456',
35 my $result = $processor->submit(@items);
39 Requires L<Net::SFTP::Foreign> and ssh (for file transfer) and the zip and
40 unzip programs. Unlikely to work on non-Unix systems.
42 =head2 PROCESSOR ATTRIBUTES
46 =item login - the username to use for SFTP, and in the "userID" tag
48 =item password - the password for SFTP, and for creating zip files
50 =item merchantID - your 6- or 12-digit Paymentech merchant ID
52 =item bin - your BIN: 000001 or 000002
54 =item terminalID - your 3-digit terminal ID
56 =item industryType - your 2-letter industry type code
62 use File::Temp qw(tempdir);
69 with 'Business::BatchPayment::Processor';
70 with 'Business::BatchPayment::TestMode';
72 # could have some validation on all of these
73 has [ qw(merchantID terminalID bin industryType login password) ] => (
79 has 'fileDateTime' => (
83 DateTime->now->strftime('%Y%m%d%H%M%S')
88 'personal checking' => 'C',
89 'personal savings' => 'S',
90 'business checking' => 'X',
91 'business savings' => 'X',
94 my %paymentech_countries = map { $_ => 1 } qw( US CA GB UK );
96 sub default_transport {
98 Business::BatchPayment::Paymentech::Transport->new(
99 login => $self->login,
100 password => $self->password,
101 put_path => $self->fileDateTime,
102 debug => $self->debug,
103 test_mode => $self->test_mode,
112 my $xml = XML::Writer->new(
117 $self->format_header($batch, $xml);
119 foreach my $item ( @{ $batch->items } ) {
121 $self->format_item($item, $batch, $xml, $count);
124 $self->format_error($item, $_);
127 $self->format_trailer($batch, $xml, $count);
132 my ($self, $batch, $xml) = @_;
133 my $num_items = $batch->count;
135 $xml->startTag('transRequest', RequestCount => $num_items + 1);
136 $xml->startTag('batchFileID');
137 $xml->dataElement(userID => $self->login);
138 $xml->dataElement(fileDateTime => $self->fileDateTime);
139 $xml->dataElement(fileID => $self->fileDateTime);
140 $xml->endTag('batchFileID');
144 my ($self, $item, $batch, $xml, $count) = @_;
145 if ( $item->action eq 'payment' ) {
146 $xml->startTag('newOrder', BatchRequestNo => $count);
148 industryType => $self->industryType,
151 merchantID => $self->merchantID,
152 terminalID => $self->terminalID,
154 if ($item->payment_type eq 'CC') {
156 ccAccountNum => $item->card_number,
157 ccExp => $item->expiration,
160 elsif ( $item->payment_type eq 'ECHECK' ) {
163 ecpCheckRT => $item->routing_code,
164 ecpCheckDDA => $item->account_number,
165 ecpBankAcctType => $BankAcctType{ $item->account_type },
166 ecpDelvMethod => 'A',
170 die "payment type ".$item->type." not supported";
173 avsZip => $item->zip,
174 avsAddress1 => substr($item->address, 0, 30),
175 avsAddress2 => substr($item->address2, 0, 30),
176 avsCity => substr($item->city, 0, 20),
177 avsState => $item->state,
178 avsName => substr($item->first_name .' '. $item->last_name, 0, 30),
179 avsCountryCode => ( $paymentech_countries{ $item->country }
183 orderID => $item->tid,
184 amount => int( $item->amount * 100 ),
187 my $key = shift @order;
188 my $value = shift @order;
189 $xml->dataElement($key, $value);
191 $xml->endTag('newOrder');
192 } # if action eq 'payment'
194 die "action ".$item->action." not supported";
200 my ($self, $batch, $xml, $count) = @_;
201 $xml->startTag('endOfDay', 'BatchRequestNo', $count);
202 $xml->dataElement('bin' => $self->bin);
203 $xml->dataElement('merchantID' => $self->merchantID);
204 $xml->dataElement('terminalID' => $self->terminalID);
205 $xml->endTag('endOfDay');
206 $xml->endTag('transRequest');
212 my $batch = Business::BatchPayment->create('Batch');
214 my $tree = XML::Simple::XMLin($input, KeepRoot => 1);
215 my $newOrderResp = $tree->{transResponse}->{newOrderResp};
216 die "can't find <transResponse><newOrderResp> in input"
217 unless defined $newOrderResp;
219 $newOrderResp = [ $newOrderResp ] if ref($newOrderResp) ne 'ARRAY';
220 foreach my $resp (@$newOrderResp) {
222 $batch->push( $self->parse_item($resp) );
224 # parse_error needs a string representation of the
225 # input data...and if it 's failing because it wasn't valid
226 # XML, we wouldn't get this far.
227 $self->parse_error(XML::Simple::XMLout($resp), $_);
234 my ($self, $resp) = @_;
236 my ($mon, $day, $year, $hour, $min, $sec) =
237 $resp->{respDateTime} =~ /^(..)(..)(....)(..)(..)(..)$/;
238 my $dt = DateTime->new(
247 my $item = Business::BatchPayment->create(Item =>
248 tid => $resp->{orderID},
250 authorization => $resp->{authorizationCode},
251 order_number => $resp->{txRefNum},
252 approved => ($resp->{approvalStatus} == 1),
253 error_message => $resp->{procStatusMessage},
258 package Business::BatchPayment::Paymentech::Transport;
260 use File::Temp qw( tempdir );
261 use File::Slurp qw( read_file write_file );
263 use Moose::Util::TypeConstraints;
264 extends 'Business::BatchPayment::Transport::SFTP';
265 with 'Business::BatchPayment::TestMode';
270 $self->test_mode ? 'orbitalbatchvar.paymentech.net'
271 : 'orbitalbatch.paymentech.net'
278 where { !defined($_) or ( -d $_ and -w $_ ) },
279 message { "can't write to '$_'" };
281 has 'archive_to' => (
286 # batch content passed as an argument
292 my $tmpdir = tempdir( CLEANUP => 1 );
293 my $filename = $self->put_path; # also the value of the fileId tag
294 my $archive_dir = $self->archive_to;
296 warn "Writing temp file to $tmpdir/$filename.xml.\n" if $self->debug;
297 write_file("$tmpdir/$filename.xml", $content);
299 warn "Creating zip file.\n" if $self->debug;
304 "$tmpdir/$filename.zip",
305 "$tmpdir/$filename.xml",
307 unshift @args, '-q' unless $self->debug;
308 system('zip', @args);
309 die "failed to create zip file" if (! -f "$tmpdir/$filename.zip");
311 warn "Uploading.\n" if $self->debug;
312 $self->put("$tmpdir/$filename.zip", "$filename.zip");
319 my $tmpdir = tempdir( CLEANUP => 1 );
320 my $ls_info = $self->ls('.', wanted => qr/_resp\.zip$/);
321 my $archive_dir = $self->archive_to;
323 foreach (@$ls_info) {
324 my $filename = $_->{filename}; # still ends in _resp
325 $filename =~ s/\.zip$//;
326 warn "Retrieving $filename.zip\n" if $self->debug;
327 $self->get("$filename.zip", "$tmpdir/$filename.zip");
332 "$tmpdir/$filename.zip",
336 unshift @args, '-q' unless $self->debug;
337 system('unzip', @args);
338 if (! -f "$tmpdir/$filename.xml") {
339 warn "failed to extract $filename.xml from $filename.zip\n";
342 my $content = read_file("$tmpdir/$filename.xml");
343 if ( $archive_dir ) {
344 warn "Copying $tmpdir/$filename.xml to archive dir $archive_dir\n";
345 write_file("$archive_dir/$filename.xml", $content);
347 push @batches, $content;
354 'info_compat' => '0.01',
355 'gateway_name' => 'Paymentech',
356 'gateway_url' => 'http://www.chasepaymentech.com/',
357 'module_version' => $VERSION,
358 'supported_types' => [ qw( CC ECHECK ) ],
359 'token_support' => 0,
360 'test_transaction' => 1,
361 'supported_actions' => [ 'Payment' ],
367 Mark Wells, C<< <mark at freeside.biz> >>
371 Relying on external zip/unzip is awkward.
375 You can find documentation for this module with the perldoc command.
377 perldoc Business::BatchPayment::Paymentech
379 Commercial support is available from Freeside Internet Services, Inc.
381 L<http://www.freeside.biz>
383 =head1 ACKNOWLEDGEMENTS
385 =head1 LICENSE AND COPYRIGHT
387 Copyright 2012 Mark Wells.
389 This program is free software; you can redistribute it and/or modify it
390 under the terms of either: the GNU General Public License as published
391 by the Free Software Foundation; or the Artistic License.
393 See http://dev.perl.org/licenses/ for more information.
398 1; # End of Business::BatchPayment::Paymentech