1 package Business::OnlinePayment::PayflowPro;
4 use vars qw($VERSION $DEBUG);
5 use Carp qw(carp croak);
8 use Business::OnlinePayment::HTTPS 0.06;
10 use base qw(Business::OnlinePayment::HTTPS);
13 $VERSION = eval $VERSION;
16 # return current request_id or generate a new one if not yet set
20 $self->{"__request_id"} = shift if (@_); # allow value change/reset
21 $self->{"__request_id"} = $self->_new_request_id()
22 unless ( $self->{"__request_id"} );
23 return $self->{"__request_id"};
26 return $self->_new_request_id();
32 my $md5 = Digest::MD5->new();
33 $md5->add( $$, time(), rand(time) );
34 return $md5->hexdigest();
41 my $level = shift || 0;
43 $self->{"__DEBUG"} = $level;
48 $Business::OnlinePayment::HTTPS::DEBUG = $level;
50 return ref($self) ? ( $self->{"__DEBUG"} || $DEBUG ) : $DEBUG;
53 # cvv2_code: support legacy code and but deprecate method
54 sub cvv2_code { shift->cvv2_response(@_); }
60 # standard B::OP methods/data
61 #$self->server("payflow.verisign.com");
62 $self->server("payflowpro.verisign.com");
64 $self->path("/transaction");
68 client_certification_id client_timeout
71 order_number avs_code cvv2_response
72 response_page response_code response_headers
75 # module specific data
77 $self->debug( $opts{debug} );
81 # HTTPS Interface Dev Guide: must be set but will be removed in future
82 $self->client_certification_id("ClientCertificationIdNotSet");
84 # required: 45 secs recommended by HTTPS Interface Dev Guide
85 $self->client_timeout(45);
87 $self->test_server( "pilot-payflowpro.verisign.com" );
93 my %content = $self->content();
97 'normal authorization' => 'S', # Sale transaction
98 'credit' => 'C', # Credit (refund)
99 'authorization only' => 'A', # Authorization
100 'post authorization' => 'D', # Delayed Capture
101 'void' => 'V', # Void
104 $content{'action'} = $actions{ lc( $content{'action'} ) }
105 || $content{'action'};
111 'american express' => 'C',
115 #'check' => 'ECHECK',
118 $content{'type'} = $types{ lc( $content{'type'} ) } || $content{'type'};
120 $self->transaction_type( $content{'type'} );
122 # stuff it back into %content
123 $self->content(%content);
127 my ( $self, %map ) = @_;
128 my %content = $self->content();
129 foreach ( keys %map ) {
133 : $content{ $map{$_} };
135 $self->content(%content);
140 my $expiration = shift;
142 if ( defined($expiration) and $expiration =~ /^(\d+)\D+\d*(\d{2})$/ ) {
143 my ( $month, $year ) = ( $1, $2 );
144 $expdate_mmyy = sprintf( "%02d", $month ) . $year;
146 return defined($expdate_mmyy) ? $expdate_mmyy : $expiration;
152 $self->_map_fields();
154 my %content = $self->content;
156 if ( $self->transaction_type() ne 'C' ) {
157 croak( "PayflowPro can't (yet?) handle transaction type: "
158 . $self->transaction_type() );
161 my $expdate_mmyy = $self->expdate_mmyy( $content{"expiration"} );
162 my $zip = $content{'zip'};
163 $zip =~ s/[^[:alnum:]]//g;
165 $self->server( $self->test_server ) if $self->test_transaction;
167 my $vendor = $self->vendor;
168 my $partner = $self->partner;
170 $self->_revmap_fields(
172 # BUG?: VENDOR B::OP:PayflowPro < 0.05 backward compatibility. If
173 # vendor not set use login although test indicate undef vendor is ok
174 VENDOR => $vendor ? \$vendor : 'login',
175 PARTNER => \$partner,
180 ORIGID => 'order_number',
181 COMMENT1 => 'description',
182 COMMENT2 => 'invoice_number',
184 ACCT => 'card_number',
186 EXPDATE => \$expdate_mmyy, # MM/YY from 'expiration'
189 FIRSTNAME => 'first_name',
190 LASTNAME => 'last_name',
193 COMPANYNAME => 'company',
197 ZIP => \$zip, # 'zip' with non-alnums removed
198 COUNTRY => 'country',
201 my @required = qw( TRXTYPE TENDER PARTNER VENDOR USER PWD );
202 if ( $self->transaction_type() eq 'C' ) { # credit card
203 if ( $content{'action'} =~ /^[CDV]$/
204 && defined( $content{'ORIGID'} )
205 && length( $content{'ORIGID'} ) )
207 push @required, qw(ORIGID);
211 # never get here, we croak above if transaction_type ne 'C'
212 push @required, qw(AMT ACCT EXPDATE);
215 $self->required_fields(@required);
217 my %params = $self->get_fields(
219 VENDOR PARTNER USER PWD TRXTYPE TENDER ORIGID COMMENT1 COMMENT2
220 ACCT CVV2 EXPDATE AMT
221 FIRSTNAME LASTNAME NAME EMAIL COMPANYNAME
222 STREET CITY STATE ZIP COUNTRY
227 my %req_headers = %{ $self->headers || {} };
229 # get request_id from %content if defined for ease of use
230 if ( defined $content{"request_id"} ) {
231 $self->request_id( $content{"request_id"} );
234 unless ( defined( $req_headers{"X-VPS-Request-ID"} ) ) {
235 $req_headers{"X-VPS-Request-ID"} = $self->request_id();
238 unless ( defined( $req_headers{"X-VPS-VIT-Client-Certification-Id"} ) ) {
239 $req_headers{"X-VPS-VIT-Client-Certification-Id"} =
240 $self->client_certification_id;
243 unless ( defined( $req_headers{"X-VPS-Client-Timeout"} ) ) {
244 $req_headers{"X-VPS-Client-Timeout"} = $self->client_timeout();
248 "Content-Type" => "text/namevalue",
249 "headers" => \%req_headers,
252 my ( $page, $resp, %resp_headers ) =
253 $self->https_post( \%options, \%params );
255 $self->response_code( $resp );
256 $self->response_page( $page );
257 $self->response_headers( \%resp_headers );
259 # $page should contain name=value[[&name=value]...] pairs
260 my $cgi = CGI->new("$page");
262 # AVS and CVS values may be set on success or failure
264 if ( defined $cgi->param("AVSADDR") or defined $cgi->param("AVSZIP") ) {
265 if ( $cgi->param("AVSADDR") eq "Y" && $cgi->param("AVSZIP") eq "Y" ) {
268 elsif ( $cgi->param("AVSADDR") eq "Y" ) {
271 elsif ( $cgi->param("AVSZIP") eq "Y" ) {
274 elsif ( $cgi->param("AVSADDR") eq "N" or $cgi->param("AVSZIP") eq "N" )
283 $self->avs_code($avs_code);
284 $self->cvv2_response( $cgi->param("CVV2MATCH") );
285 $self->result_code( $cgi->param("RESULT") );
286 $self->order_number( $cgi->param("PNREF") );
287 $self->error_message( $cgi->param("RESPMSG") );
288 $self->authorization( $cgi->param("AUTHCODE") );
290 # RESULT must be an explicit zero, not just numerically equal
291 if ( $cgi->param("RESULT") eq "0" ) {
292 $self->is_success(1);
295 $self->is_success(0);
305 Business::OnlinePayment::PayflowPro - Payflow Pro backend for Business::OnlinePayment
309 use Business::OnlinePayment;
311 my $tx = new Business::OnlinePayment(
313 'vendor' => 'your_vendor',
314 'partner' => 'your_partner',
315 'client_certification_id' => 'GuidUpTo32Chars',
318 # See the module documentation for details of content()
321 action => 'Normal Authorization',
322 description => 'Business::OnlinePayment::PayflowPro test',
324 invoice_number => '100100',
325 customer_id => 'jsk',
326 name => 'Jason Kohles',
327 address => '123 Anystreet',
331 email => 'ivan-payflowpro@420.am',
332 card_number => '4111111111111111',
333 expiration => '12/09',
335 order_number => 'string',
336 request_id => 'unique_identifier_for_transaction',
341 if ( $tx->is_success() ) {
343 "Card processed successfully: ", $tx->authorization, "\n",
344 "order number: ", $tx->order_number, "\n",
345 "CVV2 response: ", $tx->cvv2_response, "\n",
346 "AVS code: ", $tx->avs_code, "\n",
351 $info = " (CVV2 mismatch)" if ( $tx->result_code == 114 );
354 "Card was rejected: ", $tx->error_message, $info, "\n",
355 "order number: ", $tx->order_number, "\n",
361 This module is a back end driver that implements the interface
362 specified by L<Business::OnlinePayment> to support payment handling
363 via the PayPal's Payflow Pro Internet payment solution.
365 See L<Business::OnlinePayment> for details on the interface this
368 =head1 Standard methods
374 This method sets the 'server' attribute to 'payflowpro.verisign.com'
375 and the port attribute to '443'. This method also sets up the
376 L</Module specific methods> described below.
382 =head1 Unofficial methods
384 This module provides the following methods which are not officially
385 part of the standard Business::OnlinePayment interface (as of 3.00_06)
386 but are nevertheless supported by multiple gateways modules and
387 expected to be standardized soon:
391 =item L<order_number()|/order_number()>
393 =item L<avs_code()|/avs_code()>
395 =item L<cvv2_response()|/cvv2_response()>
399 =head1 Module specific methods
401 This module provides the following methods which are not currently
402 part of the standard Business::OnlinePayment interface:
404 =head2 client_certification_id()
406 This gets/sets the X-VPS-VITCLIENTCERTIFICATION-ID which is REQUIRED
407 and defaults to "ClientCertificationIdNotSet". This is described in
408 Website Payments Pro HTTPS Interface Developer's Guide as follows:
410 "A random globally unique identifier (GUID) that is currently
411 required. This requirement will be removed in the future. At this
412 time, you can send any alpha-numeric ID up to 32 characters in length.
414 NOTE: Once you have created this ID, do not change it. Use the same ID
415 for every transaction."
417 =head2 client_timeout()
419 Timeout value, in seconds, after which this transaction should be
420 aborted. Defaults to 45, the value recommended by the Website
421 Payments Pro HTTPS Interface Developer's Guide.
425 Enable or disble debugging. The value specified here will also set
426 $Business::OnlinePayment::HTTPS::DEBUG in submit() to aid in
427 troubleshooting problems.
429 =head2 expdate_mmyy()
431 The expdate_mmyy() method takes a single scalar argument (typically
432 the value in $content{expiration}) and attempts to parse and format
433 and put the date in MMYY format as required by PayflowPro
434 specification. If unable to parse the expiration date simply leave it
435 as is and let the PayflowPro system attempt to handle it as-is.
439 It is recommended that you specify your own unique request_id for each
440 transaction in %content. A request_id is REQUIRED by the PayflowPro
441 processor. If a request_id is not set, then Digest::MD5 is used to
442 attempt to generate a request_id for a transaction.
444 =head2 Deprecated methods
446 The following methods are deprecated and may be removed in a future
447 release. Values for vendor and partner should now be set as arguments
448 to Business::OnlinePayment->new(). The value for cert_path was used
449 to support passing a path to PFProAPI.pm (a Perl module/SDK from
450 Verisign/Paypal) which is no longer used.
466 The following default settings exist:
472 payflowpro.verisign.com or pilot-payflowpro.verisign.com if
473 test_transaction() is TRUE
481 =head1 Handling of content(%content)
483 The following rules apply to content(%content) data:
487 If 'action' matches one of the following keys it is replaced by the
488 right hand side value:
490 'normal authorization' => 'S', # Sale transaction
491 'credit' => 'C', # Credit (refund)
492 'authorization only' => 'A', # Authorization
493 'post authorization' => 'D', # Delayed Capture
496 If 'action' is 'C', 'D' or 'V' and 'order_number' is not set then
497 'amount', 'card_number' and 'expiration' must be set.
501 If 'type' matches one of the following keys it is replaced by the
502 right hand side value:
506 'american express' => 'C',
510 The value of 'type' is used to set transaction_type(). Currently this
511 module only supports a transaction_type() of 'C' any other values will
512 cause Carp::croak() to be called in submit().
514 Note: Payflow Pro supports multiple credit card types, including:
515 American Express/Optima, Diners Club, Discover/Novus, Enroute, JCB,
518 =head1 Setting Payflow Pro parameters from content(%content)
520 The following rules are applied to map data to Payflow Pro parameters
521 from content(%content):
523 # PFP param => $content{<key>}
524 VENDOR => $self->vendor ? \( $self->vendor ) : 'login',
525 PARTNER => \( $self->partner ),
530 ORIGID => 'order_number',
531 COMMENT1 => 'description',
532 COMMENT2 => 'invoice_number',
534 ACCT => 'card_number',
536 EXPDATE => \( $month.$year ), # MM/YY from 'expiration'
539 FIRSTNAME => 'first_name',
540 LASTNAME => 'last_name',
543 COMPANYNAME => 'company',
547 ZIP => \$zip, # 'zip' with non-alphanumerics removed
548 COUNTRY => 'country',
550 The required Payflow Pro parameters for credit card transactions are:
552 TRXTYPE TENDER PARTNER VENDOR USER PWD ORIGID
554 =head1 Mapping Payflow Pro transaction responses to object methods
556 The following methods provides access to the transaction response data
557 resulting from a Payflow Pro request (after submit()) is called:
559 =head2 order_number()
561 This order_number() method returns the PNREF field, also known as the
562 PayPal Reference ID, which is a unique number that identifies the
567 The result_code() method returns the RESULT field, which is the
568 numeric return code indicating the outcome of the attempted
571 A RESULT of 0 (zero) indicates the transaction was approved and
572 is_success() will return '1' (one/TRUE). Any other RESULT value
573 indicates a decline or error and is_success() will return '0'
576 =head2 error_message()
578 The error_message() method returns the RESPMSG field, which is a
579 response message returned with the transaction result.
581 =head2 authorization()
583 The authorization() method returns the AUTHCODE field, which is the
584 approval code obtained from the processing network.
588 The avs_code() method returns a combination of the AVSADDR and AVSZIP
589 fields from the transaction result. The value in avs_code is as
592 Y - Address and ZIP match
593 A - Address matches but not ZIP
594 Z - ZIP matches but not address
596 undef - AVS values not available
598 =head2 cvv2_response()
600 The cvv2_response() method returns the CVV2MATCH field, which is a
601 response message returned with the transaction result.
605 As of 0.07, this module communicates with the Payflow gateway directly
606 and no longer requires the Payflow Pro SDK or other download. Thanks
607 to Phil Lobbes for this great work.
611 Ivan Kohler <ivan-payflowpro@420.am>
613 Phil Lobbes E<lt>phil at perkpartners.comE<gt>
615 Based on Business::OnlinePayment::AuthorizeNet written by Jason Kohles.
619 perl(1), L<Business::OnlinePayment>, L<Carp>, and the PayPal
620 Integration Center Payflow Pro resources at
621 L<https://www.paypal.com/IntegrationCenter/ic_payflowpro.html>