use 5.006;
use strict;
use warnings;
-our $VERSION = '0.01';
+our $VERSION = '0.03';
=head1 NAME
Business::BatchPayment::Paymentech - Chase Paymentech XML batch format.
-=head1 VERSION
-
-Version 0.01
-
=head1 USAGE
See L<Business::BatchPayment> for general usage notes.
with 'Business::BatchPayment::Processor';
with 'Business::BatchPayment::TestMode';
+use Encode;
+
# could have some validation on all of these
has [ qw(merchantID terminalID bin industryType login password) ] => (
- is => 'ro',
- isa => 'Str',
+ is => 'ro',
+ isa => 'Str',
required => 1,
);
has 'fileDateTime' => (
- is => 'ro',
- isa => 'Str',
+ is => 'ro',
+ isa => 'Str',
default => sub {
DateTime->now->strftime('%Y%m%d%H%M%S')
},
'business savings' => 'X',
);
+my %paymentech_countries = map { $_ => 1 } qw( US CA GB UK );
+
sub default_transport {
my $self = shift;
Business::BatchPayment::Paymentech::Transport->new(
login => $self->login,
password => $self->password,
- put_path => $self->fileDateTime,
debug => $self->debug,
test_mode => $self->test_mode,
);
my $output;
my $xml = XML::Writer->new(
- OUTPUT => \$output,
- DATA_MODE => 1,
+ OUTPUT => \$output,
+ DATA_MODE => 1,
DATA_INDENT => 2,
+ ENCODING => 'utf-8',
);
$self->format_header($batch, $xml);
my $count = 1;
my ($self, $batch, $xml) = @_;
my $num_items = $batch->count;
+ $xml->xmlDecl();
$xml->startTag('transRequest', RequestCount => $num_items + 1);
$xml->startTag('batchFileID');
$xml->dataElement(userID => $self->login);
$xml->dataElement(fileDateTime => $self->fileDateTime);
- $xml->dataElement(fileID => $self->fileDateTime);
+ $xml->dataElement(fileID => sprintf('%06d-', $batch->batch_id) .
+ $self->fileDateTime);
$xml->endTag('batchFileID');
}
if ( $item->action eq 'payment' ) {
$xml->startTag('newOrder', BatchRequestNo => $count);
my @order = (
- industryType => $self->industryType,
- transType => 'AC',
- bin => $self->bin,
- merchantID => $self->merchantID,
- terminalID => $self->terminalID,
+ industryType => $self->industryType,
+ transType => 'AC',
+ bin => $self->bin,
+ merchantID => $self->merchantID,
+ terminalID => $self->terminalID,
);
if ($item->payment_type eq 'CC') {
push @order, (
- ccAccountNum => $item->card_number,
- ccExp => $item->expiration,
+ ccAccountNum => $item->card_number,
+ ccExp => $item->expiration,
);
}
elsif ( $item->payment_type eq 'ECHECK' ) {
push @order, (
- cardBrand => 'EC',
- ecpCheckRT => $item->routing_code,
- ecpCheckDDA => $item->account_number,
+ cardBrand => 'EC',
+ ecpCheckRT => $item->routing_code,
+ ecpCheckDDA => $item->account_number,
ecpBankAcctType => $BankAcctType{ $item->account_type },
- ecpDelvMethod => 'A',
+ ecpDelvMethod => 'A',
);
}
else {
die "payment type ".$item->type." not supported";
}
push @order, (
- avsZip => $item->zip,
- avsAddress1 => substr($item->address, 0, 30),
- avsAddress2 => substr($item->address2, 0, 30),
- avsCity => substr($item->city, 0, 20),
- avsState => $item->state,
- avsName => substr($item->first_name .' '. $item->last_name, 0, 30),
- avsCountryCode => $item->country,
- orderID => $item->tid,
- amount => int( $item->amount * 100 ),
+ avsZip => $item->zip,
+ avsAddress1 => bytes_substr($item->address, 0, 30),
+ avsAddress2 => bytes_substr($item->address2, 0, 30),
+ avsCity => bytes_substr($item->city, 0, 20),
+ avsState => bytes_substr($item->state, 0, 2),
+ avsName => bytes_substr($item->first_name. ' '. $item->last_name, 0, 30),
+ ( $paymentech_countries{ $item->country }
+ ? ( avsCountryCode => $item->country )
+ : ()
+ ),
+ orderID => $item->tid,
+ amount => int( $item->amount * 100 ),
);
while (@order) {
my $key = shift @order;
second => $sec,
);
+ my %failure_status = (
+ # API version 2.6, April 2013
+ '00' => undef, # Approved
+ '04' => 'pickup',
+ '33' => 'expired',
+ '41' => 'stolen',
+ '42' => 'inactive',
+ '43' => 'stolen',
+ '44' => 'inactive',
+ 'B7' => 'blacklisted', # Fraud
+ 'B9' => 'blacklisted', # On Negative File
+ 'BB' => 'stolen', # Possible Compromise
+ 'BG' => 'blacklisted', # Blocked Account
+ 'BQ' => 'blacklisted', # Issuer has Flagged Account as Suspected Fraud
+ 'C4' => 'nsf', # Over Credit Limit
+ 'D5' => 'blacklisted', # On Negative File
+ 'D7' => 'nsf', # Insufficient Funds
+ 'F3' => 'inactive', # Account Closed
+ 'K6' => 'nsf', # NSF
+ ); # all others are "decline"
+
+ my $failure_status = undef;
+ my $error_message;
+
+ if ( $resp->{procStatus} ) {
+ $error_message = $resp->{procStatusMessage};
+ } elsif ( $resp->{respCode} ) {
+ $error_message = $resp->{respCodeMessage};
+ $failure_status = $failure_status{ $resp->{respCode} } || 'decline';
+ } else {
+ $error_message = '';
+ }
+
my $item = Business::BatchPayment->create(Item =>
tid => $resp->{orderID},
process_date => $dt,
authorization => $resp->{authorizationCode},
order_number => $resp->{txRefNum},
approved => ($resp->{approvalStatus} == 1),
- error_message => $resp->{procStatusMessage},
+ error_message => $error_message,
+ failure_status => $failure_status,
);
$item;
}
+# internal use
+
+sub bytes_substr {
+ my ($string, $offset, $length, $repl) = @_;
+ my $bytes = substr(
+ Encode::encode('utf8', $string),
+ $offset,
+ $length,
+ Encode::encode('utf8', $repl)
+ );
+ return Encode::decode('utf8', $bytes, Encode::FB_QUIET);
+}
+
+
package Business::BatchPayment::Paymentech::Transport;
use File::Temp qw( tempdir );
my $self = shift;
my $content = shift;
my $tmpdir = tempdir( CLEANUP => 1 );
- my $filename = $self->put_path; # also the value of the fileId tag
+ $content =~ /<fileID>(.*)<\/fileID>/;
+ my $filename = $1;
my $archive_dir = $self->archive_to;
warn "Writing temp file to $tmpdir/$filename.xml.\n" if $self->debug;