1 package Business::OnlinePayment::PayflowPro;
4 use vars qw($VERSION $DEBUG);
5 use Carp qw(carp croak);
9 use base qw(Business::OnlinePayment::HTTPS);
12 $VERSION = eval $VERSION;
17 my $md5 = Digest::MD5->new();
18 $md5->add( $$, time(), rand(time) );
19 return $md5->hexdigest();
26 $self->{__PARAM} ||= {};
27 my $param = $self->{__PARAM};
30 if ( @args % 2 == 0 ) {
31 %$param = ( %$param, @args );
33 elsif ( @args == 1 ) {
35 if ( ref($arg) eq "HASH" ) {
36 %$param = ( %$param, %$arg );
40 return $param->{$arg};
44 croak("param: invalid arguments: @_\n");
48 return ( keys %$param );
56 my $level = shift || 0;
58 $self->{"__DEBUG"} = $level;
63 $Business::OnlinePayment::HTTPS::DEBUG = $level;
65 return ref($self) ? ( $self->{"__DEBUG"} || $DEBUG ) : $DEBUG;
69 carp( __PACKAGE__ . " cert_path method is deprecated" );
73 # maybe get rid of build_subs() someday and use param()?
74 sub vendor { my $self = shift; return $self->param( "vendor", @_ ); }
75 sub partner { my $self = shift; return $self->param( "partner", @_ ); }
78 return $self->param( "order_number", @_ );
80 sub avs_code { my $self = shift; return $self->param( "avs_code", @_ ); }
81 sub cvv2_code { my $self = shift; return $self->param( "cvv2_code", @_ ); }
87 $self->server("payflow.verisign.com");
89 $self->path(""); # PayflowPro uses /transaction and /commit
91 $self->debug( $opts{debug} );
95 "path_transaction" => "/transaction",
96 "path_commit" => "/commit",
104 my %content = $self->content();
108 'normal authorization' => 'S', # Sale transaction
109 'credit' => 'C', # Credit (refund)
110 'authorization only' => 'A', # Authorization
111 'post authorization' => 'D', # Delayed Capture
112 'void' => 'V', # Void
115 $content{'action'} = $actions{ lc( $content{'action'} ) }
116 || $content{'action'};
122 'american express' => 'C',
126 #'check' => 'ECHECK',
129 $content{'type'} = $types{ lc( $content{'type'} ) } || $content{'type'};
131 $self->transaction_type( $content{'type'} );
133 # stuff it back into %content
134 $self->content(%content);
138 my ( $self, %map ) = @_;
139 my %content = $self->content();
140 foreach ( keys %map ) {
144 : $content{ $map{$_} };
146 $self->content(%content);
151 my $expiration = shift;
153 if ( defined($expiration) and $expiration =~ /^(\d+)\D+\d*(\d{2})$/ ) {
154 my ( $month, $year ) = ( $1, $2 );
155 $expdate_mmyy = sprintf( "%02d", $month ) . $year;
157 return defined($expdate_mmyy) ? $expdate_mmyy : $expiration;
163 $self->_map_fields();
165 my %content = $self->content;
167 if ( $self->transaction_type() ne 'C' ) {
168 croak( "PayflowPro can't (yet?) handle transaction type: "
169 . $self->transaction_type() );
172 my $expdate_mmyy = $self->expdate_mmyy( $content{"expiration"} );
173 my $zip = $content{'zip'};
174 $zip =~ s/[^[:alnum:]]//g;
176 $self->server('pilot-payflowpro.verisign.com') if $self->test_transaction;
178 my $vendor = $self->vendor;
179 my $partner = $self->partner;
180 $self->_revmap_fields(
182 # BUG?: VENDOR B::OP:PayflowPro < 0.05 backward compatibility. If
183 # vendor not set use login although test indicate undef vendor is ok
184 VENDOR => $vendor ? \$vendor : 'login',
185 PARTNER => \$partner,
190 ORIGID => 'order_number',
191 COMMENT1 => 'description',
192 COMMENT2 => 'invoice_number',
194 ACCT => 'card_number',
196 EXPDATE => \$expdate_mmyy, # MM/YY from 'expiration'
199 FIRSTNAME => 'first_name',
200 LASTNAME => 'last_name',
203 COMPANYNAME => 'company',
207 ZIP => \$zip, # 'zip' with non-alnums removed
208 COUNTRY => 'country',
211 my @required = qw( TRXTYPE TENDER PARTNER VENDOR USER PWD );
212 if ( $self->transaction_type() eq 'C' ) { # credit card
213 if ( $content{'action'} =~ /^[CDV]$/
214 && defined( $content{'ORIGID'} )
215 && length( $content{'ORIGID'} ) )
217 push @required, qw(ORIGID);
221 # never get here, we croak above if transaction_type ne 'C'
222 push @required, qw(AMT ACCT EXPDATE);
225 $self->required_fields(@required);
227 my %params = $self->get_fields(
229 VENDOR PARTNER USER PWD TRXTYPE TENDER ORIGID COMMENT1 COMMENT2
230 ACCT CVV2 EXPDATE AMT
231 FIRSTNAME LASTNAME NAME EMAIL COMPANYNAME
232 STREET CITY STATE ZIP COUNTRY
236 # get header data, get request_id from %content if defined for ease of use
237 my %req_headers = %{ $self->param("headers") || {} };
238 if ( defined $content{"request_id"} ) {
239 $req_headers{"X-VPS-REQUEST-ID"} = $content{"request_id"};
241 unless ( defined( $req_headers{"X-VPS-REQUEST-ID"} ) ) {
242 $req_headers{"X-VPS-REQUEST-ID"} = $self->request_id();
246 "Content-Type" => "text/namevalue",
247 "headers" => \%req_headers,
250 $self->path( $self->param("path_transaction") );
251 my ( $tpage, $tresp, %tresp_headers ) =
252 $self->https_post( \%options, \%params );
255 "transaction_response" => {
258 headers => \%tresp_headers,
262 # $tpage should contain name=value[[&name=value]...] pairs
263 my $cgi = CGI->new("$tpage");
265 if ( $cgi->param("RESULT") eq "0" ) {
266 my $response_id = $tresp_headers{"X-VPS-RESPONSE-ID"};
267 $options{headers}->{"X-VPS-RESPONSE-ID"} = $response_id;
268 $self->path( $self->param("path_commit") );
269 my ( $cpage, $cresp, %cresp_headers ) =
270 $self->https_post( \%options, \%params );
272 "commit_response" => {
275 headers => \%cresp_headers,
278 my $comcgi = CGI->new("$cpage");
280 # merge commit results with transaction
281 foreach my $p ( $comcgi->param() ) {
282 $cgi->param( $p => $comcgi->param($p) );
286 # AVS and CVS values may be set on success or failure
288 if ( defined $cgi->param("AVSADDR") or defined $cgi->param("AVSZIP") ) {
289 if ( $cgi->param("AVSADDR") eq "Y" && $cgi->param("AVSZIP") eq "Y" ) {
292 elsif ( $cgi->param("AVSADDR") eq "Y" ) {
295 elsif ( $cgi->param("AVSZIP") eq "Y" ) {
298 elsif ( $cgi->param("AVSADDR") eq "N" or $cgi->param("AVSZIP") eq "N" )
307 $self->avs_code($avs_code);
308 $self->cvv2_code( $cgi->param("CVV2MATCH") );
309 $self->result_code( $cgi->param("RESULT") );
310 $self->order_number( $cgi->param("PNREF") );
311 $self->error_message( $cgi->param("RESPMSG") );
312 $self->authorization( $cgi->param("AUTHCODE") );
314 # RESULT must be an explicit zero, not just numerically equal
315 if ( $cgi->param("RESULT") eq "0" ) {
316 $self->is_success(1);
319 $self->is_success(0);
329 Business::OnlinePayment::PayflowPro - Payflow Pro backend for Business::OnlinePayment
333 use Business::OnlinePayment;
335 my $tx = new Business::OnlinePayment(
337 'vendor' => 'your_vendor',
338 'partner' => 'your_partner',
341 # See the module documentation for details of content()
344 action => 'Normal Authorization',
345 description => 'Business::OnlinePayment::PayflowPro test',
347 invoice_number => '100100',
348 customer_id => 'jsk',
349 name => 'Jason Kohles',
350 address => '123 Anystreet',
354 email => 'ivan-payflowpro@420.am',
355 card_number => '4111111111111111',
356 expiration => '12/09',
358 order_number => 'string',
363 if ( $tx->is_success() ) {
365 "Card processed successfully: ", $tx->authorization, "\n",
366 "order number: ", $tx->order_number, "\n",
367 "CVV2 code: ", $tx->cvv2_code, "\n",
368 "AVS code: ", $tx->avs_code, "\n",
373 $info = " (CVV2 mismatch)" if ( $tx->result_code == 114 );
376 "Card was rejected: ", $tx->error_message, $info, "\n",
377 "order number: ", $tx->order_number, "\n",
383 This module is a back end driver that implements the interface
384 specified by L<Business::OnlinePayment> to support payment handling
385 via the PayPal's Payflow Pro Internet payment solution.
387 See L<Business::OnlinePayment> for details on the interface this
390 =head1 Standard methods
396 This method sets the 'server' attribute to 'payflow.verisign.com' and
397 the port attribute to '443'. This method also sets up the
398 L</Module specific methods> described below.
404 =head1 Module specific methods
406 This module provides the following methods which are not currently
407 part of the standard Business::OnlinePayment interface:
415 =item L<order_number()|/order_number()>
417 =item L<avs_code()|/avs_code()>
419 =item L<cvv2_code()|/cvv2_code()>
421 =item L<expdate_mmyy()|/expdate_mmyy()>
423 =item L<requeset_id()/request_id()>
425 =item L<param()|/param()>
427 =item L<debug()|/debug()>
431 This method is deprecated and will be removed in the next release.
432 This method was used to support passing a path to PFProAPI.pm (a Perl
433 module/SDK from Verisign/Paypal) which is no longer used.
439 The following default settings exist:
445 payflow.verisign.com or test-payflow.verisign.com if
446 test_transaction() is TRUE
454 =head1 Handling of content(%content)
456 The following rules apply to content(%content) data:
460 If 'action' matches one of the following keys it is replaced by the
461 right hand side value:
463 'normal authorization' => 'S', # Sale transaction
464 'credit' => 'C', # Credit (refund)
465 'authorization only' => 'A', # Authorization
466 'post authorization' => 'D', # Delayed Capture
469 If 'action' is 'C', 'D' or 'V' and 'order_number' is not set then
470 'amount', 'card_number' and 'expiration' must be set.
474 If 'type' matches one of the following keys it is replaced by the
475 right hand side value:
479 'american express' => 'C',
483 The value of 'type' is used to set transaction_type(). Currently this
484 module only supports a transaction_type() of 'C' any other values will
485 cause Carp::croak() to be called in submit().
487 Note: Payflow Pro supports multiple credit card types, including:
488 American Express/Optima, Diners Club, Discover/Novus, Enroute, JCB,
491 =head1 Setting Payflow Pro parameters from content(%content)
493 The following rules are applied to map data to Payflow Pro parameters
494 from content(%content):
496 # PFP param => $content{<key>}
497 VENDOR => $self->vendor ? \( $self->vendor ) : 'login',
498 PARTNER => \( $self->partner ),
503 ORIGID => 'order_number',
504 COMMENT1 => 'description',
505 COMMENT2 => 'invoice_number',
507 ACCT => 'card_number',
509 EXPDATE => \( $month.$year ), # MM/YY from 'expiration'
512 FIRSTNAME => 'first_name',
513 LASTNAME => 'last_name',
516 COMPANYNAME => 'company',
520 ZIP => \$zip, # 'zip' with non-alphanumerics removed
521 COUNTRY => 'country',
523 The required Payflow Pro parameters for credit card transactions are:
525 TRXTYPE TENDER PARTNER VENDOR USER PWD ORIGID
527 =head1 Mapping Payflow Pro transaction responses to object methods
529 The following methods provides access to the transaction response data
530 resulting from a Payflow Pro request (after submit()) is called:
532 =head2 order_number()
534 This order_number() method returns the PNREF field, also known as the
535 PayPal Reference ID, which is a unique number that identifies the
540 The result_code() method returns the RESULT field, which is the
541 numeric return code indicating the outcome of the attempted
544 A RESULT of 0 (zero) indicates the transaction was approved and
545 is_success() will return '1' (one/TRUE). Any other RESULT value
546 indicates a decline or error and is_success() will return '0'
549 =head2 error_message()
551 The error_message() method returns the RESPMSG field, which is a
552 response message returned with the transaction result.
554 =head2 authorization()
556 The authorization() method returns the AUTHCODE field, which is the
557 approval code obtained from the processing network.
561 The avs_code() method returns a combination of the AVSADDR and AVSZIP
562 fields from the transaction result. The value in avs_code is as
565 Y - Address and ZIP match
566 A - Address matches but not ZIP
567 Z - ZIP matches but not address
569 undef - AVS values not available
573 The cvv2_code() method returns the CVV2MATCH field, which is a
574 response message returned with the transaction result.
576 =head2 expdate_mmyy()
578 The expdate_mmyy() method takes a single scalar argument (typically
579 the value in $content{expiration}) and attempts to parse and format
580 and put the date in MMYY format as required by PayflowPro
581 specification. If unable to parse the expiration date simply leave it
582 as is and let the PayflowPro system attempt to handle it as-is.
586 The request_id() method uses Digest::MD5 to attempt to generate a
587 request_id for a transaction. It is recommended that you specify your
588 own unique request_id for each transaction in %content. A request_id
589 is REQUIRED by the PayflowPro processor.
593 The param() method is used to get/set object parameters. The param()
594 method may be called in several different ways:
596 Get the value of 'myparam':
598 my $value_or_reference = $self->param('myparam');
600 Get a list of all parameters that exist:
602 my @params = $self->param();
604 Set multiple parameters at the same time:
613 Enable or disble debugging. The value specified here will also set
614 $Business::OnlinePayment::HTTPS::DEBUG in submit() to aid in
615 troubleshooting problems.
619 This module implements an interface to the Payflow Pro Perl API, which
620 can be downloaded at https://manager.paypal.com/ with a valid login.
624 Ivan Kohler <ivan-payflowpro@420.am>
626 Phil Lobbes E<lt>phil at perkpartners.comE<gt>
628 Based on Business::OnlinePayment::AuthorizeNet written by Jason Kohles.
632 perl(1), L<Business::OnlinePayment>, L<Carp>, and the PayPal
633 Integration Center Payflow Pro resources at
634 L<https://www.paypal.com/IntegrationCenter/ic_payflowpro.html>