6f49649177095e044c93b92dd4f7f167fb94e3c5
[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.00';
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     if ( $self->transaction_type() eq 'C' ) {    # credit card
226         if (   $content{'action'} =~ /^[CDV]$/
227             && defined( $content{'ORIGID'} )
228             && length( $content{'ORIGID'} ) )
229         {
230             push @required, qw(ORIGID);
231         }
232         else {
233
234             # never get here, we croak above if transaction_type ne 'C'
235             push @required, qw(AMT ACCT EXPDATE);
236         }
237     }
238     $self->required_fields(@required);
239
240     my %params = $self->get_fields(
241         qw(
242           VENDOR PARTNER USER PWD TRXTYPE TENDER ORIGID COMMENT1 COMMENT2
243           ACCT CVV2 EXPDATE AMT
244           FIRSTNAME LASTNAME NAME EMAIL COMPANYNAME
245           STREET CITY STATE ZIP COUNTRY
246           SHIPTOFIRSTNAME SHIPTOLASTNAME
247           SHIPTOSTREET SHIPTOCITY SHIPTOSTATE SHIPTOZIP SHIPTOCOUNTRY
248           CUSTCODE
249           )
250     );
251
252     # get header data
253     my %req_headers = %{ $self->headers || {} };
254
255     # get request_id from %content if defined for ease of use
256     if ( defined $content{"request_id"} ) {
257         $self->request_id( $content{"request_id"} );
258     }
259
260     unless ( defined( $req_headers{"X-VPS-Request-ID"} ) ) {
261         $req_headers{"X-VPS-Request-ID"} = $self->request_id();
262     }
263
264     unless ( defined( $req_headers{"X-VPS-VIT-Client-Certification-Id"} ) ) {
265         $req_headers{"X-VPS-VIT-Client-Certification-Id"} =
266           $self->client_certification_id;
267     }
268
269     unless ( defined( $req_headers{"X-VPS-Client-Timeout"} ) ) {
270         $req_headers{"X-VPS-Client-Timeout"} = $self->client_timeout();
271     }
272
273     my %options = (
274         "Content-Type" => "text/namevalue",
275         "headers"      => \%req_headers,
276     );
277
278     # Payflow Pro does not use URL encoding for the request.  The
279     # following implements their custom encoding scheme.  Per the
280     # developer docs, the PARMLIST Syntax Guidelines are:
281     # - Spaces are allowed in values
282     # - Enclose the PARMLIST in quotation marks ("")
283     # - Do not place quotation marks ("") within the body of the PARMLIST
284     # - Separate all PARMLIST name-value pairs using an ampersand (&)
285     # 
286     # Because '&' and '=' have special meanings/uses values containing
287     # these special characters must be encoded using a special "length
288     # tag".  The "length tag" is simply the length of the "value"
289     # enclosed in square brackets ([]) and appended to the "name"
290     # portion of the name-value pair.
291     #
292     # For more details see the sections 'Using Special Characters in
293     # Values' and 'PARMLIST Syntax Guidelines' in the PayPal Payflow
294     # Pro Developer's Guide
295     #
296     # NOTE: we pass a string to https_post so it does not do encoding
297     my $params_string = join(
298         '&',
299         map {
300             my $key = $_;
301             my $value = defined( $params{$key} ) ? $params{$key} : '';
302             if ( index( $value, '&' ) != -1 || index( $value, '=' ) != -1 ) {
303                 $key = $key . "[" . length($value) . "]";
304             }
305             "$key=$value";
306           } keys %params
307     );
308
309     my ( $page, $resp, %resp_headers ) =
310       $self->https_post( \%options, $params_string );
311
312     $self->response_code($resp);
313     $self->response_page($page);
314     $self->response_headers( \%resp_headers );
315
316     # $page should contain name=value[[&name=value]...] pairs
317     my $response = $self->_get_response( \$page );
318
319     # AVS and CVS values may be set on success or failure
320     my $avs_code;
321     if ( defined $response->{"AVSADDR"} or defined $response->{"AVSZIP"} ) {
322         if ( $response->{"AVSADDR"} eq "Y" && $response->{"AVSZIP"} eq "Y" ) {
323             $avs_code = "Y";
324         }
325         elsif ( $response->{"AVSADDR"} eq "Y" ) {
326             $avs_code = "A";
327         }
328         elsif ( $response->{"AVSZIP"} eq "Y" ) {
329             $avs_code = "Z";
330         }
331         elsif ( $response->{"AVSADDR"} eq "N" or $response->{"AVSZIP"} eq "N" )
332         {
333             $avs_code = "N";
334         }
335         else {
336             $avs_code = "";
337         }
338     }
339
340     $self->avs_code($avs_code);
341     $self->cvv2_response( $response->{"CVV2MATCH"} );
342     $self->result_code( $response->{"RESULT"} );
343     $self->order_number( $response->{"PNREF"} );
344     $self->error_message( $response->{"RESPMSG"} );
345     $self->authorization( $response->{"AUTHCODE"} );
346
347     # RESULT must be an explicit zero, not just numerically equal
348     if ( defined( $response->{"RESULT"} ) && $response->{"RESULT"} eq "0" ) {
349         $self->is_success(1);
350     }
351     else {
352         $self->is_success(0);
353     }
354 }
355
356 # Process the response page for params.  Based on parse_params in CGI
357 # by Lincoln D. Stein.
358 sub _get_response {
359     my ( $self, $page ) = @_;
360
361     my %response;
362
363     if ( !defined($page) || ( ref($page) && !defined($$page) ) ) {
364         return \%response;
365     }
366
367     my ( $param, $value );
368     foreach ( split( /[&;]/, ref($page) ? $$page : $page ) ) {
369         ( $param, $value ) = split( '=', $_, 2 );
370         next unless defined $param;
371         $value = '' unless defined $value;
372
373         if ($no_cgi_util) {    # use old pre-CGI::Util method of unescaping
374             $param =~ tr/+/ /;    # pluses become spaces
375             $param =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
376             $value =~ tr/+/ /;    # pluses become spaces
377             $value =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
378         }
379         else {
380             $param = CGI::Util::unescape($param);
381             $value = CGI::Util::unescape($value);
382         }
383         $response{$param} = $value;
384     }
385     return \%response;
386 }
387
388 1;
389
390 __END__
391
392 =head1 NAME
393
394 Business::OnlinePayment::PayflowPro - Payflow Pro backend for Business::OnlinePayment
395
396 =head1 SYNOPSIS
397
398   use Business::OnlinePayment;
399   
400   my $tx = new Business::OnlinePayment(
401       'PayflowPro',
402       'vendor'  => 'your_vendor',
403       'partner' => 'your_partner',
404       'client_certification_id' => 'GuidUpTo32Chars',
405   );
406   
407   # See the module documentation for details of content()
408   $tx->content(
409       type           => 'VISA',
410       action         => 'Normal Authorization',
411       description    => 'Business::OnlinePayment::PayflowPro test',
412       amount         => '49.95',
413       invoice_number => '100100',
414       customer_id    => 'jsk',
415       name           => 'Jason Kohles',
416       address        => '123 Anystreet',
417       city           => 'Anywhere',
418       state          => 'GA',
419       zip            => '30004',
420       email          => 'ivan-payflowpro@420.am',
421       card_number    => '4111111111111111',
422       expiration     => '12/09',
423       cvv2           => '123',
424       order_number   => 'string',
425       request_id     => 'unique_identifier_for_transaction',
426   );
427   
428   $tx->submit();
429   
430   if ( $tx->is_success() ) {
431       print(
432           "Card processed successfully: ", $tx->authorization, "\n",
433           "order number: ",                $tx->order_number,  "\n",
434           "CVV2 response: ",               $tx->cvv2_response, "\n",
435           "AVS code: ",                    $tx->avs_code,      "\n",
436       );
437   }
438   else {
439       my $info = "";
440       $info = " (CVV2 mismatch)" if ( $tx->result_code == 114 );
441       
442       print(
443           "Card was rejected: ", $tx->error_message, $info, "\n",
444           "order number: ",      $tx->order_number,         "\n",
445       );
446   }
447
448 =head1 DESCRIPTION
449
450 This module is a back end driver that implements the interface
451 specified by L<Business::OnlinePayment> to support payment handling
452 via the PayPal's Payflow Pro Internet payment solution.
453
454 See L<Business::OnlinePayment> for details on the interface this
455 modules supports.
456
457 =head1 Standard methods
458
459 =over 4
460
461 =item set_defaults()
462
463 This method sets the 'server' attribute to 'payflowpro.paypal.com'
464 and the port attribute to '443'.  This method also sets up the
465 L</Module specific methods> described below.
466
467 =item submit()
468
469 =back
470
471 =head1 Unofficial methods
472
473 This module provides the following methods which are not officially
474 part of the standard Business::OnlinePayment interface (as of 3.00_06)
475 but are nevertheless supported by multiple gateways modules and
476 expected to be standardized soon:
477
478 =over 4
479
480 =item L<order_number()|/order_number()>
481
482 =item L<avs_code()|/avs_code()>
483
484 =item L<cvv2_response()|/cvv2_response()>
485
486 =back
487
488 =head1 Module specific methods
489
490 This module provides the following methods which are not currently
491 part of the standard Business::OnlinePayment interface:
492
493 =head2 client_certification_id()
494
495 This gets/sets the X-VPS-VITCLIENTCERTIFICATION-ID which is REQUIRED
496 and defaults to "ClientCertificationIdNotSet".  This is described in
497 Website Payments Pro HTTPS Interface Developer's Guide as follows:
498
499 "A random globally unique identifier (GUID) that is currently
500 required. This requirement will be removed in the future. At this
501 time, you can send any alpha-numeric ID up to 32 characters in length.
502
503 NOTE: Once you have created this ID, do not change it. Use the same ID
504 for every transaction."
505
506 =head2 client_timeout()
507
508 Timeout value, in seconds, after which this transaction should be
509 aborted.  Defaults to 45, the value recommended by the Website
510 Payments Pro HTTPS Interface Developer's Guide.
511
512 =head2 debug()
513
514 Enable or disble debugging.  The value specified here will also set
515 $Business::OnlinePayment::HTTPS::DEBUG in submit() to aid in
516 troubleshooting problems.
517
518 =head2 expdate_mmyy()
519
520 The expdate_mmyy() method takes a single scalar argument (typically
521 the value in $content{expiration}) and attempts to parse and format
522 and put the date in MMYY format as required by PayflowPro
523 specification.  If unable to parse the expiration date simply leave it
524 as is and let the PayflowPro system attempt to handle it as-is.
525
526 =head2 request_id()
527
528 It is recommended that you specify your own unique request_id for each
529 transaction in %content.  A request_id is REQUIRED by the PayflowPro
530 processor.  If a request_id is not set, then Digest::MD5 is used to
531 attempt to generate a request_id for a transaction.
532
533 =head2 Deprecated methods
534
535 The following methods are deprecated and may be removed in a future
536 release.  Values for vendor and partner should now be set as arguments
537 to Business::OnlinePayment->new().  The value for cert_path was used
538 to support passing a path to PFProAPI.pm (a Perl module/SDK from
539 Verisign/Paypal) which is no longer used.
540
541 =over 4
542
543 =item vendor()
544
545 =item partner()
546
547 =item cert_path()
548
549 =item cvv2_code()
550
551 =back
552
553 =head1 Settings
554
555 The following default settings exist:
556
557 =over 4
558
559 =item server
560
561 payflowpro.paypal.com or pilot-payflowpro.paypal.com if
562 test_transaction() is TRUE
563
564 =item port
565
566 443
567
568 =back
569
570 =head1 Handling of content(%content)
571
572 The following rules apply to content(%content) data:
573
574 =head2 action
575
576 If 'action' matches one of the following keys it is replaced by the
577 right hand side value:
578
579   'normal authorization' => 'S', # Sale transaction
580   'credit'               => 'C', # Credit (refund)
581   'authorization only'   => 'A', # Authorization
582   'post authorization'   => 'D', # Delayed Capture
583   'void'                 => 'V',
584
585 If 'action' is 'C', 'D' or 'V' and 'order_number' is not set then
586 'amount', 'card_number' and 'expiration' must be set.
587
588 =head2 type
589
590 If 'type' matches one of the following keys it is replaced by the
591 right hand side value:
592
593   'visa'               => 'C',
594   'mastercard'         => 'C',
595   'american express'   => 'C',
596   'discover'           => 'C',
597   'cc'                 => 'C',
598
599 The value of 'type' is used to set transaction_type().  Currently this
600 module only supports a transaction_type() of 'C' any other values will
601 cause Carp::croak() to be called in submit().
602
603 Note: Payflow Pro supports multiple credit card types, including:
604 American Express/Optima, Diners Club, Discover/Novus, Enroute, JCB,
605 MasterCard and Visa.
606
607 =head1 Setting Payflow Pro parameters from content(%content)
608
609 The following rules are applied to map data to Payflow Pro parameters
610 from content(%content):
611
612       # PFP param => $content{<key>}
613       VENDOR      => $self->vendor ? \( $self->vendor ) : 'login',
614       PARTNER     => \( $self->partner ),
615       USER        => 'login',
616       PWD         => 'password',
617       TRXTYPE     => 'action',
618       TENDER      => 'type',
619       ORIGID      => 'order_number',
620       COMMENT1    => 'description',
621       COMMENT2    => 'invoice_number',
622
623       ACCT        => 'card_number',
624       CVV2        => 'cvv2',
625       EXPDATE     => \( $month.$year ), # MM/YY from 'expiration'
626       AMT         => 'amount',
627
628       FIRSTNAME   => 'first_name',
629       LASTNAME    => 'last_name',
630       NAME        => 'name',
631       EMAIL       => 'email',
632       COMPANYNAME => 'company',
633       STREET      => 'address',
634       CITY        => 'city',
635       STATE       => 'state',
636       ZIP         => \$zip, # 'zip' with non-alphanumerics removed
637       COUNTRY     => 'country',
638
639       # As of 8/18/2009: CUSTCODE appears to be cut off at 18
640       # characters and isn't currently reportable.  Consider storing
641       # local customer ids in the COMMENT1/2 fields as a workaround.
642       CUSTCODE    => 'customer_id',
643
644       SHIPTOFIRSTNAME => 'ship_first_name',
645       SHIPTOLASTNAME  => 'ship_last_name',
646       SHIPTOSTREET    => 'ship_address',
647       SHIPTOCITY      => 'ship_city',
648       SHIPTOSTATE     => 'ship_state',
649       SHIPTOZIP       => 'ship_zip',
650       SHIPTOCOUNTRY   => 'ship_country',
651
652 The required Payflow Pro parameters for credit card transactions are:
653
654   TRXTYPE TENDER PARTNER VENDOR USER PWD ORIGID
655
656 =head1 Mapping Payflow Pro transaction responses to object methods
657
658 The following methods provides access to the transaction response data
659 resulting from a Payflow Pro request (after submit()) is called:
660
661 =head2 order_number()
662
663 This order_number() method returns the PNREF field, also known as the
664 PayPal Reference ID, which is a unique number that identifies the
665 transaction.
666
667 =head2 result_code()
668
669 The result_code() method returns the RESULT field, which is the
670 numeric return code indicating the outcome of the attempted
671 transaction.
672
673 A RESULT of 0 (zero) indicates the transaction was approved and
674 is_success() will return '1' (one/TRUE).  Any other RESULT value
675 indicates a decline or error and is_success() will return '0'
676 (zero/FALSE).
677
678 =head2 error_message()
679
680 The error_message() method returns the RESPMSG field, which is a
681 response message returned with the transaction result.
682
683 =head2 authorization()
684
685 The authorization() method returns the AUTHCODE field, which is the
686 approval code obtained from the processing network.
687
688 =head2 avs_code()
689
690 The avs_code() method returns a combination of the AVSADDR and AVSZIP
691 fields from the transaction result.  The value in avs_code is as
692 follows:
693
694   Y     - Address and ZIP match
695   A     - Address matches but not ZIP
696   Z     - ZIP matches but not address
697   N     - no match
698   undef - AVS values not available
699
700 =head2 cvv2_response()
701
702 The cvv2_response() method returns the CVV2MATCH field, which is a
703 response message returned with the transaction result.
704
705 =head1 COMPATIBILITY
706
707 As of 0.07, this module communicates with the Payflow gateway directly
708 and no longer requires the Payflow Pro SDK or other download.  Thanks
709 to Phil Lobbes for this great work and Josh Rosenbaum for additional
710 enhancements and bug fixes.
711
712 =head1 AUTHORS
713
714 Ivan Kohler <ivan-payflowpro@420.am>
715
716 Phil Lobbes E<lt>phil at perkpartners.comE<gt>
717
718 Based on Business::OnlinePayment::AuthorizeNet written by Jason Kohles.
719
720 =head1 SEE ALSO
721
722 perl(1), L<Business::OnlinePayment>, L<Carp>, and the PayPal
723 Integration Center Payflow Pro resources at
724 L<https://www.paypal.com/IntegrationCenter/ic_payflowpro.html>
725
726 =cut