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 );
226 # NOTE: we croak above if transaction_type ne 'C'
227 if ( $self->transaction_type() eq 'C' ) { # credit card
228 if ( defined( $content{'ORIGID'} ) && length( $content{'ORIGID'} ) ) {
229 push @required, qw(ORIGID);
232 push @required, qw(AMT ACCT EXPDATE);
236 $self->required_fields(@required);
238 my %params = $self->get_fields(
240 VENDOR PARTNER USER PWD TRXTYPE TENDER ORIGID COMMENT1 COMMENT2
241 ACCT CVV2 EXPDATE AMT
242 FIRSTNAME LASTNAME NAME EMAIL COMPANYNAME
243 STREET CITY STATE ZIP COUNTRY
244 SHIPTOFIRSTNAME SHIPTOLASTNAME
245 SHIPTOSTREET SHIPTOCITY SHIPTOSTATE SHIPTOZIP SHIPTOCOUNTRY
251 my %req_headers = %{ $self->headers || {} };
253 # get request_id from %content if defined for ease of use
254 if ( defined $content{"request_id"} ) {
255 $self->request_id( $content{"request_id"} );
258 unless ( defined( $req_headers{"X-VPS-Request-ID"} ) ) {
259 $req_headers{"X-VPS-Request-ID"} = $self->request_id();
262 unless ( defined( $req_headers{"X-VPS-VIT-Client-Certification-Id"} ) ) {
263 $req_headers{"X-VPS-VIT-Client-Certification-Id"} =
264 $self->client_certification_id;
267 unless ( defined( $req_headers{"X-VPS-Client-Timeout"} ) ) {
268 $req_headers{"X-VPS-Client-Timeout"} = $self->client_timeout();
272 "Content-Type" => "text/namevalue",
273 "headers" => \%req_headers,
276 # Payflow Pro does not use URL encoding for the request. The
277 # following implements their custom encoding scheme. Per the
278 # developer docs, the PARMLIST Syntax Guidelines are:
279 # - Spaces are allowed in values
280 # - Enclose the PARMLIST in quotation marks ("")
281 # - Do not place quotation marks ("") within the body of the PARMLIST
282 # - Separate all PARMLIST name-value pairs using an ampersand (&)
284 # Because '&' and '=' have special meanings/uses values containing
285 # these special characters must be encoded using a special "length
286 # tag". The "length tag" is simply the length of the "value"
287 # enclosed in square brackets ([]) and appended to the "name"
288 # portion of the name-value pair.
290 # For more details see the sections 'Using Special Characters in
291 # Values' and 'PARMLIST Syntax Guidelines' in the PayPal Payflow
292 # Pro Developer's Guide
294 # NOTE: we pass a string to https_post so it does not do encoding
295 my $params_string = join(
299 my $value = defined( $params{$key} ) ? $params{$key} : '';
300 if ( index( $value, '&' ) != -1 || index( $value, '=' ) != -1 ) {
301 $key = $key . "[" . length($value) . "]";
307 my ( $page, $resp, %resp_headers ) =
308 $self->https_post( \%options, $params_string );
310 $self->response_code($resp);
311 $self->response_page($page);
312 $self->response_headers( \%resp_headers );
314 # $page should contain name=value[[&name=value]...] pairs
315 my $response = $self->_get_response( \$page );
317 # AVS and CVS values may be set on success or failure
319 if ( defined $response->{"AVSADDR"} or defined $response->{"AVSZIP"} ) {
320 if ( $response->{"AVSADDR"} eq "Y" && $response->{"AVSZIP"} eq "Y" ) {
323 elsif ( $response->{"AVSADDR"} eq "Y" ) {
326 elsif ( $response->{"AVSZIP"} eq "Y" ) {
329 elsif ( $response->{"AVSADDR"} eq "N" or $response->{"AVSZIP"} eq "N" )
338 $self->avs_code($avs_code);
339 $self->cvv2_response( $response->{"CVV2MATCH"} );
340 $self->result_code( $response->{"RESULT"} );
341 $self->order_number( $response->{"PNREF"} );
342 $self->error_message( $response->{"RESPMSG"} );
343 $self->authorization( $response->{"AUTHCODE"} );
345 # RESULT must be an explicit zero, not just numerically equal
346 if ( defined( $response->{"RESULT"} ) && $response->{"RESULT"} eq "0" ) {
347 $self->is_success(1);
350 $self->is_success(0);
354 # Process the response page for params. Based on parse_params in CGI
355 # by Lincoln D. Stein.
357 my ( $self, $page ) = @_;
361 if ( !defined($page) || ( ref($page) && !defined($$page) ) ) {
365 my ( $param, $value );
366 foreach ( split( /[&;]/, ref($page) ? $$page : $page ) ) {
367 ( $param, $value ) = split( '=', $_, 2 );
368 next unless defined $param;
369 $value = '' unless defined $value;
371 if ($no_cgi_util) { # use old pre-CGI::Util method of unescaping
372 $param =~ tr/+/ /; # pluses become spaces
373 $param =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
374 $value =~ tr/+/ /; # pluses become spaces
375 $value =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
378 $param = CGI::Util::unescape($param);
379 $value = CGI::Util::unescape($value);
381 $response{$param} = $value;
392 Business::OnlinePayment::PayflowPro - Payflow Pro backend for Business::OnlinePayment
396 use Business::OnlinePayment;
398 my $tx = new Business::OnlinePayment(
400 'vendor' => 'your_vendor',
401 'partner' => 'your_partner',
402 'client_certification_id' => 'GuidUpTo32Chars',
405 # See the module documentation for details of content()
408 action => 'Normal Authorization',
409 description => 'Business::OnlinePayment::PayflowPro test',
411 invoice_number => '100100',
412 customer_id => 'jsk',
413 name => 'Jason Kohles',
414 address => '123 Anystreet',
418 email => 'ivan-payflowpro@420.am',
419 card_number => '4111111111111111',
420 expiration => '12/09',
422 order_number => 'string',
423 request_id => 'unique_identifier_for_transaction',
428 if ( $tx->is_success() ) {
430 "Card processed successfully: ", $tx->authorization, "\n",
431 "order number: ", $tx->order_number, "\n",
432 "CVV2 response: ", $tx->cvv2_response, "\n",
433 "AVS code: ", $tx->avs_code, "\n",
438 $info = " (CVV2 mismatch)" if ( $tx->result_code == 114 );
441 "Card was rejected: ", $tx->error_message, $info, "\n",
442 "order number: ", $tx->order_number, "\n",
448 This module is a back end driver that implements the interface
449 specified by L<Business::OnlinePayment> to support payment handling
450 via the PayPal's Payflow Pro Internet payment solution.
452 See L<Business::OnlinePayment> for details on the interface this
455 =head1 Standard methods
461 This method sets the 'server' attribute to 'payflowpro.paypal.com'
462 and the port attribute to '443'. This method also sets up the
463 L</Module specific methods> described below.
469 =head1 Unofficial methods
471 This module provides the following methods which are not officially
472 part of the standard Business::OnlinePayment interface (as of 3.00_06)
473 but are nevertheless supported by multiple gateways modules and
474 expected to be standardized soon:
478 =item L<order_number()|/order_number()>
480 =item L<avs_code()|/avs_code()>
482 =item L<cvv2_response()|/cvv2_response()>
486 =head1 Module specific methods
488 This module provides the following methods which are not currently
489 part of the standard Business::OnlinePayment interface:
491 =head2 client_certification_id()
493 This gets/sets the X-VPS-VITCLIENTCERTIFICATION-ID which is REQUIRED
494 and defaults to "ClientCertificationIdNotSet". This is described in
495 Website Payments Pro HTTPS Interface Developer's Guide as follows:
497 "A random globally unique identifier (GUID) that is currently
498 required. This requirement will be removed in the future. At this
499 time, you can send any alpha-numeric ID up to 32 characters in length.
501 NOTE: Once you have created this ID, do not change it. Use the same ID
502 for every transaction."
504 =head2 client_timeout()
506 Timeout value, in seconds, after which this transaction should be
507 aborted. Defaults to 45, the value recommended by the Website
508 Payments Pro HTTPS Interface Developer's Guide.
512 Enable or disble debugging. The value specified here will also set
513 $Business::OnlinePayment::HTTPS::DEBUG in submit() to aid in
514 troubleshooting problems.
516 =head2 expdate_mmyy()
518 The expdate_mmyy() method takes a single scalar argument (typically
519 the value in $content{expiration}) and attempts to parse and format
520 and put the date in MMYY format as required by PayflowPro
521 specification. If unable to parse the expiration date simply leave it
522 as is and let the PayflowPro system attempt to handle it as-is.
526 It is recommended that you specify your own unique request_id for each
527 transaction in %content. A request_id is REQUIRED by the PayflowPro
528 processor. If a request_id is not set, then Digest::MD5 is used to
529 attempt to generate a request_id for a transaction.
531 =head2 Deprecated methods
533 The following methods are deprecated and may be removed in a future
534 release. Values for vendor and partner should now be set as arguments
535 to Business::OnlinePayment->new(). The value for cert_path was used
536 to support passing a path to PFProAPI.pm (a Perl module/SDK from
537 Verisign/Paypal) which is no longer used.
553 The following default settings exist:
559 payflowpro.paypal.com or pilot-payflowpro.paypal.com if
560 test_transaction() is TRUE
568 =head1 Handling of content(%content)
570 The following rules apply to content(%content) data:
574 If 'action' matches one of the following keys it is replaced by the
575 right hand side value:
577 'normal authorization' => 'S', # Sale transaction
578 'credit' => 'C', # Credit (refund)
579 'authorization only' => 'A', # Authorization
580 'post authorization' => 'D', # Delayed Capture
583 If 'action' is 'C', 'D' or 'V' and 'order_number' is not set then
584 'amount', 'card_number' and 'expiration' must be set.
588 If 'type' matches one of the following keys it is replaced by the
589 right hand side value:
593 'american express' => 'C',
597 The value of 'type' is used to set transaction_type(). Currently this
598 module only supports a transaction_type() of 'C' any other values will
599 cause Carp::croak() to be called in submit().
601 Note: Payflow Pro supports multiple credit card types, including:
602 American Express/Optima, Diners Club, Discover/Novus, Enroute, JCB,
605 =head1 Setting Payflow Pro parameters from content(%content)
607 The following rules are applied to map data to Payflow Pro parameters
608 from content(%content):
610 # PFP param => $content{<key>}
611 VENDOR => $self->vendor ? \( $self->vendor ) : 'login',
612 PARTNER => \( $self->partner ),
617 ORIGID => 'order_number',
618 COMMENT1 => 'description',
619 COMMENT2 => 'invoice_number',
621 ACCT => 'card_number',
623 EXPDATE => \( $month.$year ), # MM/YY from 'expiration'
626 FIRSTNAME => 'first_name',
627 LASTNAME => 'last_name',
630 COMPANYNAME => 'company',
634 ZIP => \$zip, # 'zip' with non-alphanumerics removed
635 COUNTRY => 'country',
637 # As of 8/18/2009: CUSTCODE appears to be cut off at 18
638 # characters and isn't currently reportable. Consider storing
639 # local customer ids in the COMMENT1/2 fields as a workaround.
640 CUSTCODE => 'customer_id',
642 SHIPTOFIRSTNAME => 'ship_first_name',
643 SHIPTOLASTNAME => 'ship_last_name',
644 SHIPTOSTREET => 'ship_address',
645 SHIPTOCITY => 'ship_city',
646 SHIPTOSTATE => 'ship_state',
647 SHIPTOZIP => 'ship_zip',
648 SHIPTOCOUNTRY => 'ship_country',
650 The required Payflow Pro parameters for credit card transactions are:
652 TRXTYPE TENDER PARTNER VENDOR USER PWD ORIGID
654 =head1 Mapping Payflow Pro transaction responses to object methods
656 The following methods provides access to the transaction response data
657 resulting from a Payflow Pro request (after submit()) is called:
659 =head2 order_number()
661 This order_number() method returns the PNREF field, also known as the
662 PayPal Reference ID, which is a unique number that identifies the
667 The result_code() method returns the RESULT field, which is the
668 numeric return code indicating the outcome of the attempted
671 A RESULT of 0 (zero) indicates the transaction was approved and
672 is_success() will return '1' (one/TRUE). Any other RESULT value
673 indicates a decline or error and is_success() will return '0'
676 =head2 error_message()
678 The error_message() method returns the RESPMSG field, which is a
679 response message returned with the transaction result.
681 =head2 authorization()
683 The authorization() method returns the AUTHCODE field, which is the
684 approval code obtained from the processing network.
688 The avs_code() method returns a combination of the AVSADDR and AVSZIP
689 fields from the transaction result. The value in avs_code is as
692 Y - Address and ZIP match
693 A - Address matches but not ZIP
694 Z - ZIP matches but not address
696 undef - AVS values not available
698 =head2 cvv2_response()
700 The cvv2_response() method returns the CVV2MATCH field, which is a
701 response message returned with the transaction result.
705 As of 0.07, this module communicates with the Payflow gateway directly
706 and no longer requires the Payflow Pro SDK or other download. Thanks
707 to Phil Lobbes for this great work and Josh Rosenbaum for additional
708 enhancements and bug fixes.
712 Ivan Kohler <ivan-payflowpro@420.am>
714 Phil Lobbes E<lt>phil at perkpartners.comE<gt>
716 Based on Business::OnlinePayment::AuthorizeNet written by Jason Kohles.
720 perl(1), L<Business::OnlinePayment>, L<Carp>, and the PayPal
721 Integration Center Payflow Pro resources at
722 L<https://www.paypal.com/IntegrationCenter/ic_payflowpro.html>