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();
252 unless ( defined( $req_headers{"X-VPS-VIT-CLIENT-CERTIFICATION-ID"} ) ) {
253 $req_headers{"X-VPS-VIT-CLIENT-CERTIFICATION-ID"} =
254 $self->param("client_certification_id");
258 "Content-Type" => "text/namevalue",
259 "headers" => \%req_headers,
262 my ( $page, $resp, %resp_headers ) =
263 $self->https_post( \%options, \%params );
266 "transaction_response" => {
269 headers => \%resp_headers,
273 # $page should contain name=value[[&name=value]...] pairs
274 my $cgi = CGI->new("$page");
276 # AVS and CVS values may be set on success or failure
278 if ( defined $cgi->param("AVSADDR") or defined $cgi->param("AVSZIP") ) {
279 if ( $cgi->param("AVSADDR") eq "Y" && $cgi->param("AVSZIP") eq "Y" ) {
282 elsif ( $cgi->param("AVSADDR") eq "Y" ) {
285 elsif ( $cgi->param("AVSZIP") eq "Y" ) {
288 elsif ( $cgi->param("AVSADDR") eq "N" or $cgi->param("AVSZIP") eq "N" )
297 $self->avs_code($avs_code);
298 $self->cvv2_code( $cgi->param("CVV2MATCH") );
299 $self->result_code( $cgi->param("RESULT") );
300 $self->order_number( $cgi->param("PNREF") );
301 $self->error_message( $cgi->param("RESPMSG") );
302 $self->authorization( $cgi->param("AUTHCODE") );
304 # RESULT must be an explicit zero, not just numerically equal
305 if ( $cgi->param("RESULT") eq "0" ) {
306 $self->is_success(1);
309 $self->is_success(0);
319 Business::OnlinePayment::PayflowPro - Payflow Pro backend for Business::OnlinePayment
323 use Business::OnlinePayment;
325 my $tx = new Business::OnlinePayment(
327 'vendor' => 'your_vendor',
328 'partner' => 'your_partner',
329 'client_certification_id' => 'assigned_certification_id',
332 # See the module documentation for details of content()
335 action => 'Normal Authorization',
336 description => 'Business::OnlinePayment::PayflowPro test',
338 invoice_number => '100100',
339 customer_id => 'jsk',
340 name => 'Jason Kohles',
341 address => '123 Anystreet',
345 email => 'ivan-payflowpro@420.am',
346 card_number => '4111111111111111',
347 expiration => '12/09',
349 order_number => 'string',
350 request_id => 'unique_identifier_for_transaction',
355 if ( $tx->is_success() ) {
357 "Card processed successfully: ", $tx->authorization, "\n",
358 "order number: ", $tx->order_number, "\n",
359 "CVV2 code: ", $tx->cvv2_code, "\n",
360 "AVS code: ", $tx->avs_code, "\n",
365 $info = " (CVV2 mismatch)" if ( $tx->result_code == 114 );
368 "Card was rejected: ", $tx->error_message, $info, "\n",
369 "order number: ", $tx->order_number, "\n",
375 This module is a back end driver that implements the interface
376 specified by L<Business::OnlinePayment> to support payment handling
377 via the PayPal's Payflow Pro Internet payment solution.
379 See L<Business::OnlinePayment> for details on the interface this
382 =head1 Standard methods
388 This method sets the 'server' attribute to 'payflow.verisign.com' and
389 the port attribute to '443'. This method also sets up the
390 L</Module specific methods> described below.
396 =head1 Module specific methods
398 This module provides the following methods which are not currently
399 part of the standard Business::OnlinePayment interface:
403 =item L<order_number()|/order_number()>
405 =item L<avs_code()|/avs_code()>
407 =item L<cvv2_code()|/cvv2_code()>
409 =item L<expdate_mmyy()|/expdate_mmyy()>
411 =item L<requeset_id()/request_id()>
413 =item L<param()|/param()>
415 =item L<debug()|/debug()>
419 =head2 Deprecated methods
421 The following methods are deprecated and may be removed in the next
422 release. Values for vendor and partner should now be set using the
423 param() method or as arguments to Business::OnlinePayment->new(). The
424 value for cert_path was used to support passing a path to PFProAPI.pm
425 (a Perl 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>