9d3c73f892c0a98ca2261a898d20cf61bc127bc8
[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 CGI;
7 use Digest::MD5;
8
9 use base qw(Business::OnlinePayment::HTTPS);
10
11 $VERSION = '0.07_02';
12 $VERSION = eval $VERSION;
13 $DEBUG   = 0;
14
15 sub request_id {
16     my $self = shift;
17     my $md5  = Digest::MD5->new();
18     $md5->add( $$, time(), rand(time) );
19     return $md5->hexdigest();
20 }
21
22 sub param {
23     my $self = shift;
24     my @args = @_;
25
26     $self->{__PARAM} ||= {};
27     my $param = $self->{__PARAM};
28
29     if (@args) {
30         if ( @args % 2 == 0 ) {
31             %$param = ( %$param, @args );
32         }
33         elsif ( @args == 1 ) {
34             my $arg = shift;
35             if ( ref($arg) eq "HASH" ) {
36                 %$param = ( %$param, %$arg );
37                 return keys %$arg;
38             }
39             else {
40                 return $param->{$arg};
41             }
42         }
43         else {
44             croak("param: invalid arguments: @_\n");
45         }
46     }
47     else {
48         return ( keys %$param );
49     }
50 }
51
52 sub debug {
53     my $self = shift;
54
55     if (@_) {
56         my $level = shift || 0;
57         if ( ref($self) ) {
58             $self->{"__DEBUG"} = $level;
59         }
60         else {
61             $DEBUG = $level;
62         }
63         $Business::OnlinePayment::HTTPS::DEBUG = $level;
64     }
65     return ref($self) ? ( $self->{"__DEBUG"} || $DEBUG ) : $DEBUG;
66 }
67
68 sub _deprecate {
69     my $self = shift;
70     carp( "method '", __PACKAGE__, "::$_[0]' is deprecated" );
71     return $self->param(@_);
72 }
73
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?
76
77 # deprecated methods:
78 sub cert_path { return shift->_deprecate( "cert_path", @_ ); }
79
80 # custom methods:
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",       @_ ); }
86
87 sub set_defaults {
88     my $self = shift;
89     my %opts = @_;
90
91     # standard B::OP methods/data
92     $self->server("payflow.verisign.com");
93     $self->port("443");
94     $self->path("/transaction");
95
96     # module specific data
97     if ( $opts{debug} ) {
98         $self->debug( $opts{debug} );
99         delete $opts{debug};
100     }
101
102     $self->param(
103         "test_server" => "pilot-payflowpro.verisign.com",
104         %opts,
105     );
106 }
107
108 sub _map_fields {
109     my ($self) = @_;
110
111     my %content = $self->content();
112
113     #ACTION MAP
114     my %actions = (
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
120     );
121
122     $content{'action'} = $actions{ lc( $content{'action'} ) }
123       || $content{'action'};
124
125     # TYPE MAP
126     my %types = (
127         'visa'             => 'C',
128         'mastercard'       => 'C',
129         'american express' => 'C',
130         'discover'         => 'C',
131         'cc'               => 'C',
132
133         #'check'            => 'ECHECK',
134     );
135
136     $content{'type'} = $types{ lc( $content{'type'} ) } || $content{'type'};
137
138     $self->transaction_type( $content{'type'} );
139
140     # stuff it back into %content
141     $self->content(%content);
142 }
143
144 sub _revmap_fields {
145     my ( $self, %map ) = @_;
146     my %content = $self->content();
147     foreach ( keys %map ) {
148         $content{$_} =
149           ref( $map{$_} )
150           ? ${ $map{$_} }
151           : $content{ $map{$_} };
152     }
153     $self->content(%content);
154 }
155
156 sub expdate_mmyy {
157     my $self       = shift;
158     my $expiration = shift;
159     my $expdate_mmyy;
160     if ( defined($expiration) and $expiration =~ /^(\d+)\D+\d*(\d{2})$/ ) {
161         my ( $month, $year ) = ( $1, $2 );
162         $expdate_mmyy = sprintf( "%02d", $month ) . $year;
163     }
164     return defined($expdate_mmyy) ? $expdate_mmyy : $expiration;
165 }
166
167 sub submit {
168     my ($self) = @_;
169
170     $self->_map_fields();
171
172     my %content = $self->content;
173
174     if ( $self->transaction_type() ne 'C' ) {
175         croak( "PayflowPro can't (yet?) handle transaction type: "
176               . $self->transaction_type() );
177     }
178
179     my $expdate_mmyy = $self->expdate_mmyy( $content{"expiration"} );
180     my $zip          = $content{'zip'};
181     $zip =~ s/[^[:alnum:]]//g;
182
183     $self->server( $self->param("test_server") ) if $self->test_transaction;
184
185     my $vendor  = $self->param("vendor");
186     my $partner = $self->param("partner");
187
188     $self->_revmap_fields(
189
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,
194         USER     => 'login',
195         PWD      => 'password',
196         TRXTYPE  => 'action',
197         TENDER   => 'type',
198         ORIGID   => 'order_number',
199         COMMENT1 => 'description',
200         COMMENT2 => 'invoice_number',
201
202         ACCT    => 'card_number',
203         CVV2    => 'cvv2',
204         EXPDATE => \$expdate_mmyy,    # MM/YY from 'expiration'
205         AMT     => 'amount',
206
207         FIRSTNAME   => 'first_name',
208         LASTNAME    => 'last_name',
209         NAME        => 'name',
210         EMAIL       => 'email',
211         COMPANYNAME => 'company',
212         STREET      => 'address',
213         CITY        => 'city',
214         STATE       => 'state',
215         ZIP         => \$zip,          # 'zip' with non-alnums removed
216         COUNTRY     => 'country',
217     );
218
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'} ) )
224         {
225             push @required, qw(ORIGID);
226         }
227         else {
228
229             # never get here, we croak above if transaction_type ne 'C'
230             push @required, qw(AMT ACCT EXPDATE);
231         }
232     }
233     $self->required_fields(@required);
234
235     my %params = $self->get_fields(
236         qw(
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
241           )
242     );
243
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"};
248     }
249     unless ( defined( $req_headers{"X-VPS-REQUEST-ID"} ) ) {
250         $req_headers{"X-VPS-REQUEST-ID"} = $self->request_id();
251     }
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");
255     }
256
257     my %options = (
258         "Content-Type" => "text/namevalue",
259         "headers"      => \%req_headers,
260     );
261
262     my ( $page, $resp, %resp_headers ) =
263       $self->https_post( \%options, \%params );
264
265     $self->param(
266         "transaction_response" => {
267             page     => $page,
268             response => $resp,
269             headers  => \%resp_headers,
270         },
271     );
272
273     # $page should contain name=value[[&name=value]...] pairs
274     my $cgi = CGI->new("$page");
275
276     # AVS and CVS values may be set on success or failure
277     my $avs_code;
278     if ( defined $cgi->param("AVSADDR") or defined $cgi->param("AVSZIP") ) {
279         if ( $cgi->param("AVSADDR") eq "Y" && $cgi->param("AVSZIP") eq "Y" ) {
280             $avs_code = "Y";
281         }
282         elsif ( $cgi->param("AVSADDR") eq "Y" ) {
283             $avs_code = "A";
284         }
285         elsif ( $cgi->param("AVSZIP") eq "Y" ) {
286             $avs_code = "Z";
287         }
288         elsif ( $cgi->param("AVSADDR") eq "N" or $cgi->param("AVSZIP") eq "N" )
289         {
290             $avs_code = "N";
291         }
292         else {
293             $avs_code = "";
294         }
295     }
296
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") );
303
304     # RESULT must be an explicit zero, not just numerically equal
305     if ( $cgi->param("RESULT") eq "0" ) {
306         $self->is_success(1);
307     }
308     else {
309         $self->is_success(0);
310     }
311 }
312
313 1;
314
315 __END__
316
317 =head1 NAME
318
319 Business::OnlinePayment::PayflowPro - Payflow Pro backend for Business::OnlinePayment
320
321 =head1 SYNOPSIS
322
323   use Business::OnlinePayment;
324   
325   my $tx = new Business::OnlinePayment(
326       'PayflowPro',
327       'vendor'    => 'your_vendor',
328       'partner'   => 'your_partner',
329       'client_certification_id' => 'assigned_certification_id',
330   );
331   
332   # See the module documentation for details of content()
333   $tx->content(
334       type           => 'VISA',
335       action         => 'Normal Authorization',
336       description    => 'Business::OnlinePayment::PayflowPro test',
337       amount         => '49.95',
338       invoice_number => '100100',
339       customer_id    => 'jsk',
340       name           => 'Jason Kohles',
341       address        => '123 Anystreet',
342       city           => 'Anywhere',
343       state          => 'GA',
344       zip            => '30004',
345       email          => 'ivan-payflowpro@420.am',
346       card_number    => '4111111111111111',
347       expiration     => '12/09',
348       cvv2           => '123',
349       order_number   => 'string',
350       request_id     => 'unique_identifier_for_transaction',
351   );
352   
353   $tx->submit();
354   
355   if ( $tx->is_success() ) {
356       print(
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",
361       );
362   }
363   else {
364       my $info = "";
365       $info = " (CVV2 mismatch)" if ( $tx->result_code == 114 );
366       
367       print(
368           "Card was rejected: ", $tx->error_message, $info, "\n",
369           "order number: ",      $tx->order_number,         "\n",
370       );
371   }
372
373 =head1 DESCRIPTION
374
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.
378
379 See L<Business::OnlinePayment> for details on the interface this
380 modules supports.
381
382 =head1 Standard methods
383
384 =over 4
385
386 =item set_defaults()
387
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.
391
392 =item submit()
393
394 =back
395
396 =head1 Module specific methods
397
398 This module provides the following methods which are not currently
399 part of the standard Business::OnlinePayment interface:
400
401 =over 4
402
403 =item L<order_number()|/order_number()>
404
405 =item L<avs_code()|/avs_code()>
406
407 =item L<cvv2_code()|/cvv2_code()>
408
409 =item L<expdate_mmyy()|/expdate_mmyy()>
410
411 =item L<requeset_id()/request_id()>
412
413 =item L<param()|/param()>
414
415 =item L<debug()|/debug()>
416
417 =back
418
419 =head2 Deprecated methods
420
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.
426
427 =over 4
428
429 =item vendor()
430
431 =item partner()
432
433 =item cert_path()
434
435 =back
436
437 =head1 Settings
438
439 The following default settings exist:
440
441 =over 4
442
443 =item server
444
445 payflow.verisign.com or test-payflow.verisign.com if
446 test_transaction() is TRUE
447
448 =item port
449
450 443
451
452 =back
453
454 =head1 Handling of content(%content)
455
456 The following rules apply to content(%content) data:
457
458 =head2 action
459
460 If 'action' matches one of the following keys it is replaced by the
461 right hand side value:
462
463   'normal authorization' => 'S', # Sale transaction
464   'credit'               => 'C', # Credit (refund)
465   'authorization only'   => 'A', # Authorization
466   'post authorization'   => 'D', # Delayed Capture
467   'void'                 => 'V',
468
469 If 'action' is 'C', 'D' or 'V' and 'order_number' is not set then
470 'amount', 'card_number' and 'expiration' must be set.
471
472 =head2 type
473
474 If 'type' matches one of the following keys it is replaced by the
475 right hand side value:
476
477   'visa'               => 'C',
478   'mastercard'         => 'C',
479   'american express'   => 'C',
480   'discover'           => 'C',
481   'cc'                 => 'C',
482
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().
486
487 Note: Payflow Pro supports multiple credit card types, including:
488 American Express/Optima, Diners Club, Discover/Novus, Enroute, JCB,
489 MasterCard and Visa.
490
491 =head1 Setting Payflow Pro parameters from content(%content)
492
493 The following rules are applied to map data to Payflow Pro parameters
494 from content(%content):
495
496       # PFP param => $content{<key>}
497       VENDOR      => $self->vendor ? \( $self->vendor ) : 'login',
498       PARTNER     => \( $self->partner ),
499       USER        => 'login',
500       PWD         => 'password',
501       TRXTYPE     => 'action',
502       TENDER      => 'type',
503       ORIGID      => 'order_number',
504       COMMENT1    => 'description',
505       COMMENT2    => 'invoice_number',
506
507       ACCT        => 'card_number',
508       CVV2        => 'cvv2',
509       EXPDATE     => \( $month.$year ), # MM/YY from 'expiration'
510       AMT         => 'amount',
511
512       FIRSTNAME   => 'first_name',
513       LASTNAME    => 'last_name',
514       NAME        => 'name',
515       EMAIL       => 'email',
516       COMPANYNAME => 'company',
517       STREET      => 'address',
518       CITY        => 'city',
519       STATE       => 'state',
520       ZIP         => \$zip, # 'zip' with non-alphanumerics removed
521       COUNTRY     => 'country',
522
523 The required Payflow Pro parameters for credit card transactions are:
524
525   TRXTYPE TENDER PARTNER VENDOR USER PWD ORIGID
526
527 =head1 Mapping Payflow Pro transaction responses to object methods
528
529 The following methods provides access to the transaction response data
530 resulting from a Payflow Pro request (after submit()) is called:
531
532 =head2 order_number()
533
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
536 transaction.
537
538 =head2 result_code()
539
540 The result_code() method returns the RESULT field, which is the
541 numeric return code indicating the outcome of the attempted
542 transaction.
543
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'
547 (zero/FALSE).
548
549 =head2 error_message()
550
551 The error_message() method returns the RESPMSG field, which is a
552 response message returned with the transaction result.
553
554 =head2 authorization()
555
556 The authorization() method returns the AUTHCODE field, which is the
557 approval code obtained from the processing network.
558
559 =head2 avs_code()
560
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
563 follows:
564
565   Y     - Address and ZIP match
566   A     - Address matches but not ZIP
567   Z     - ZIP matches but not address
568   N     - no match
569   undef - AVS values not available
570
571 =head2 cvv2_code()
572
573 The cvv2_code() method returns the CVV2MATCH field, which is a
574 response message returned with the transaction result.
575
576 =head2 expdate_mmyy()
577
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.
583
584 =head2 request_id()
585
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.
590
591 =head2 param()
592
593 The param() method is used to get/set object parameters.  The param()
594 method may be called in several different ways:
595
596 Get the value of 'myparam':
597
598   my $value_or_reference = $self->param('myparam');
599
600 Get a list of all parameters that exist:
601
602   my @params = $self->param();
603
604 Set multiple parameters at the same time:
605
606   $self->param(
607       'key1' => 'val1',
608       'key2' => 'val2',
609   );
610
611 =head2 debug()
612
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.
616
617 =head1 COMPATIBILITY
618
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.
621
622 =head1 AUTHORS
623
624 Ivan Kohler <ivan-payflowpro@420.am>
625
626 Phil Lobbes E<lt>phil at perkpartners.comE<gt>
627
628 Based on Business::OnlinePayment::AuthorizeNet written by Jason Kohles.
629
630 =head1 SEE ALSO
631
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>
635
636 =cut