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;
70 carp( "method '", __PACKAGE__, "::$_[0]' is deprecated" );
71 return $self->param(@_);
74 # NOTE: for bigger picture perhaps we get rid of build_subs() some day
75 # and instead use something like param() as a standard method?
78 sub cert_path { return shift->_deprecate( "cert_path", @_ ); }
81 sub avs_code { return shift->param( "avs_code", @_ ); }
82 sub cvv2_code { return shift->param( "cvv2_code", @_ ); }
83 sub order_number { return shift->param( "order_number", @_ ); }
84 sub partner { return shift->param( "partner", @_ ); }
85 sub vendor { return shift->param( "vendor", @_ ); }
91 # standard B::OP methods/data
92 $self->server("payflow.verisign.com");
94 $self->path("/transaction");
96 # module specific data
98 $self->debug( $opts{debug} );
103 "test_server" => "pilot-payflowpro.verisign.com",
111 my %content = $self->content();
115 'normal authorization' => 'S', # Sale transaction
116 'credit' => 'C', # Credit (refund)
117 'authorization only' => 'A', # Authorization
118 'post authorization' => 'D', # Delayed Capture
119 'void' => 'V', # Void
122 $content{'action'} = $actions{ lc( $content{'action'} ) }
123 || $content{'action'};
129 'american express' => 'C',
133 #'check' => 'ECHECK',
136 $content{'type'} = $types{ lc( $content{'type'} ) } || $content{'type'};
138 $self->transaction_type( $content{'type'} );
140 # stuff it back into %content
141 $self->content(%content);
145 my ( $self, %map ) = @_;
146 my %content = $self->content();
147 foreach ( keys %map ) {
151 : $content{ $map{$_} };
153 $self->content(%content);
158 my $expiration = shift;
160 if ( defined($expiration) and $expiration =~ /^(\d+)\D+\d*(\d{2})$/ ) {
161 my ( $month, $year ) = ( $1, $2 );
162 $expdate_mmyy = sprintf( "%02d", $month ) . $year;
164 return defined($expdate_mmyy) ? $expdate_mmyy : $expiration;
170 $self->_map_fields();
172 my %content = $self->content;
174 if ( $self->transaction_type() ne 'C' ) {
175 croak( "PayflowPro can't (yet?) handle transaction type: "
176 . $self->transaction_type() );
179 my $expdate_mmyy = $self->expdate_mmyy( $content{"expiration"} );
180 my $zip = $content{'zip'};
181 $zip =~ s/[^[:alnum:]]//g;
183 $self->server( $self->param("test_server") ) if $self->test_transaction;
185 my $vendor = $self->param("vendor");
186 my $partner = $self->param("partner");
188 $self->_revmap_fields(
190 # BUG?: VENDOR B::OP:PayflowPro < 0.05 backward compatibility. If
191 # vendor not set use login although test indicate undef vendor is ok
192 VENDOR => $vendor ? \$vendor : 'login',
193 PARTNER => \$partner,
198 ORIGID => 'order_number',
199 COMMENT1 => 'description',
200 COMMENT2 => 'invoice_number',
202 ACCT => 'card_number',
204 EXPDATE => \$expdate_mmyy, # MM/YY from 'expiration'
207 FIRSTNAME => 'first_name',
208 LASTNAME => 'last_name',
211 COMPANYNAME => 'company',
215 ZIP => \$zip, # 'zip' with non-alnums removed
216 COUNTRY => 'country',
219 my @required = qw( TRXTYPE TENDER PARTNER VENDOR USER PWD );
220 if ( $self->transaction_type() eq 'C' ) { # credit card
221 if ( $content{'action'} =~ /^[CDV]$/
222 && defined( $content{'ORIGID'} )
223 && length( $content{'ORIGID'} ) )
225 push @required, qw(ORIGID);
229 # never get here, we croak above if transaction_type ne 'C'
230 push @required, qw(AMT ACCT EXPDATE);
233 $self->required_fields(@required);
235 my %params = $self->get_fields(
237 VENDOR PARTNER USER PWD TRXTYPE TENDER ORIGID COMMENT1 COMMENT2
238 ACCT CVV2 EXPDATE AMT
239 FIRSTNAME LASTNAME NAME EMAIL COMPANYNAME
240 STREET CITY STATE ZIP COUNTRY
244 # get header data, get request_id from %content if defined for ease of use
245 my %req_headers = %{ $self->param("headers") || {} };
246 if ( defined $content{"request_id"} ) {
247 $req_headers{"X-VPS-REQUEST-ID"} = $content{"request_id"};
249 unless ( defined( $req_headers{"X-VPS-REQUEST-ID"} ) ) {
250 $req_headers{"X-VPS-REQUEST-ID"} = $self->request_id();
254 "Content-Type" => "text/namevalue",
255 "headers" => \%req_headers,
258 my ( $page, $resp, %resp_headers ) =
259 $self->https_post( \%options, \%params );
262 "transaction_response" => {
265 headers => \%resp_headers,
269 # $page should contain name=value[[&name=value]...] pairs
270 my $cgi = CGI->new("$page");
272 # AVS and CVS values may be set on success or failure
274 if ( defined $cgi->param("AVSADDR") or defined $cgi->param("AVSZIP") ) {
275 if ( $cgi->param("AVSADDR") eq "Y" && $cgi->param("AVSZIP") eq "Y" ) {
278 elsif ( $cgi->param("AVSADDR") eq "Y" ) {
281 elsif ( $cgi->param("AVSZIP") eq "Y" ) {
284 elsif ( $cgi->param("AVSADDR") eq "N" or $cgi->param("AVSZIP") eq "N" )
293 $self->avs_code($avs_code);
294 $self->cvv2_code( $cgi->param("CVV2MATCH") );
295 $self->result_code( $cgi->param("RESULT") );
296 $self->order_number( $cgi->param("PNREF") );
297 $self->error_message( $cgi->param("RESPMSG") );
298 $self->authorization( $cgi->param("AUTHCODE") );
300 # RESULT must be an explicit zero, not just numerically equal
301 if ( $cgi->param("RESULT") eq "0" ) {
302 $self->is_success(1);
305 $self->is_success(0);
315 Business::OnlinePayment::PayflowPro - Payflow Pro backend for Business::OnlinePayment
319 use Business::OnlinePayment;
321 my $tx = new Business::OnlinePayment(
323 'vendor' => 'your_vendor',
324 'partner' => 'your_partner',
327 # See the module documentation for details of content()
330 action => 'Normal Authorization',
331 description => 'Business::OnlinePayment::PayflowPro test',
333 invoice_number => '100100',
334 customer_id => 'jsk',
335 name => 'Jason Kohles',
336 address => '123 Anystreet',
340 email => 'ivan-payflowpro@420.am',
341 card_number => '4111111111111111',
342 expiration => '12/09',
344 order_number => 'string',
349 if ( $tx->is_success() ) {
351 "Card processed successfully: ", $tx->authorization, "\n",
352 "order number: ", $tx->order_number, "\n",
353 "CVV2 code: ", $tx->cvv2_code, "\n",
354 "AVS code: ", $tx->avs_code, "\n",
359 $info = " (CVV2 mismatch)" if ( $tx->result_code == 114 );
362 "Card was rejected: ", $tx->error_message, $info, "\n",
363 "order number: ", $tx->order_number, "\n",
369 This module is a back end driver that implements the interface
370 specified by L<Business::OnlinePayment> to support payment handling
371 via the PayPal's Payflow Pro Internet payment solution.
373 See L<Business::OnlinePayment> for details on the interface this
376 =head1 Standard methods
382 This method sets the 'server' attribute to 'payflow.verisign.com' and
383 the port attribute to '443'. This method also sets up the
384 L</Module specific methods> described below.
390 =head1 Module specific methods
392 This module provides the following methods which are not currently
393 part of the standard Business::OnlinePayment interface:
397 =item L<order_number()|/order_number()>
399 =item L<avs_code()|/avs_code()>
401 =item L<cvv2_code()|/cvv2_code()>
403 =item L<expdate_mmyy()|/expdate_mmyy()>
405 =item L<requeset_id()/request_id()>
407 =item L<param()|/param()>
409 =item L<debug()|/debug()>
413 =head2 Deprecated methods
415 The following methods are deprecated and may be removed in the next
416 release. Values for vendor and partner should now be set using the
417 param() method or as arguments to Business::OnlinePayment->new(). The
418 value for cert_path was used to support passing a path to PFProAPI.pm
419 (a Perl module/SDK from Verisign/Paypal) which is no longer used.
433 The following default settings exist:
439 payflow.verisign.com or test-payflow.verisign.com if
440 test_transaction() is TRUE
448 =head1 Handling of content(%content)
450 The following rules apply to content(%content) data:
454 If 'action' matches one of the following keys it is replaced by the
455 right hand side value:
457 'normal authorization' => 'S', # Sale transaction
458 'credit' => 'C', # Credit (refund)
459 'authorization only' => 'A', # Authorization
460 'post authorization' => 'D', # Delayed Capture
463 If 'action' is 'C', 'D' or 'V' and 'order_number' is not set then
464 'amount', 'card_number' and 'expiration' must be set.
468 If 'type' matches one of the following keys it is replaced by the
469 right hand side value:
473 'american express' => 'C',
477 The value of 'type' is used to set transaction_type(). Currently this
478 module only supports a transaction_type() of 'C' any other values will
479 cause Carp::croak() to be called in submit().
481 Note: Payflow Pro supports multiple credit card types, including:
482 American Express/Optima, Diners Club, Discover/Novus, Enroute, JCB,
485 =head1 Setting Payflow Pro parameters from content(%content)
487 The following rules are applied to map data to Payflow Pro parameters
488 from content(%content):
490 # PFP param => $content{<key>}
491 VENDOR => $self->vendor ? \( $self->vendor ) : 'login',
492 PARTNER => \( $self->partner ),
497 ORIGID => 'order_number',
498 COMMENT1 => 'description',
499 COMMENT2 => 'invoice_number',
501 ACCT => 'card_number',
503 EXPDATE => \( $month.$year ), # MM/YY from 'expiration'
506 FIRSTNAME => 'first_name',
507 LASTNAME => 'last_name',
510 COMPANYNAME => 'company',
514 ZIP => \$zip, # 'zip' with non-alphanumerics removed
515 COUNTRY => 'country',
517 The required Payflow Pro parameters for credit card transactions are:
519 TRXTYPE TENDER PARTNER VENDOR USER PWD ORIGID
521 =head1 Mapping Payflow Pro transaction responses to object methods
523 The following methods provides access to the transaction response data
524 resulting from a Payflow Pro request (after submit()) is called:
526 =head2 order_number()
528 This order_number() method returns the PNREF field, also known as the
529 PayPal Reference ID, which is a unique number that identifies the
534 The result_code() method returns the RESULT field, which is the
535 numeric return code indicating the outcome of the attempted
538 A RESULT of 0 (zero) indicates the transaction was approved and
539 is_success() will return '1' (one/TRUE). Any other RESULT value
540 indicates a decline or error and is_success() will return '0'
543 =head2 error_message()
545 The error_message() method returns the RESPMSG field, which is a
546 response message returned with the transaction result.
548 =head2 authorization()
550 The authorization() method returns the AUTHCODE field, which is the
551 approval code obtained from the processing network.
555 The avs_code() method returns a combination of the AVSADDR and AVSZIP
556 fields from the transaction result. The value in avs_code is as
559 Y - Address and ZIP match
560 A - Address matches but not ZIP
561 Z - ZIP matches but not address
563 undef - AVS values not available
567 The cvv2_code() method returns the CVV2MATCH field, which is a
568 response message returned with the transaction result.
570 =head2 expdate_mmyy()
572 The expdate_mmyy() method takes a single scalar argument (typically
573 the value in $content{expiration}) and attempts to parse and format
574 and put the date in MMYY format as required by PayflowPro
575 specification. If unable to parse the expiration date simply leave it
576 as is and let the PayflowPro system attempt to handle it as-is.
580 The request_id() method uses Digest::MD5 to attempt to generate a
581 request_id for a transaction. It is recommended that you specify your
582 own unique request_id for each transaction in %content. A request_id
583 is REQUIRED by the PayflowPro processor.
587 The param() method is used to get/set object parameters. The param()
588 method may be called in several different ways:
590 Get the value of 'myparam':
592 my $value_or_reference = $self->param('myparam');
594 Get a list of all parameters that exist:
596 my @params = $self->param();
598 Set multiple parameters at the same time:
607 Enable or disble debugging. The value specified here will also set
608 $Business::OnlinePayment::HTTPS::DEBUG in submit() to aid in
609 troubleshooting problems.
613 This module implements an interface to the Payflow Pro Perl API, which
614 can be downloaded at https://manager.paypal.com/ with a valid login.
618 Ivan Kohler <ivan-payflowpro@420.am>
620 Phil Lobbes E<lt>phil at perkpartners.comE<gt>
622 Based on Business::OnlinePayment::AuthorizeNet written by Jason Kohles.
626 perl(1), L<Business::OnlinePayment>, L<Carp>, and the PayPal
627 Integration Center Payflow Pro resources at
628 L<https://www.paypal.com/IntegrationCenter/ic_payflowpro.html>