X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=Paymentech.pm;h=0743bbad81c7e483d0a393a5ef2db335199f6e21;hb=HEAD;hp=fd3f4ae860e200725d94d7a69157713512567ba4;hpb=8c67441ae837ff8d99d47239d5b5562ff310bd3f;p=Business-BatchPayment-Paymentech.git diff --git a/Paymentech.pm b/Paymentech.pm index fd3f4ae..0743bba 100644 --- a/Paymentech.pm +++ b/Paymentech.pm @@ -3,7 +3,9 @@ package Business::BatchPayment::Paymentech; use 5.006; use strict; use warnings; -our $VERSION = '0.03'; +our $VERSION = '0.08'; + +use Unicode::Truncate 'truncate_egc'; =head1 NAME @@ -26,6 +28,7 @@ my $processor = Business::BatchPayment->processor('Paymentech', industryType => 'EC' login => 'TESTUSER', password => 'MYPASS', + with_recurringInd => 1, ); my $result = $processor->submit(@items); @@ -51,6 +54,8 @@ unzip programs. Unlikely to work on non-Unix systems. =item industryType - your 2-letter industry type code +=item with_recurringInd - enable the recurring charge indicator field + =back =cut @@ -65,6 +70,8 @@ use Moose; 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', @@ -72,6 +79,12 @@ has [ qw(merchantID terminalID bin industryType login password) ] => ( required => 1, ); +has 'with_recurringInd' => ( + is => 'ro', + isa => 'Bool', + default => 0, +); + has 'fileDateTime' => ( is => 'ro', isa => 'Str', @@ -94,7 +107,6 @@ sub default_transport { Business::BatchPayment::Paymentech::Transport->new( login => $self->login, password => $self->password, - put_path => $self->fileDateTime, debug => $self->debug, test_mode => $self->test_mode, ); @@ -109,6 +121,7 @@ sub format_request { OUTPUT => \$output, DATA_MODE => 1, DATA_INDENT => 2, + ENCODING => 'utf-8', ); $self->format_header($batch, $xml); my $count = 1; @@ -128,11 +141,13 @@ sub format_header { 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'); } @@ -148,12 +163,13 @@ sub format_item { terminalID => $self->terminalID, ); if ($item->payment_type eq 'CC') { + my $expiration = $item->expiration; + $expiration =~ s/\D//g; push @order, ( ccAccountNum => $item->card_number, - ccExp => $item->expiration, + ccExp => $expiration, ); - } - elsif ( $item->payment_type eq 'ECHECK' ) { + } elsif ( $item->payment_type eq 'ECHECK' ) { push @order, ( cardBrand => 'EC', ecpCheckRT => $item->routing_code, @@ -161,21 +177,30 @@ sub format_item { ecpBankAcctType => $BankAcctType{ $item->account_type }, ecpDelvMethod => 'A', ); - } - else { + } 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 => substr($item->state, 0, 2), - avsName => substr($item->first_name. ' '. $item->last_name, 0, 30), - avsCountryCode => ( $paymentech_countries{ $item->country } - ? $_->country - : '' - ), + if ( $self->with_recurringInd ) { + if ( $item->recurring_billing eq 'F' ) { + push @order, ( recurringInd => 'RF' ); + } elsif ( $item->recurring_billing eq 'S' ) { + push @order, ( recurringInd => 'RS' ); + } + } # else don't send recurringInd at all + + push @order, ( # truncate_egc will die() on empty string + avsZip => $item->zip, + avsAddress1 => $item->address ? truncate_egc($item->address, 30, '') : undef, + avsAddress2 => $item->address2 ? truncate_egc($item->address2, 30, '') : undef, + avsCity => $item->city ? truncate_egc($item->city, 20, '') : undef, + avsState => $item->state ? truncate_egc($item->state, 2, '') : undef, + avsName => ($item->first_name || $item->last_name) + ? truncate_egc($item->first_name.' '.$item->last_name, 30, '') + : undef, + ( $paymentech_countries{ $item->country } + ? ( avsCountryCode => $item->country ) + : () + ), orderID => $item->tid, amount => int( $item->amount * 100 ), ); @@ -240,17 +265,65 @@ sub parse_item { 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; } +# DEPRECATED + +# 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 ); @@ -286,7 +359,8 @@ sub upload { 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>/; + my $filename = $1; my $archive_dir = $self->archive_to; warn "Writing temp file to $tmpdir/$filename.xml.\n" if $self->debug;