X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=Paymentech.pm;h=0743bbad81c7e483d0a393a5ef2db335199f6e21;hb=HEAD;hp=7fa2e442745e65706341c05abd13d592b2a49f18;hpb=eabacc85da1879f947797698461efa97a27c7b50;p=Business-BatchPayment-Paymentech.git diff --git a/Paymentech.pm b/Paymentech.pm index 7fa2e44..0743bba 100644 --- a/Paymentech.pm +++ b/Paymentech.pm @@ -3,16 +3,14 @@ package Business::BatchPayment::Paymentech; use 5.006; use strict; use warnings; -our $VERSION = '0.01'; +our $VERSION = '0.08'; + +use Unicode::Truncate 'truncate_egc'; =head1 NAME Business::BatchPayment::Paymentech - Chase Paymentech XML batch format. -=head1 VERSION - -Version 0.01 - =head1 USAGE See L for general usage notes. @@ -30,6 +28,7 @@ my $processor = Business::BatchPayment->processor('Paymentech', industryType => 'EC' login => 'TESTUSER', password => 'MYPASS', + with_recurringInd => 1, ); my $result = $processor->submit(@items); @@ -55,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 @@ -69,16 +70,24 @@ 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', - isa => 'Str', + is => 'ro', + isa => 'Str', required => 1, ); +has 'with_recurringInd' => ( + is => 'ro', + isa => 'Bool', + default => 0, +); + has 'fileDateTime' => ( - is => 'ro', - isa => 'Str', + is => 'ro', + isa => 'Str', default => sub { DateTime->now->strftime('%Y%m%d%H%M%S') }, @@ -91,12 +100,13 @@ my %BankAcctType = ( '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, ); @@ -108,9 +118,10 @@ sub format_request { 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; @@ -130,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'); } @@ -143,40 +156,53 @@ sub format_item { 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') { + my $expiration = $item->expiration; + $expiration =~ s/\D//g; push @order, ( - ccAccountNum => $item->card_number, - ccExp => $item->expiration, + ccAccountNum => $item->card_number, + ccExp => $expiration, ); - } - elsif ( $item->payment_type eq 'ECHECK' ) { + } 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 { + } else { die "payment type ".$item->type." not supported"; } - push @order, ( + 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 => 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 ), + 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 ), ); while (@order) { my $key = shift @order; @@ -239,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 ); @@ -285,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;