1 package Business::BatchPayment::Paymentech;
10 Business::BatchPayment::Paymentech - Chase Paymentech XML batch format.
14 See L<Business::BatchPayment> for general usage notes.
18 use Business::BatchPayment;
20 my @items = Business::BatchPayment::Item->new( ... );
22 my $processor = Business::BatchPayment->processor('Paymentech',
23 merchantID => '123456',
31 my $result = $processor->submit(@items);
35 Requires L<Net::SFTP::Foreign> and ssh (for file transfer) and the zip and
36 unzip programs. Unlikely to work on non-Unix systems.
38 =head2 PROCESSOR ATTRIBUTES
42 =item login - the username to use for SFTP, and in the "userID" tag
44 =item password - the password for SFTP, and for creating zip files
46 =item merchantID - your 6- or 12-digit Paymentech merchant ID
48 =item bin - your BIN: 000001 or 000002
50 =item terminalID - your 3-digit terminal ID
52 =item industryType - your 2-letter industry type code
58 use File::Temp qw(tempdir);
65 with 'Business::BatchPayment::Processor';
66 with 'Business::BatchPayment::TestMode';
68 # could have some validation on all of these
69 has [ qw(merchantID terminalID bin industryType login password) ] => (
75 has 'fileDateTime' => (
79 DateTime->now->strftime('%Y%m%d%H%M%S')
84 'personal checking' => 'C',
85 'personal savings' => 'S',
86 'business checking' => 'X',
87 'business savings' => 'X',
90 my %paymentech_countries = map { $_ => 1 } qw( US CA GB UK );
92 sub default_transport {
94 Business::BatchPayment::Paymentech::Transport->new(
95 login => $self->login,
96 password => $self->password,
97 debug => $self->debug,
98 test_mode => $self->test_mode,
107 my $xml = XML::Writer->new(
112 $self->format_header($batch, $xml);
114 foreach my $item ( @{ $batch->items } ) {
116 $self->format_item($item, $batch, $xml, $count);
119 $self->format_error($item, $_);
122 $self->format_trailer($batch, $xml, $count);
127 my ($self, $batch, $xml) = @_;
128 my $num_items = $batch->count;
130 $xml->startTag('transRequest', RequestCount => $num_items + 1);
131 $xml->startTag('batchFileID');
132 $xml->dataElement(userID => $self->login);
133 $xml->dataElement(fileDateTime => $self->fileDateTime);
134 $xml->dataElement(fileID => sprintf('%06d-', $batch->batch_id) .
135 $self->fileDateTime);
136 $xml->endTag('batchFileID');
140 my ($self, $item, $batch, $xml, $count) = @_;
141 if ( $item->action eq 'payment' ) {
142 $xml->startTag('newOrder', BatchRequestNo => $count);
144 industryType => $self->industryType,
147 merchantID => $self->merchantID,
148 terminalID => $self->terminalID,
150 if ($item->payment_type eq 'CC') {
152 ccAccountNum => $item->card_number,
153 ccExp => $item->expiration,
156 elsif ( $item->payment_type eq 'ECHECK' ) {
159 ecpCheckRT => $item->routing_code,
160 ecpCheckDDA => $item->account_number,
161 ecpBankAcctType => $BankAcctType{ $item->account_type },
162 ecpDelvMethod => 'A',
166 die "payment type ".$item->type." not supported";
169 avsZip => $item->zip,
170 avsAddress1 => substr($item->address, 0, 30),
171 avsAddress2 => substr($item->address2, 0, 30),
172 avsCity => substr($item->city, 0, 20),
173 avsState => substr($item->state, 0, 2),
174 avsName => substr($item->first_name. ' '. $item->last_name, 0, 30),
175 avsCountryCode => ( $paymentech_countries{ $item->country }
179 orderID => $item->tid,
180 amount => int( $item->amount * 100 ),
183 my $key = shift @order;
184 my $value = shift @order;
185 $xml->dataElement($key, $value);
187 $xml->endTag('newOrder');
188 } # if action eq 'payment'
190 die "action ".$item->action." not supported";
196 my ($self, $batch, $xml, $count) = @_;
197 $xml->startTag('endOfDay', 'BatchRequestNo', $count);
198 $xml->dataElement('bin' => $self->bin);
199 $xml->dataElement('merchantID' => $self->merchantID);
200 $xml->dataElement('terminalID' => $self->terminalID);
201 $xml->endTag('endOfDay');
202 $xml->endTag('transRequest');
208 my $batch = Business::BatchPayment->create('Batch');
210 my $tree = XML::Simple::XMLin($input, KeepRoot => 1);
211 my $newOrderResp = $tree->{transResponse}->{newOrderResp};
212 die "can't find <transResponse><newOrderResp> in input"
213 unless defined $newOrderResp;
215 $newOrderResp = [ $newOrderResp ] if ref($newOrderResp) ne 'ARRAY';
216 foreach my $resp (@$newOrderResp) {
218 $batch->push( $self->parse_item($resp) );
220 # parse_error needs a string representation of the
221 # input data...and if it 's failing because it wasn't valid
222 # XML, we wouldn't get this far.
223 $self->parse_error(XML::Simple::XMLout($resp), $_);
230 my ($self, $resp) = @_;
232 my ($mon, $day, $year, $hour, $min, $sec) =
233 $resp->{respDateTime} =~ /^(..)(..)(....)(..)(..)(..)$/;
234 my $dt = DateTime->new(
243 my $item = Business::BatchPayment->create(Item =>
244 tid => $resp->{orderID},
246 authorization => $resp->{authorizationCode},
247 order_number => $resp->{txRefNum},
248 approved => ($resp->{approvalStatus} == 1),
249 error_message => $resp->{procStatusMessage},
254 package Business::BatchPayment::Paymentech::Transport;
256 use File::Temp qw( tempdir );
257 use File::Slurp qw( read_file write_file );
259 use Moose::Util::TypeConstraints;
260 extends 'Business::BatchPayment::Transport::SFTP';
261 with 'Business::BatchPayment::TestMode';
266 $self->test_mode ? 'orbitalbatchvar.paymentech.net'
267 : 'orbitalbatch.paymentech.net'
274 where { !defined($_) or ( -d $_ and -w $_ ) },
275 message { "can't write to '$_'" };
277 has 'archive_to' => (
282 # batch content passed as an argument
288 my $tmpdir = tempdir( CLEANUP => 1 );
289 $content =~ /<fileID>(.*)<\/fileID>/;
291 my $archive_dir = $self->archive_to;
293 warn "Writing temp file to $tmpdir/$filename.xml.\n" if $self->debug;
294 write_file("$tmpdir/$filename.xml", $content);
296 warn "Creating zip file.\n" if $self->debug;
301 "$tmpdir/$filename.zip",
302 "$tmpdir/$filename.xml",
304 unshift @args, '-q' unless $self->debug;
305 system('zip', @args);
306 die "failed to create zip file" if (! -f "$tmpdir/$filename.zip");
308 warn "Uploading.\n" if $self->debug;
309 $self->put("$tmpdir/$filename.zip", "$filename.zip");
316 my $tmpdir = tempdir( CLEANUP => 1 );
317 my $ls_info = $self->ls('.', wanted => qr/_resp\.zip$/);
318 my $archive_dir = $self->archive_to;
320 foreach (@$ls_info) {
321 my $filename = $_->{filename}; # still ends in _resp
322 $filename =~ s/\.zip$//;
323 warn "Retrieving $filename.zip\n" if $self->debug;
324 $self->get("$filename.zip", "$tmpdir/$filename.zip");
329 "$tmpdir/$filename.zip",
333 unshift @args, '-q' unless $self->debug;
334 system('unzip', @args);
335 if (! -f "$tmpdir/$filename.xml") {
336 warn "failed to extract $filename.xml from $filename.zip\n";
339 my $content = read_file("$tmpdir/$filename.xml");
340 if ( $archive_dir ) {
341 warn "Copying $tmpdir/$filename.xml to archive dir $archive_dir\n";
342 write_file("$archive_dir/$filename.xml", $content);
344 push @batches, $content;
351 'info_compat' => '0.01',
352 'gateway_name' => 'Paymentech',
353 'gateway_url' => 'http://www.chasepaymentech.com/',
354 'module_version' => $VERSION,
355 'supported_types' => [ qw( CC ECHECK ) ],
356 'token_support' => 0,
357 'test_transaction' => 1,
358 'supported_actions' => [ 'Payment' ],
364 Mark Wells, C<< <mark at freeside.biz> >>
368 Relying on external zip/unzip is awkward.
372 You can find documentation for this module with the perldoc command.
374 perldoc Business::BatchPayment::Paymentech
376 Commercial support is available from Freeside Internet Services, Inc.
378 L<http://www.freeside.biz>
380 =head1 ACKNOWLEDGEMENTS
382 =head1 LICENSE AND COPYRIGHT
384 Copyright 2012 Mark Wells.
386 This program is free software; you can redistribute it and/or modify it
387 under the terms of either: the GNU General Public License as published
388 by the Free Software Foundation; or the Artistic License.
390 See http://dev.perl.org/licenses/ for more information.
395 1; # End of Business::BatchPayment::Paymentech