package Business::OnlinePayment::PayflowPro;
use strict;
-use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
+use vars qw($VERSION);
use Carp qw(croak);
-use AutoLoader;
-use Business::OnlinePayment;
+use base qw(Business::OnlinePayment);
-use PFProAPI; #Payflow PRO SDK from Verisign
+# PayflowPRO SDK from PayPal/Verisign
+use PFProAPI qw( pfpro );
-require Exporter;
-
-@ISA = qw(Exporter AutoLoader Business::OnlinePayment);
-@EXPORT = qw();
-@EXPORT_OK = qw();
-$VERSION = '0.02';
+$VERSION = '0.05';
+$VERSION = eval $VERSION;
sub set_defaults {
my $self = shift;
- #$self->server('staging.linkpt.net');
- $self->server('secure.linkpt.net');
- $self->port('1139');
+ $self->server('payflow.verisign.com');
+ $self->port('443');
+ $self->build_subs(qw(
+ vendor partner order_number cert_path avs_code cvv2_code
+ ));
}
sub map_fields {
my %content = $self->content();
#ACTION MAP
- my %actions = ('normal authorization' => 'ApproveSale',
- 'authorization only' => 'CapturePayment',
- 'credit' => 'ReturnOrder',
- 'post authorization' => 'BillOrders',
+ my %actions = ('normal authorization' => 'S', #Sale
+ 'authorization only' => 'A', #Authorization
+ 'credit' => 'C', #Credit (refund)
+ 'post authorization' => 'D', #Delayed Capture
+ 'void' => 'V',
);
$content{'action'} = $actions{lc($content{'action'})} || $content{'action'};
+ # TYPE MAP
+ my %types = ('visa' => 'C',
+ 'mastercard' => 'C',
+ 'american express' => 'C',
+ 'discover' => 'C',
+ 'cc' => 'C'
+ #'check' => 'ECHECK',
+ );
+ $content{'type'} = $types{lc($content{'type'})} || $content{'type'};
+ $self->transaction_type($content{'type'});
+
# stuff it back into %content
$self->content(%content);
}
-sub build_subs {
- my $self = shift;
- foreach(@_) {
- #no warnings; #not 5.005
- local($^W)=0;
- eval "sub $_ { my \$self = shift; if(\@_) { \$self->{$_} = shift; } return \$self->{$_}; }";
- }
-}
-
sub remap_fields {
my($self,%map) = @_;
$self->content(%content);
}
-sub get_fields {
- my($self,@fields) = @_;
-
- my %content = $self->content();
- my %new = ();
- foreach( grep defined $content{$_}, @fields) { $new{$_} = $content{$_}; }
- return %new;
-}
-
sub submit {
my($self) = @_;
-
$self->map_fields();
my %content = $self->content;
- my($month, $year);
- unless ( $content{action} eq 'BillOrders' ) {
+ my($month, $year, $zip);
- if ( $self->transaction_type() =~
- /^(cc|visa|mastercard|american express|discover)$/i
- ) {
- } else {
- Carp::croak("PayflowPro can't handle transaction type: ".
- $self->transaction_type());
- }
+ if ( $self->transaction_type() ne 'C' ) {
+ croak("PayflowPro can't (yet?) handle transaction type: " .
+ $self->transaction_type());
+ }
- $content{'expiration'} =~ /^(\d+)\D+\d*(\d{2})$/
- or croak "unparsable expiration $content{expiration}";
+ if ( defined($content{'expiration'}) && length($content{'expiration'}) ) {
+ $content{'expiration'} =~ /^(\d+)\D+\d*(\d{2})$/
+ or croak "unparsable expiration $content{expiration}";
- ( $month, $year ) = ( $1, $2 );
- $month = '0'. $month if $month =~ /^\d$/;
- $year += 2000 if $year < 2000; #not y4k safe, oh shit
+ ( $month, $year ) = ( $1, $2 );
+ $month = '0'. $month if $month =~ /^\d$/;
}
- $content{'address'} =~ /^(\S+)\s/;
- my $addrnum = $1;
+ ( $zip = $content{'zip'} ) =~ s/\D//g;
- $self->server('staging.linkpt.net') if $self->test_transaction;
+ $self->server('test-payflow.verisign.com') if $self->test_transaction;
$self->revmap_fields(
- hostname => \( $self->server ),
- port => \( $self->port ),
- storename => \( $self->storename ),
- keyfile => \( $self->keyfile ),
- addrnum => \$addrnum,
-
- cardNumber => 'card_number',
- cardExpMonth => \$month,
- cardExpYear => \$year,
+ ACCT => 'card_number',
+ EXPDATE => \( $month.$year ),
+ AMT => 'amount',
+ USER => 'login',
+ # (BUG?) VENDOR B::OP:PayflowPro < 0.05 backward compatibility. If
+ # vendor not set use login (although test indicate undef vendor is ok)
+ VENDOR => $self->vendor ? \( $self->vendor ) : 'login',
+ PARTNER => \( $self->partner ),
+ PWD => 'password',
+ TRXTYPE => 'action',
+ TENDER => 'type',
+
+ STREET => 'address',
+ ZIP => \$zip,
+
+ CITY => 'city',
+ COMMENT1 => 'description',
+ COMMENT2 => 'invoice_number',
+ COMPANYNAME => 'company',
+ COUNTRY => 'country',
+ FIRSTNAME => 'first_name',
+ LASTNAME => 'last_name',
+ NAME => 'name',
+ EMAIL => 'email',
+ STATE => 'state',
+
+ CVV2 => 'cvv2',
+ ORIGID => 'order_number'
+
);
- my $lperl = new LPERL
- $self->lbin,
- 'FILE',
- $self->can('tmp')
- ? $self->tmp
- : '/tmp';
- my $action = $content{action};
-
- $self->required_fields(qw/
- hostname port storename keyfile amount cardNumber cardExpMonth cardExpYear
- /);
-
- my %post_data = $self->get_fields(qw/
- hostname port storename keyfile
- result
- amount cardNumber cardExpMonth cardExpYear
- name email phone address city state zip country
- /);
-
- #print "$_ => $post_data{$_}\n" foreach keys %post_data;
-
- my %response;
- {
- local($^W)=0;
- %response = $lperl->$action(\%post_data);
+ my @required = qw( TRXTYPE TENDER PARTNER VENDOR USER PWD );
+ if ( $self->transaction_type() eq 'C' ) { #credit card
+ if ( $content{'action'} =~ /^[CDV]$/
+ && defined($content{'ORIGID'})
+ && length($content{'ORIGID'})
+ )
+ {
+ push @required, qw(ORIGID);
+ } else {
+ push @required, qw(AMT ACCT EXPDATE);
+ }
}
-
- if ( $response{'statusCode'} == 0 ) {
- $self->is_success(0);
- $self->result_code('');
- $self->error_message($response{'statusMessage'});
- } else {
+ $self->required_fields(@required);
+
+ my %params = $self->get_fields(qw(
+ ACCT EXPDATE AMT USER VENDOR PARTNER PWD TRXTYPE TENDER
+ STREET ZIP CITY COMMENT1 COMMENT2 COMPANYNAME COUNTRY
+ FIRSTNAME LASTNAME NAME EMAIL STATE
+ CVV2 ORIGID
+ ));
+
+ $ENV{'PFPRO_CERT_PATH'} = $self->cert_path;
+ my( $response, $resultstr ) = pfpro( \%params, $self->server, $self->port );
+ # PNREF (aka transaction id) is set on success and failure
+ $self->order_number( $response->{'PNREF'} );
+
+ if ( $response->{'RESULT'} eq '0' ) { #want an explicit zero, not just
+ #numerically equal
$self->is_success(1);
- $self->result_code($response{'AVCCode'});
- $self->authorization($response{'trackingID'});
-# $self->order_number($response{'neworderID'});
+ $self->result_code( $response->{'RESULT'} );
+ $self->error_message( $response->{'RESPMSG'} );
+ $self->authorization( $response->{'AUTHCODE'} );
+ my $avs_code = '';
+ if ( exists $response->{AVSADDR} || exists $response->{AVSZIP} ) {
+ if ( $response->{AVSADDR} eq 'Y' && $response->{AVSZIP} eq 'Y' ) {
+ $avs_code = 'Y';
+ } elsif ( $response->{AVSADDR} eq 'Y' ) {
+ $avs_code = 'A';
+ } elsif ( $response->{AVSZIP} eq 'Y' ) {
+ $avs_code = 'Z';
+ } elsif ( $response->{AVSADDR} eq 'N' || $response->{AVSZIP} eq 'N' ) {
+ $avs_code = 'N';
+ }
+ }
+ $self->avs_code( $avs_code );
+ $self->cvv2_code( $response->{'CVV2MATCH'});
+ } else {
+ $self->is_success(0);
+ $self->result_code( $response->{'RESULT'} );
+ $self->error_message( $response->{'RESPMSG'} );
}
}
1;
+
__END__
=head1 NAME
use Business::OnlinePayment;
my $tx = new Business::OnlinePayment( 'PayflowPro',
- 'storename' => 'your_store_number',
- 'keyfile' => '/path/to/keyfile.pem',
- 'lbin' => '/path/to/binary/lbin',
- 'tmp' => '/secure/tmp', # a secure tmp directory
+ 'vendor' => 'your_vendor',
+ 'partner' => 'your_partner',
+ 'cert_path' => '/path/to/your/certificate/file/', #just the dir
);
$tx->content(
zip => '84058',
email => 'ivan-payflowpro@420.am',
card_number => '4007000000027',
- expiration => '09/99',
+ expiration => '09/04',
+
+ #advanced params
+ cvv2 => '420',
+ order_number => 'string', # returned by $tx->order_number() from an
+ # "authorization only" or
+ # "normal authorization" action, used by a
+ # "credit", "void", or "post authorization"
);
$tx->submit();
if($tx->is_success()) {
- print "Card processed successfully: ".$tx->authorization."\n";
+ print "Card processed successfully: ", $tx->authorization, "\n";
+ print "order number: ", $tx->order_number, "\n";
+ print "AVS code: ", $tx->avs_code, "\n"; # Y - Address and ZIP match
+ # A - Address matches but not ZIP
+ # Z - ZIP matches bu tnot address
+ # N - no match
+ # E - AVS error or unsupported
+ # (null) - AVS error
+ print "CVV2 code: ", $tx->cvv2_code, "\n";
+
} else {
- print "Card was rejected: ".$tx->error_message."\n";
+ print "Card was rejected: ", $tx->error_message;
+ print " (CVV2 mismatch)" if $tx->result_code == 114;
+ print "\n";
}
=head1 SUPPORTED TRANSACTION TYPES
-=head2 Visa, MasterCard, American Express, JCB, Discover/Novus, Carte blanche/Diners Club
+=head2 Visa, MasterCard, American Express, JCB, Discover/Novus, Carte blanche/Diners Club, CC
+
+=head1 SUPPORTED ACTIONS
+
+=head2 Normal Authorization, Authorization Only, Post Authorization, Credit, Void
=head1 DESCRIPTION
=head1 COMPATIBILITY
-This module implements an interface to the PayflowPro Perl API, which can
-be downloaded at https://manager.verisign.com/ with a valid login.
+This module implements an interface to the PayflowPro Perl API, which
+can be downloaded at https://manager.verisign.com/ with a valid login.
=head1 BUGS
perl(1), L<Business::OnlinePayment>.
=cut
-