1 package Business::OnlinePayment::PayflowPro;
4 use vars qw($VERSION $DEBUG);
5 use Carp qw(carp croak);
7 use Business::OnlinePayment::HTTPS 0.06;
9 use base qw(Business::OnlinePayment::HTTPS);
12 $VERSION = eval $VERSION;
15 # CGI::Util was included starting with Perl 5.6. For previous
16 # Perls, let them use the old simple CGI method of unescaping
19 eval { require CGI::Util; };
20 $no_cgi_util = 1 if $@;
23 # return current request_id or generate a new one if not yet set
27 $self->{"__request_id"} = shift if (@_); # allow value change/reset
28 $self->{"__request_id"} = $self->_new_request_id()
29 unless ( $self->{"__request_id"} );
30 return $self->{"__request_id"};
33 return $self->_new_request_id();
39 my $md5 = Digest::MD5->new();
40 $md5->add( $$, time(), rand(time) );
41 return $md5->hexdigest();
48 my $level = shift || 0;
50 $self->{"__DEBUG"} = $level;
55 $Business::OnlinePayment::HTTPS::DEBUG = $level;
57 return ref($self) ? ( $self->{"__DEBUG"} || $DEBUG ) : $DEBUG;
60 # cvv2_code: support legacy code and but deprecate method
61 sub cvv2_code { shift->cvv2_response(@_); }
67 # standard B::OP methods/data
68 $self->server("payflowpro.paypal.com");
70 $self->path("/transaction");
75 client_certification_id client_timeout
78 order_number avs_code cvv2_response
79 response_page response_code response_headers
83 # module specific data
85 $self->debug( $opts{debug} );
89 # HTTPS Interface Dev Guide: must be set but will be removed in future
90 $self->client_certification_id("ClientCertificationIdNotSet");
92 # required: 45 secs recommended by HTTPS Interface Dev Guide
93 $self->client_timeout(45);
95 $self->test_server("pilot-payflowpro.paypal.com");
101 my %content = $self->content();
105 'normal authorization' => 'S', # Sale transaction
106 'credit' => 'C', # Credit (refund)
107 'authorization only' => 'A', # Authorization
108 'post authorization' => 'D', # Delayed Capture
109 'void' => 'V', # Void
112 $content{'action'} = $actions{ lc( $content{'action'} ) }
113 || $content{'action'};
119 'american express' => 'C',
123 #'check' => 'ECHECK',
126 $content{'type'} = $types{ lc( $content{'type'} ) } || $content{'type'};
128 $self->transaction_type( $content{'type'} );
130 # stuff it back into %content
131 $self->content(%content);
135 my ( $self, %map ) = @_;
136 my %content = $self->content();
137 foreach ( keys %map ) {
141 : $content{ $map{$_} };
143 $self->content(%content);
148 my $expiration = shift;
150 if ( defined($expiration) and $expiration =~ /^(\d+)\D+\d*(\d{2})$/ ) {
151 my ( $month, $year ) = ( $1, $2 );
152 $expdate_mmyy = sprintf( "%02d", $month ) . $year;
154 return defined($expdate_mmyy) ? $expdate_mmyy : $expiration;
160 $self->_map_fields();
162 my %content = $self->content;
164 if ( $self->transaction_type() ne 'C' ) {
165 croak( "PayflowPro can't (yet?) handle transaction type: "
166 . $self->transaction_type() );
169 my $expdate_mmyy = $self->expdate_mmyy( $content{"expiration"} );
170 my $zip = $content{'zip'};
171 $zip =~ s/[^[:alnum:]]//g;
173 $self->server( $self->test_server ) if $self->test_transaction;
175 my $vendor = $self->vendor;
176 my $partner = $self->partner;
178 $self->_revmap_fields(
180 # BUG?: VENDOR B::OP:PayflowPro < 0.05 backward compatibility. If
181 # vendor not set use login although test indicate undef vendor is ok
182 VENDOR => $vendor ? \$vendor : 'login',
183 PARTNER => \$partner,
188 ORIGID => 'order_number',
189 COMMENT1 => 'description',
190 COMMENT2 => 'invoice_number',
192 ACCT => 'card_number',
194 EXPDATE => \$expdate_mmyy, # MM/YY from 'expiration'
197 FIRSTNAME => 'first_name',
198 LASTNAME => 'last_name',
201 COMPANYNAME => 'company',
205 ZIP => \$zip, # 'zip' with non-alnums removed
206 COUNTRY => 'country',
208 # As of 8/18/2009: CUSTCODE appears to be cut off at 18
209 # characters and isn't currently reportable. Consider storing
210 # local customer ids in the COMMENT1/2 fields as a workaround.
211 CUSTCODE => 'customer_id',
212 SHIPTOFIRSTNAME => 'ship_first_name',
213 SHIPTOLASTNAME => 'ship_last_name',
214 SHIPTOSTREET => 'ship_address',
215 SHIPTOCITY => 'ship_city',
216 SHIPTOSTATE => 'ship_state',
217 SHIPTOZIP => 'ship_zip',
218 SHIPTOCOUNTRY => 'ship_country',
221 # Reload %content as _revmap_fields makes our copy old/invalid!
222 %content = $self->content;
224 my @required = qw( TRXTYPE TENDER PARTNER VENDOR USER PWD );
225 if ( $self->transaction_type() eq 'C' ) { # credit card
226 if ( $content{'action'} =~ /^[CDV]$/
227 && defined( $content{'ORIGID'} )
228 && length( $content{'ORIGID'} ) )
230 push @required, qw(ORIGID);
234 # never get here, we croak above if transaction_type ne 'C'
235 push @required, qw(AMT ACCT EXPDATE);
238 $self->required_fields(@required);
240 my %params = $self->get_fields(
242 VENDOR PARTNER USER PWD TRXTYPE TENDER ORIGID COMMENT1 COMMENT2
243 ACCT CVV2 EXPDATE AMT
244 FIRSTNAME LASTNAME NAME EMAIL COMPANYNAME
245 STREET CITY STATE ZIP COUNTRY
246 SHIPTOFIRSTNAME SHIPTOLASTNAME
247 SHIPTOSTREET SHIPTOCITY SHIPTOSTATE SHIPTOZIP SHIPTOCOUNTRY
253 my %req_headers = %{ $self->headers || {} };
255 # get request_id from %content if defined for ease of use
256 if ( defined $content{"request_id"} ) {
257 $self->request_id( $content{"request_id"} );
260 unless ( defined( $req_headers{"X-VPS-Request-ID"} ) ) {
261 $req_headers{"X-VPS-Request-ID"} = $self->request_id();
264 unless ( defined( $req_headers{"X-VPS-VIT-Client-Certification-Id"} ) ) {
265 $req_headers{"X-VPS-VIT-Client-Certification-Id"} =
266 $self->client_certification_id;
269 unless ( defined( $req_headers{"X-VPS-Client-Timeout"} ) ) {
270 $req_headers{"X-VPS-Client-Timeout"} = $self->client_timeout();
274 "Content-Type" => "text/namevalue",
275 "headers" => \%req_headers,
278 # Payflow Pro does not use URL encoding for the request. The
279 # following implements their custom encoding scheme. Per the
280 # developer docs, the PARMLIST Syntax Guidelines are:
281 # - Spaces are allowed in values
282 # - Enclose the PARMLIST in quotation marks ("")
283 # - Do not place quotation marks ("") within the body of the PARMLIST
284 # - Separate all PARMLIST name-value pairs using an ampersand (&)
286 # Because '&' and '=' have special meanings/uses values containing
287 # these special characters must be encoded using a special "length
288 # tag". The "length tag" is simply the length of the "value"
289 # enclosed in square brackets ([]) and appended to the "name"
290 # portion of the name-value pair.
292 # For more details see the sections 'Using Special Characters in
293 # Values' and 'PARMLIST Syntax Guidelines' in the PayPal Payflow
294 # Pro Developer's Guide
296 # NOTE: we pass a string to https_post so it does not do encoding
297 my $params_string = join(
301 my $value = defined( $params{$key} ) ? $params{$key} : '';
302 if ( index( $value, '&' ) != -1 || index( $value, '=' ) != -1 ) {
303 $key = $key . "[" . length($value) . "]";
309 my ( $page, $resp, %resp_headers ) =
310 $self->https_post( \%options, $params_string );
312 $self->response_code($resp);
313 $self->response_page($page);
314 $self->response_headers( \%resp_headers );
316 # $page should contain name=value[[&name=value]...] pairs
317 my $response = $self->_get_response( \$page );
319 # AVS and CVS values may be set on success or failure
321 if ( defined $response->{"AVSADDR"} or defined $response->{"AVSZIP"} ) {
322 if ( $response->{"AVSADDR"} eq "Y" && $response->{"AVSZIP"} eq "Y" ) {
325 elsif ( $response->{"AVSADDR"} eq "Y" ) {
328 elsif ( $response->{"AVSZIP"} eq "Y" ) {
331 elsif ( $response->{"AVSADDR"} eq "N" or $response->{"AVSZIP"} eq "N" )
340 $self->avs_code($avs_code);
341 $self->cvv2_response( $response->{"CVV2MATCH"} );
342 $self->result_code( $response->{"RESULT"} );
343 $self->order_number( $response->{"PNREF"} );
344 $self->error_message( $response->{"RESPMSG"} );
345 $self->authorization( $response->{"AUTHCODE"} );
347 # RESULT must be an explicit zero, not just numerically equal
348 if ( defined( $response->{"RESULT"} ) && $response->{"RESULT"} eq "0" ) {
349 $self->is_success(1);
352 $self->is_success(0);
356 # Process the response page for params. Based on parse_params in CGI
357 # by Lincoln D. Stein.
359 my ( $self, $page ) = @_;
363 if ( !defined($page) || ( ref($page) && !defined($$page) ) ) {
367 my ( $param, $value );
368 foreach ( split( /[&;]/, ref($page) ? $$page : $page ) ) {
369 ( $param, $value ) = split( '=', $_, 2 );
370 next unless defined $param;
371 $value = '' unless defined $value;
373 if ($no_cgi_util) { # use old pre-CGI::Util method of unescaping
374 $param =~ tr/+/ /; # pluses become spaces
375 $param =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
376 $value =~ tr/+/ /; # pluses become spaces
377 $value =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
380 $param = CGI::Util::unescape($param);
381 $value = CGI::Util::unescape($value);
383 $response{$param} = $value;
394 Business::OnlinePayment::PayflowPro - Payflow Pro backend for Business::OnlinePayment
398 use Business::OnlinePayment;
400 my $tx = new Business::OnlinePayment(
402 'vendor' => 'your_vendor',
403 'partner' => 'your_partner',
404 'client_certification_id' => 'GuidUpTo32Chars',
407 # See the module documentation for details of content()
410 action => 'Normal Authorization',
411 description => 'Business::OnlinePayment::PayflowPro test',
413 invoice_number => '100100',
414 customer_id => 'jsk',
415 name => 'Jason Kohles',
416 address => '123 Anystreet',
420 email => 'ivan-payflowpro@420.am',
421 card_number => '4111111111111111',
422 expiration => '12/09',
424 order_number => 'string',
425 request_id => 'unique_identifier_for_transaction',
430 if ( $tx->is_success() ) {
432 "Card processed successfully: ", $tx->authorization, "\n",
433 "order number: ", $tx->order_number, "\n",
434 "CVV2 response: ", $tx->cvv2_response, "\n",
435 "AVS code: ", $tx->avs_code, "\n",
440 $info = " (CVV2 mismatch)" if ( $tx->result_code == 114 );
443 "Card was rejected: ", $tx->error_message, $info, "\n",
444 "order number: ", $tx->order_number, "\n",
450 This module is a back end driver that implements the interface
451 specified by L<Business::OnlinePayment> to support payment handling
452 via the PayPal's Payflow Pro Internet payment solution.
454 See L<Business::OnlinePayment> for details on the interface this
457 =head1 Standard methods
463 This method sets the 'server' attribute to 'payflowpro.paypal.com'
464 and the port attribute to '443'. This method also sets up the
465 L</Module specific methods> described below.
471 =head1 Unofficial methods
473 This module provides the following methods which are not officially
474 part of the standard Business::OnlinePayment interface (as of 3.00_06)
475 but are nevertheless supported by multiple gateways modules and
476 expected to be standardized soon:
480 =item L<order_number()|/order_number()>
482 =item L<avs_code()|/avs_code()>
484 =item L<cvv2_response()|/cvv2_response()>
488 =head1 Module specific methods
490 This module provides the following methods which are not currently
491 part of the standard Business::OnlinePayment interface:
493 =head2 client_certification_id()
495 This gets/sets the X-VPS-VITCLIENTCERTIFICATION-ID which is REQUIRED
496 and defaults to "ClientCertificationIdNotSet". This is described in
497 Website Payments Pro HTTPS Interface Developer's Guide as follows:
499 "A random globally unique identifier (GUID) that is currently
500 required. This requirement will be removed in the future. At this
501 time, you can send any alpha-numeric ID up to 32 characters in length.
503 NOTE: Once you have created this ID, do not change it. Use the same ID
504 for every transaction."
506 =head2 client_timeout()
508 Timeout value, in seconds, after which this transaction should be
509 aborted. Defaults to 45, the value recommended by the Website
510 Payments Pro HTTPS Interface Developer's Guide.
514 Enable or disble debugging. The value specified here will also set
515 $Business::OnlinePayment::HTTPS::DEBUG in submit() to aid in
516 troubleshooting problems.
518 =head2 expdate_mmyy()
520 The expdate_mmyy() method takes a single scalar argument (typically
521 the value in $content{expiration}) and attempts to parse and format
522 and put the date in MMYY format as required by PayflowPro
523 specification. If unable to parse the expiration date simply leave it
524 as is and let the PayflowPro system attempt to handle it as-is.
528 It is recommended that you specify your own unique request_id for each
529 transaction in %content. A request_id is REQUIRED by the PayflowPro
530 processor. If a request_id is not set, then Digest::MD5 is used to
531 attempt to generate a request_id for a transaction.
533 =head2 Deprecated methods
535 The following methods are deprecated and may be removed in a future
536 release. Values for vendor and partner should now be set as arguments
537 to Business::OnlinePayment->new(). The value for cert_path was used
538 to support passing a path to PFProAPI.pm (a Perl module/SDK from
539 Verisign/Paypal) which is no longer used.
555 The following default settings exist:
561 payflowpro.paypal.com or pilot-payflowpro.paypal.com if
562 test_transaction() is TRUE
570 =head1 Handling of content(%content)
572 The following rules apply to content(%content) data:
576 If 'action' matches one of the following keys it is replaced by the
577 right hand side value:
579 'normal authorization' => 'S', # Sale transaction
580 'credit' => 'C', # Credit (refund)
581 'authorization only' => 'A', # Authorization
582 'post authorization' => 'D', # Delayed Capture
585 If 'action' is 'C', 'D' or 'V' and 'order_number' is not set then
586 'amount', 'card_number' and 'expiration' must be set.
590 If 'type' matches one of the following keys it is replaced by the
591 right hand side value:
595 'american express' => 'C',
599 The value of 'type' is used to set transaction_type(). Currently this
600 module only supports a transaction_type() of 'C' any other values will
601 cause Carp::croak() to be called in submit().
603 Note: Payflow Pro supports multiple credit card types, including:
604 American Express/Optima, Diners Club, Discover/Novus, Enroute, JCB,
607 =head1 Setting Payflow Pro parameters from content(%content)
609 The following rules are applied to map data to Payflow Pro parameters
610 from content(%content):
612 # PFP param => $content{<key>}
613 VENDOR => $self->vendor ? \( $self->vendor ) : 'login',
614 PARTNER => \( $self->partner ),
619 ORIGID => 'order_number',
620 COMMENT1 => 'description',
621 COMMENT2 => 'invoice_number',
623 ACCT => 'card_number',
625 EXPDATE => \( $month.$year ), # MM/YY from 'expiration'
628 FIRSTNAME => 'first_name',
629 LASTNAME => 'last_name',
632 COMPANYNAME => 'company',
636 ZIP => \$zip, # 'zip' with non-alphanumerics removed
637 COUNTRY => 'country',
639 # As of 8/18/2009: CUSTCODE appears to be cut off at 18
640 # characters and isn't currently reportable. Consider storing
641 # local customer ids in the COMMENT1/2 fields as a workaround.
642 CUSTCODE => 'customer_id',
644 SHIPTOFIRSTNAME => 'ship_first_name',
645 SHIPTOLASTNAME => 'ship_last_name',
646 SHIPTOSTREET => 'ship_address',
647 SHIPTOCITY => 'ship_city',
648 SHIPTOSTATE => 'ship_state',
649 SHIPTOZIP => 'ship_zip',
650 SHIPTOCOUNTRY => 'ship_country',
652 The required Payflow Pro parameters for credit card transactions are:
654 TRXTYPE TENDER PARTNER VENDOR USER PWD ORIGID
656 =head1 Mapping Payflow Pro transaction responses to object methods
658 The following methods provides access to the transaction response data
659 resulting from a Payflow Pro request (after submit()) is called:
661 =head2 order_number()
663 This order_number() method returns the PNREF field, also known as the
664 PayPal Reference ID, which is a unique number that identifies the
669 The result_code() method returns the RESULT field, which is the
670 numeric return code indicating the outcome of the attempted
673 A RESULT of 0 (zero) indicates the transaction was approved and
674 is_success() will return '1' (one/TRUE). Any other RESULT value
675 indicates a decline or error and is_success() will return '0'
678 =head2 error_message()
680 The error_message() method returns the RESPMSG field, which is a
681 response message returned with the transaction result.
683 =head2 authorization()
685 The authorization() method returns the AUTHCODE field, which is the
686 approval code obtained from the processing network.
690 The avs_code() method returns a combination of the AVSADDR and AVSZIP
691 fields from the transaction result. The value in avs_code is as
694 Y - Address and ZIP match
695 A - Address matches but not ZIP
696 Z - ZIP matches but not address
698 undef - AVS values not available
700 =head2 cvv2_response()
702 The cvv2_response() method returns the CVV2MATCH field, which is a
703 response message returned with the transaction result.
707 As of 0.07, this module communicates with the Payflow gateway directly
708 and no longer requires the Payflow Pro SDK or other download. Thanks
709 to Phil Lobbes for this great work and Josh Rosenbaum for additional
710 enhancements and bug fixes.
714 Ivan Kohler <ivan-payflowpro@420.am>
716 Phil Lobbes E<lt>phil at perkpartners.comE<gt>
718 Based on Business::OnlinePayment::AuthorizeNet written by Jason Kohles.
722 perl(1), L<Business::OnlinePayment>, L<Carp>, and the PayPal
723 Integration Center Payflow Pro resources at
724 L<https://www.paypal.com/IntegrationCenter/ic_payflowpro.html>