- made generic method for deprecating cert_path, etc.
[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
253     my %options = (
254         "Content-Type" => "text/namevalue",
255         "headers"      => \%req_headers,
256     );
257
258     my ( $page, $resp, %resp_headers ) =
259       $self->https_post( \%options, \%params );
260
261     $self->param(
262         "transaction_response" => {
263             page     => $page,
264             response => $resp,
265             headers  => \%resp_headers,
266         },
267     );
268
269     # $page should contain name=value[[&name=value]...] pairs
270     my $cgi = CGI->new("$page");
271
272     # AVS and CVS values may be set on success or failure
273     my $avs_code;
274     if ( defined $cgi->param("AVSADDR") or defined $cgi->param("AVSZIP") ) {
275         if ( $cgi->param("AVSADDR") eq "Y" && $cgi->param("AVSZIP") eq "Y" ) {
276             $avs_code = "Y";
277         }
278         elsif ( $cgi->param("AVSADDR") eq "Y" ) {
279             $avs_code = "A";
280         }
281         elsif ( $cgi->param("AVSZIP") eq "Y" ) {
282             $avs_code = "Z";
283         }
284         elsif ( $cgi->param("AVSADDR") eq "N" or $cgi->param("AVSZIP") eq "N" )
285         {
286             $avs_code = "N";
287         }
288         else {
289             $avs_code = "";
290         }
291     }
292
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") );
299
300     # RESULT must be an explicit zero, not just numerically equal
301     if ( $cgi->param("RESULT") eq "0" ) {
302         $self->is_success(1);
303     }
304     else {
305         $self->is_success(0);
306     }
307 }
308
309 1;
310
311 __END__
312
313 =head1 NAME
314
315 Business::OnlinePayment::PayflowPro - Payflow Pro backend for Business::OnlinePayment
316
317 =head1 SYNOPSIS
318
319   use Business::OnlinePayment;
320   
321   my $tx = new Business::OnlinePayment(
322       'PayflowPro',
323       'vendor'    => 'your_vendor',
324       'partner'   => 'your_partner',
325   );
326   
327   # See the module documentation for details of content()
328   $tx->content(
329       type           => 'VISA',
330       action         => 'Normal Authorization',
331       description    => 'Business::OnlinePayment::PayflowPro test',
332       amount         => '49.95',
333       invoice_number => '100100',
334       customer_id    => 'jsk',
335       name           => 'Jason Kohles',
336       address        => '123 Anystreet',
337       city           => 'Anywhere',
338       state          => 'GA',
339       zip            => '30004',
340       email          => 'ivan-payflowpro@420.am',
341       card_number    => '4111111111111111',
342       expiration     => '12/09',
343       cvv2           => '123',
344       order_number   => 'string',
345   );
346   
347   $tx->submit();
348   
349   if ( $tx->is_success() ) {
350       print(
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",
355       );
356   }
357   else {
358       my $info = "";
359       $info = " (CVV2 mismatch)" if ( $tx->result_code == 114 );
360       
361       print(
362           "Card was rejected: ", $tx->error_message, $info, "\n",
363           "order number: ",      $tx->order_number,         "\n",
364       );
365   }
366
367 =head1 DESCRIPTION
368
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.
372
373 See L<Business::OnlinePayment> for details on the interface this
374 modules supports.
375
376 =head1 Standard methods
377
378 =over 4
379
380 =item set_defaults()
381
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.
385
386 =item submit()
387
388 =back
389
390 =head1 Module specific methods
391
392 This module provides the following methods which are not currently
393 part of the standard Business::OnlinePayment interface:
394
395 =over 4
396
397 =item L<order_number()|/order_number()>
398
399 =item L<avs_code()|/avs_code()>
400
401 =item L<cvv2_code()|/cvv2_code()>
402
403 =item L<expdate_mmyy()|/expdate_mmyy()>
404
405 =item L<requeset_id()/request_id()>
406
407 =item L<param()|/param()>
408
409 =item L<debug()|/debug()>
410
411 =back
412
413 =head2 Deprecated methods
414
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.
420
421 =over 4
422
423 =item vendor()
424
425 =item partner()
426
427 =item cert_path()
428
429 =back
430
431 =head1 Settings
432
433 The following default settings exist:
434
435 =over 4
436
437 =item server
438
439 payflow.verisign.com or test-payflow.verisign.com if
440 test_transaction() is TRUE
441
442 =item port
443
444 443
445
446 =back
447
448 =head1 Handling of content(%content)
449
450 The following rules apply to content(%content) data:
451
452 =head2 action
453
454 If 'action' matches one of the following keys it is replaced by the
455 right hand side value:
456
457   'normal authorization' => 'S', # Sale transaction
458   'credit'               => 'C', # Credit (refund)
459   'authorization only'   => 'A', # Authorization
460   'post authorization'   => 'D', # Delayed Capture
461   'void'                 => 'V',
462
463 If 'action' is 'C', 'D' or 'V' and 'order_number' is not set then
464 'amount', 'card_number' and 'expiration' must be set.
465
466 =head2 type
467
468 If 'type' matches one of the following keys it is replaced by the
469 right hand side value:
470
471   'visa'               => 'C',
472   'mastercard'         => 'C',
473   'american express'   => 'C',
474   'discover'           => 'C',
475   'cc'                 => 'C',
476
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().
480
481 Note: Payflow Pro supports multiple credit card types, including:
482 American Express/Optima, Diners Club, Discover/Novus, Enroute, JCB,
483 MasterCard and Visa.
484
485 =head1 Setting Payflow Pro parameters from content(%content)
486
487 The following rules are applied to map data to Payflow Pro parameters
488 from content(%content):
489
490       # PFP param => $content{<key>}
491       VENDOR      => $self->vendor ? \( $self->vendor ) : 'login',
492       PARTNER     => \( $self->partner ),
493       USER        => 'login',
494       PWD         => 'password',
495       TRXTYPE     => 'action',
496       TENDER      => 'type',
497       ORIGID      => 'order_number',
498       COMMENT1    => 'description',
499       COMMENT2    => 'invoice_number',
500
501       ACCT        => 'card_number',
502       CVV2        => 'cvv2',
503       EXPDATE     => \( $month.$year ), # MM/YY from 'expiration'
504       AMT         => 'amount',
505
506       FIRSTNAME   => 'first_name',
507       LASTNAME    => 'last_name',
508       NAME        => 'name',
509       EMAIL       => 'email',
510       COMPANYNAME => 'company',
511       STREET      => 'address',
512       CITY        => 'city',
513       STATE       => 'state',
514       ZIP         => \$zip, # 'zip' with non-alphanumerics removed
515       COUNTRY     => 'country',
516
517 The required Payflow Pro parameters for credit card transactions are:
518
519   TRXTYPE TENDER PARTNER VENDOR USER PWD ORIGID
520
521 =head1 Mapping Payflow Pro transaction responses to object methods
522
523 The following methods provides access to the transaction response data
524 resulting from a Payflow Pro request (after submit()) is called:
525
526 =head2 order_number()
527
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
530 transaction.
531
532 =head2 result_code()
533
534 The result_code() method returns the RESULT field, which is the
535 numeric return code indicating the outcome of the attempted
536 transaction.
537
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'
541 (zero/FALSE).
542
543 =head2 error_message()
544
545 The error_message() method returns the RESPMSG field, which is a
546 response message returned with the transaction result.
547
548 =head2 authorization()
549
550 The authorization() method returns the AUTHCODE field, which is the
551 approval code obtained from the processing network.
552
553 =head2 avs_code()
554
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
557 follows:
558
559   Y     - Address and ZIP match
560   A     - Address matches but not ZIP
561   Z     - ZIP matches but not address
562   N     - no match
563   undef - AVS values not available
564
565 =head2 cvv2_code()
566
567 The cvv2_code() method returns the CVV2MATCH field, which is a
568 response message returned with the transaction result.
569
570 =head2 expdate_mmyy()
571
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.
577
578 =head2 request_id()
579
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.
584
585 =head2 param()
586
587 The param() method is used to get/set object parameters.  The param()
588 method may be called in several different ways:
589
590 Get the value of 'myparam':
591
592   my $value_or_reference = $self->param('myparam');
593
594 Get a list of all parameters that exist:
595
596   my @params = $self->param();
597
598 Set multiple parameters at the same time:
599
600   $self->param(
601       'key1' => 'val1',
602       'key2' => 'val2',
603   );
604
605 =head2 debug()
606
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.
610
611 =head1 COMPATIBILITY
612
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.
615
616 =head1 AUTHORS
617
618 Ivan Kohler <ivan-payflowpro@420.am>
619
620 Phil Lobbes E<lt>phil at perkpartners.comE<gt>
621
622 Based on Business::OnlinePayment::AuthorizeNet written by Jason Kohles.
623
624 =head1 SEE ALSO
625
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>
629
630 =cut