- version 1.01: rt.cpan.org#49349: Fix Reference Transactions
[Business-OnlinePayment-PayflowPro.git] / PayflowPro.pm
1 package Business::OnlinePayment::PayflowPro;
2
3 use strict;
4 use vars qw($VERSION $DEBUG);
5 use Carp qw(carp croak);
6 use Digest::MD5;
7 use Business::OnlinePayment::HTTPS 0.06;
8
9 use base qw(Business::OnlinePayment::HTTPS);
10
11 $VERSION = '1.01';
12 $VERSION = eval $VERSION;
13 $DEBUG   = 0;
14
15 # CGI::Util was included starting with Perl 5.6. For previous
16 # Perls, let them use the old simple CGI method of unescaping
17 my $no_cgi_util;
18 BEGIN {
19     eval { require CGI::Util; };
20     $no_cgi_util = 1 if $@;
21 }
22
23 # return current request_id or generate a new one if not yet set
24 sub request_id {
25     my $self = shift;
26     if ( ref($self) ) {
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"};
31     }
32     else {
33         return $self->_new_request_id();
34     }
35 }
36
37 sub _new_request_id {
38     my $self = shift;
39     my $md5  = Digest::MD5->new();
40     $md5->add( $$, time(), rand(time) );
41     return $md5->hexdigest();
42 }
43
44 sub debug {
45     my $self = shift;
46
47     if (@_) {
48         my $level = shift || 0;
49         if ( ref($self) ) {
50             $self->{"__DEBUG"} = $level;
51         }
52         else {
53             $DEBUG = $level;
54         }
55         $Business::OnlinePayment::HTTPS::DEBUG = $level;
56     }
57     return ref($self) ? ( $self->{"__DEBUG"} || $DEBUG ) : $DEBUG;
58 }
59
60 # cvv2_code: support legacy code and but deprecate method
61 sub cvv2_code { shift->cvv2_response(@_); }
62
63 sub set_defaults {
64     my $self = shift;
65     my %opts = @_;
66
67     # standard B::OP methods/data
68     $self->server("payflowpro.paypal.com");
69     $self->port("443");
70     $self->path("/transaction");
71
72     $self->build_subs(
73         qw(
74           partner vendor
75           client_certification_id client_timeout
76           headers test_server
77           cert_path
78           order_number avs_code cvv2_response
79           response_page response_code response_headers
80           )
81     );
82
83     # module specific data
84     if ( $opts{debug} ) {
85         $self->debug( $opts{debug} );
86         delete $opts{debug};
87     }
88
89     # HTTPS Interface Dev Guide: must be set but will be removed in future
90     $self->client_certification_id("ClientCertificationIdNotSet");
91
92     # required: 45 secs recommended by HTTPS Interface Dev Guide
93     $self->client_timeout(45);
94
95     $self->test_server("pilot-payflowpro.paypal.com");
96 }
97
98 sub _map_fields {
99     my ($self) = @_;
100
101     my %content = $self->content();
102
103     #ACTION MAP
104     my %actions = (
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
110     );
111
112     $content{'action'} = $actions{ lc( $content{'action'} ) }
113       || $content{'action'};
114
115     # TYPE MAP
116     my %types = (
117         'visa'             => 'C',
118         'mastercard'       => 'C',
119         'american express' => 'C',
120         'discover'         => 'C',
121         'cc'               => 'C',
122
123         #'check'            => 'ECHECK',
124     );
125
126     $content{'type'} = $types{ lc( $content{'type'} ) } || $content{'type'};
127
128     $self->transaction_type( $content{'type'} );
129
130     # stuff it back into %content
131     $self->content(%content);
132 }
133
134 sub _revmap_fields {
135     my ( $self, %map ) = @_;
136     my %content = $self->content();
137     foreach ( keys %map ) {
138         $content{$_} =
139           ref( $map{$_} )
140           ? ${ $map{$_} }
141           : $content{ $map{$_} };
142     }
143     $self->content(%content);
144 }
145
146 sub expdate_mmyy {
147     my $self       = shift;
148     my $expiration = shift;
149     my $expdate_mmyy;
150     if ( defined($expiration) and $expiration =~ /^(\d+)\D+\d*(\d{2})$/ ) {
151         my ( $month, $year ) = ( $1, $2 );
152         $expdate_mmyy = sprintf( "%02d", $month ) . $year;
153     }
154     return defined($expdate_mmyy) ? $expdate_mmyy : $expiration;
155 }
156
157 sub submit {
158     my ($self) = @_;
159
160     $self->_map_fields();
161
162     my %content = $self->content;
163
164     if ( $self->transaction_type() ne 'C' ) {
165         croak( "PayflowPro can't (yet?) handle transaction type: "
166               . $self->transaction_type() );
167     }
168
169     my $expdate_mmyy = $self->expdate_mmyy( $content{"expiration"} );
170     my $zip          = $content{'zip'};
171     $zip =~ s/[^[:alnum:]]//g;
172
173     $self->server( $self->test_server ) if $self->test_transaction;
174
175     my $vendor  = $self->vendor;
176     my $partner = $self->partner;
177
178     $self->_revmap_fields(
179
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,
184         USER     => 'login',
185         PWD      => 'password',
186         TRXTYPE  => 'action',
187         TENDER   => 'type',
188         ORIGID   => 'order_number',
189         COMMENT1 => 'description',
190         COMMENT2 => 'invoice_number',
191
192         ACCT    => 'card_number',
193         CVV2    => 'cvv2',
194         EXPDATE => \$expdate_mmyy,    # MM/YY from 'expiration'
195         AMT     => 'amount',
196
197         FIRSTNAME   => 'first_name',
198         LASTNAME    => 'last_name',
199         NAME        => 'name',
200         EMAIL       => 'email',
201         COMPANYNAME => 'company',
202         STREET      => 'address',
203         CITY        => 'city',
204         STATE       => 'state',
205         ZIP         => \$zip,          # 'zip' with non-alnums removed
206         COUNTRY     => 'country',
207
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',
219     );
220
221     # Reload %content as _revmap_fields makes our copy old/invalid!
222     %content = $self->content;
223
224     my @required = qw( TRXTYPE TENDER PARTNER VENDOR USER PWD );
225
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);
230         }
231         else {
232             push @required, qw(AMT ACCT EXPDATE);
233         }
234     }
235
236     $self->required_fields(@required);
237
238     my %params = $self->get_fields(
239         qw(
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
246           CUSTCODE
247           )
248     );
249
250     # get header data
251     my %req_headers = %{ $self->headers || {} };
252
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"} );
256     }
257
258     unless ( defined( $req_headers{"X-VPS-Request-ID"} ) ) {
259         $req_headers{"X-VPS-Request-ID"} = $self->request_id();
260     }
261
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;
265     }
266
267     unless ( defined( $req_headers{"X-VPS-Client-Timeout"} ) ) {
268         $req_headers{"X-VPS-Client-Timeout"} = $self->client_timeout();
269     }
270
271     my %options = (
272         "Content-Type" => "text/namevalue",
273         "headers"      => \%req_headers,
274     );
275
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 (&)
283     # 
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.
289     #
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
293     #
294     # NOTE: we pass a string to https_post so it does not do encoding
295     my $params_string = join(
296         '&',
297         map {
298             my $key = $_;
299             my $value = defined( $params{$key} ) ? $params{$key} : '';
300             if ( index( $value, '&' ) != -1 || index( $value, '=' ) != -1 ) {
301                 $key = $key . "[" . length($value) . "]";
302             }
303             "$key=$value";
304           } keys %params
305     );
306
307     my ( $page, $resp, %resp_headers ) =
308       $self->https_post( \%options, $params_string );
309
310     $self->response_code($resp);
311     $self->response_page($page);
312     $self->response_headers( \%resp_headers );
313
314     # $page should contain name=value[[&name=value]...] pairs
315     my $response = $self->_get_response( \$page );
316
317     # AVS and CVS values may be set on success or failure
318     my $avs_code;
319     if ( defined $response->{"AVSADDR"} or defined $response->{"AVSZIP"} ) {
320         if ( $response->{"AVSADDR"} eq "Y" && $response->{"AVSZIP"} eq "Y" ) {
321             $avs_code = "Y";
322         }
323         elsif ( $response->{"AVSADDR"} eq "Y" ) {
324             $avs_code = "A";
325         }
326         elsif ( $response->{"AVSZIP"} eq "Y" ) {
327             $avs_code = "Z";
328         }
329         elsif ( $response->{"AVSADDR"} eq "N" or $response->{"AVSZIP"} eq "N" )
330         {
331             $avs_code = "N";
332         }
333         else {
334             $avs_code = "";
335         }
336     }
337
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"} );
344
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);
348     }
349     else {
350         $self->is_success(0);
351     }
352 }
353
354 # Process the response page for params.  Based on parse_params in CGI
355 # by Lincoln D. Stein.
356 sub _get_response {
357     my ( $self, $page ) = @_;
358
359     my %response;
360
361     if ( !defined($page) || ( ref($page) && !defined($$page) ) ) {
362         return \%response;
363     }
364
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;
370
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;
376         }
377         else {
378             $param = CGI::Util::unescape($param);
379             $value = CGI::Util::unescape($value);
380         }
381         $response{$param} = $value;
382     }
383     return \%response;
384 }
385
386 1;
387
388 __END__
389
390 =head1 NAME
391
392 Business::OnlinePayment::PayflowPro - Payflow Pro backend for Business::OnlinePayment
393
394 =head1 SYNOPSIS
395
396   use Business::OnlinePayment;
397   
398   my $tx = new Business::OnlinePayment(
399       'PayflowPro',
400       'vendor'  => 'your_vendor',
401       'partner' => 'your_partner',
402       'client_certification_id' => 'GuidUpTo32Chars',
403   );
404   
405   # See the module documentation for details of content()
406   $tx->content(
407       type           => 'VISA',
408       action         => 'Normal Authorization',
409       description    => 'Business::OnlinePayment::PayflowPro test',
410       amount         => '49.95',
411       invoice_number => '100100',
412       customer_id    => 'jsk',
413       name           => 'Jason Kohles',
414       address        => '123 Anystreet',
415       city           => 'Anywhere',
416       state          => 'GA',
417       zip            => '30004',
418       email          => 'ivan-payflowpro@420.am',
419       card_number    => '4111111111111111',
420       expiration     => '12/09',
421       cvv2           => '123',
422       order_number   => 'string',
423       request_id     => 'unique_identifier_for_transaction',
424   );
425   
426   $tx->submit();
427   
428   if ( $tx->is_success() ) {
429       print(
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",
434       );
435   }
436   else {
437       my $info = "";
438       $info = " (CVV2 mismatch)" if ( $tx->result_code == 114 );
439       
440       print(
441           "Card was rejected: ", $tx->error_message, $info, "\n",
442           "order number: ",      $tx->order_number,         "\n",
443       );
444   }
445
446 =head1 DESCRIPTION
447
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.
451
452 See L<Business::OnlinePayment> for details on the interface this
453 modules supports.
454
455 =head1 Standard methods
456
457 =over 4
458
459 =item set_defaults()
460
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.
464
465 =item submit()
466
467 =back
468
469 =head1 Unofficial methods
470
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:
475
476 =over 4
477
478 =item L<order_number()|/order_number()>
479
480 =item L<avs_code()|/avs_code()>
481
482 =item L<cvv2_response()|/cvv2_response()>
483
484 =back
485
486 =head1 Module specific methods
487
488 This module provides the following methods which are not currently
489 part of the standard Business::OnlinePayment interface:
490
491 =head2 client_certification_id()
492
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:
496
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.
500
501 NOTE: Once you have created this ID, do not change it. Use the same ID
502 for every transaction."
503
504 =head2 client_timeout()
505
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.
509
510 =head2 debug()
511
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.
515
516 =head2 expdate_mmyy()
517
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.
523
524 =head2 request_id()
525
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.
530
531 =head2 Deprecated methods
532
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.
538
539 =over 4
540
541 =item vendor()
542
543 =item partner()
544
545 =item cert_path()
546
547 =item cvv2_code()
548
549 =back
550
551 =head1 Settings
552
553 The following default settings exist:
554
555 =over 4
556
557 =item server
558
559 payflowpro.paypal.com or pilot-payflowpro.paypal.com if
560 test_transaction() is TRUE
561
562 =item port
563
564 443
565
566 =back
567
568 =head1 Handling of content(%content)
569
570 The following rules apply to content(%content) data:
571
572 =head2 action
573
574 If 'action' matches one of the following keys it is replaced by the
575 right hand side value:
576
577   'normal authorization' => 'S', # Sale transaction
578   'credit'               => 'C', # Credit (refund)
579   'authorization only'   => 'A', # Authorization
580   'post authorization'   => 'D', # Delayed Capture
581   'void'                 => 'V',
582
583 If 'action' is 'C', 'D' or 'V' and 'order_number' is not set then
584 'amount', 'card_number' and 'expiration' must be set.
585
586 =head2 type
587
588 If 'type' matches one of the following keys it is replaced by the
589 right hand side value:
590
591   'visa'               => 'C',
592   'mastercard'         => 'C',
593   'american express'   => 'C',
594   'discover'           => 'C',
595   'cc'                 => 'C',
596
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().
600
601 Note: Payflow Pro supports multiple credit card types, including:
602 American Express/Optima, Diners Club, Discover/Novus, Enroute, JCB,
603 MasterCard and Visa.
604
605 =head1 Setting Payflow Pro parameters from content(%content)
606
607 The following rules are applied to map data to Payflow Pro parameters
608 from content(%content):
609
610       # PFP param => $content{<key>}
611       VENDOR      => $self->vendor ? \( $self->vendor ) : 'login',
612       PARTNER     => \( $self->partner ),
613       USER        => 'login',
614       PWD         => 'password',
615       TRXTYPE     => 'action',
616       TENDER      => 'type',
617       ORIGID      => 'order_number',
618       COMMENT1    => 'description',
619       COMMENT2    => 'invoice_number',
620
621       ACCT        => 'card_number',
622       CVV2        => 'cvv2',
623       EXPDATE     => \( $month.$year ), # MM/YY from 'expiration'
624       AMT         => 'amount',
625
626       FIRSTNAME   => 'first_name',
627       LASTNAME    => 'last_name',
628       NAME        => 'name',
629       EMAIL       => 'email',
630       COMPANYNAME => 'company',
631       STREET      => 'address',
632       CITY        => 'city',
633       STATE       => 'state',
634       ZIP         => \$zip, # 'zip' with non-alphanumerics removed
635       COUNTRY     => 'country',
636
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',
641
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',
649
650 The required Payflow Pro parameters for credit card transactions are:
651
652   TRXTYPE TENDER PARTNER VENDOR USER PWD ORIGID
653
654 =head1 Mapping Payflow Pro transaction responses to object methods
655
656 The following methods provides access to the transaction response data
657 resulting from a Payflow Pro request (after submit()) is called:
658
659 =head2 order_number()
660
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
663 transaction.
664
665 =head2 result_code()
666
667 The result_code() method returns the RESULT field, which is the
668 numeric return code indicating the outcome of the attempted
669 transaction.
670
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'
674 (zero/FALSE).
675
676 =head2 error_message()
677
678 The error_message() method returns the RESPMSG field, which is a
679 response message returned with the transaction result.
680
681 =head2 authorization()
682
683 The authorization() method returns the AUTHCODE field, which is the
684 approval code obtained from the processing network.
685
686 =head2 avs_code()
687
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
690 follows:
691
692   Y     - Address and ZIP match
693   A     - Address matches but not ZIP
694   Z     - ZIP matches but not address
695   N     - no match
696   undef - AVS values not available
697
698 =head2 cvv2_response()
699
700 The cvv2_response() method returns the CVV2MATCH field, which is a
701 response message returned with the transaction result.
702
703 =head1 COMPATIBILITY
704
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.
709
710 =head1 AUTHORS
711
712 Ivan Kohler <ivan-payflowpro@420.am>
713
714 Phil Lobbes E<lt>phil at perkpartners.comE<gt>
715
716 Based on Business::OnlinePayment::AuthorizeNet written by Jason Kohles.
717
718 =head1 SEE ALSO
719
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>
723
724 =cut