- rewrite/updates for new B::OP::PayflowPro using HTTP protocol
[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_01';
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 cert_path {
69     carp( __PACKAGE__ . " cert_path method is deprecated" );
70     return undef;
71 }
72
73 # maybe get rid of build_subs() someday and use param()?
74 sub vendor  { my $self = shift; return $self->param( "vendor",  @_ ); }
75 sub partner { my $self = shift; return $self->param( "partner", @_ ); }
76 sub order_number {
77     my $self = shift;
78     return $self->param( "order_number", @_ );
79 }
80 sub avs_code  { my $self = shift; return $self->param( "avs_code",  @_ ); }
81 sub cvv2_code { my $self = shift; return $self->param( "cvv2_code", @_ ); }
82
83 sub set_defaults {
84     my $self = shift;
85     my %opts = @_;
86
87     $self->server("payflow.verisign.com");
88     $self->port("443");
89     $self->path("");    # PayflowPro uses /transaction and /commit
90     if ( $opts{debug} ) {
91         $self->debug( $opts{debug} );
92         delete $opts{debug};
93     }
94     $self->param(
95         "path_transaction" => "/transaction",
96         "path_commit"      => "/commit",
97         %opts
98     );
99 }
100
101 sub _map_fields {
102     my ($self) = @_;
103
104     my %content = $self->content();
105
106     #ACTION MAP
107     my %actions = (
108         'normal authorization' => 'S',    # Sale transaction
109         'credit'               => 'C',    # Credit (refund)
110         'authorization only'   => 'A',    # Authorization
111         'post authorization'   => 'D',    # Delayed Capture
112         'void'                 => 'V',    # Void
113     );
114
115     $content{'action'} = $actions{ lc( $content{'action'} ) }
116       || $content{'action'};
117
118     # TYPE MAP
119     my %types = (
120         'visa'             => 'C',
121         'mastercard'       => 'C',
122         'american express' => 'C',
123         'discover'         => 'C',
124         'cc'               => 'C',
125
126         #'check'            => 'ECHECK',
127     );
128
129     $content{'type'} = $types{ lc( $content{'type'} ) } || $content{'type'};
130
131     $self->transaction_type( $content{'type'} );
132
133     # stuff it back into %content
134     $self->content(%content);
135 }
136
137 sub _revmap_fields {
138     my ( $self, %map ) = @_;
139     my %content = $self->content();
140     foreach ( keys %map ) {
141         $content{$_} =
142           ref( $map{$_} )
143           ? ${ $map{$_} }
144           : $content{ $map{$_} };
145     }
146     $self->content(%content);
147 }
148
149 sub expdate_mmyy {
150     my $self       = shift;
151     my $expiration = shift;
152     my $expdate_mmyy;
153     if ( defined($expiration) and $expiration =~ /^(\d+)\D+\d*(\d{2})$/ ) {
154         my ( $month, $year ) = ( $1, $2 );
155         $expdate_mmyy = sprintf( "%02d", $month ) . $year;
156     }
157     return defined($expdate_mmyy) ? $expdate_mmyy : $expiration;
158 }
159
160 sub submit {
161     my ($self) = @_;
162
163     $self->_map_fields();
164
165     my %content = $self->content;
166
167     if ( $self->transaction_type() ne 'C' ) {
168         croak( "PayflowPro can't (yet?) handle transaction type: "
169               . $self->transaction_type() );
170     }
171
172     my $expdate_mmyy = $self->expdate_mmyy( $content{"expiration"} );
173     my $zip          = $content{'zip'};
174     $zip =~ s/[^[:alnum:]]//g;
175
176     $self->server('pilot-payflowpro.verisign.com') if $self->test_transaction;
177
178     my $vendor  = $self->vendor;
179     my $partner = $self->partner;
180     $self->_revmap_fields(
181
182         # BUG?: VENDOR B::OP:PayflowPro < 0.05 backward compatibility.  If
183         # vendor not set use login although test indicate undef vendor is ok
184         VENDOR => $vendor ? \$vendor : 'login',
185         PARTNER  => \$partner,
186         USER     => 'login',
187         PWD      => 'password',
188         TRXTYPE  => 'action',
189         TENDER   => 'type',
190         ORIGID   => 'order_number',
191         COMMENT1 => 'description',
192         COMMENT2 => 'invoice_number',
193
194         ACCT    => 'card_number',
195         CVV2    => 'cvv2',
196         EXPDATE => \$expdate_mmyy,    # MM/YY from 'expiration'
197         AMT     => 'amount',
198
199         FIRSTNAME   => 'first_name',
200         LASTNAME    => 'last_name',
201         NAME        => 'name',
202         EMAIL       => 'email',
203         COMPANYNAME => 'company',
204         STREET      => 'address',
205         CITY        => 'city',
206         STATE       => 'state',
207         ZIP         => \$zip,          # 'zip' with non-alnums removed
208         COUNTRY     => 'country',
209     );
210
211     my @required = qw( TRXTYPE TENDER PARTNER VENDOR USER PWD );
212     if ( $self->transaction_type() eq 'C' ) {    # credit card
213         if (   $content{'action'} =~ /^[CDV]$/
214             && defined( $content{'ORIGID'} )
215             && length( $content{'ORIGID'} ) )
216         {
217             push @required, qw(ORIGID);
218         }
219         else {
220
221             # never get here, we croak above if transaction_type ne 'C'
222             push @required, qw(AMT ACCT EXPDATE);
223         }
224     }
225     $self->required_fields(@required);
226
227     my %params = $self->get_fields(
228         qw(
229           VENDOR PARTNER USER PWD TRXTYPE TENDER ORIGID COMMENT1 COMMENT2
230           ACCT CVV2 EXPDATE AMT
231           FIRSTNAME LASTNAME NAME EMAIL COMPANYNAME
232           STREET CITY STATE ZIP COUNTRY
233           )
234     );
235
236     # get header data, get request_id from %content if defined for ease of use
237     my %req_headers = %{ $self->param("headers") || {} };
238     if ( defined $content{"request_id"} ) {
239         $req_headers{"X-VPS-REQUEST-ID"} = $content{"request_id"};
240     }
241     unless ( defined( $req_headers{"X-VPS-REQUEST-ID"} ) ) {
242         $req_headers{"X-VPS-REQUEST-ID"} = $self->request_id();
243     }
244
245     my %options = (
246         "Content-Type" => "text/namevalue",
247         "headers"      => \%req_headers,
248     );
249
250     $self->path( $self->param("path_transaction") );
251     my ( $tpage, $tresp, %tresp_headers ) =
252       $self->https_post( \%options, \%params );
253
254     $self->param(
255         "transaction_response" => {
256             page     => $tpage,
257             response => $tresp,
258             headers  => \%tresp_headers,
259         },
260     );
261
262     # $tpage should contain name=value[[&name=value]...] pairs
263     my $cgi = CGI->new("$tpage");
264
265     if ( $cgi->param("RESULT") eq "0" ) {
266         my $response_id = $tresp_headers{"X-VPS-RESPONSE-ID"};
267         $options{headers}->{"X-VPS-RESPONSE-ID"} = $response_id;
268         $self->path( $self->param("path_commit") );
269         my ( $cpage, $cresp, %cresp_headers ) =
270           $self->https_post( \%options, \%params );
271         $self->param(
272             "commit_response" => {
273                 page     => $cpage,
274                 response => $cresp,
275                 headers  => \%cresp_headers,
276             },
277         );
278         my $comcgi = CGI->new("$cpage");
279
280         # merge commit results with transaction
281         foreach my $p ( $comcgi->param() ) {
282             $cgi->param( $p => $comcgi->param($p) );
283         }
284     }
285
286     # AVS and CVS values may be set on success or failure
287     my $avs_code;
288     if ( defined $cgi->param("AVSADDR") or defined $cgi->param("AVSZIP") ) {
289         if ( $cgi->param("AVSADDR") eq "Y" && $cgi->param("AVSZIP") eq "Y" ) {
290             $avs_code = "Y";
291         }
292         elsif ( $cgi->param("AVSADDR") eq "Y" ) {
293             $avs_code = "A";
294         }
295         elsif ( $cgi->param("AVSZIP") eq "Y" ) {
296             $avs_code = "Z";
297         }
298         elsif ( $cgi->param("AVSADDR") eq "N" or $cgi->param("AVSZIP") eq "N" )
299         {
300             $avs_code = "N";
301         }
302         else {
303             $avs_code = "";
304         }
305     }
306
307     $self->avs_code($avs_code);
308     $self->cvv2_code( $cgi->param("CVV2MATCH") );
309     $self->result_code( $cgi->param("RESULT") );
310     $self->order_number( $cgi->param("PNREF") );
311     $self->error_message( $cgi->param("RESPMSG") );
312     $self->authorization( $cgi->param("AUTHCODE") );
313
314     # RESULT must be an explicit zero, not just numerically equal
315     if ( $cgi->param("RESULT") eq "0" ) {
316         $self->is_success(1);
317     }
318     else {
319         $self->is_success(0);
320     }
321 }
322
323 1;
324
325 __END__
326
327 =head1 NAME
328
329 Business::OnlinePayment::PayflowPro - Payflow Pro backend for Business::OnlinePayment
330
331 =head1 SYNOPSIS
332
333   use Business::OnlinePayment;
334   
335   my $tx = new Business::OnlinePayment(
336       'PayflowPro',
337       'vendor'    => 'your_vendor',
338       'partner'   => 'your_partner',
339   );
340   
341   # See the module documentation for details of content()
342   $tx->content(
343       type           => 'VISA',
344       action         => 'Normal Authorization',
345       description    => 'Business::OnlinePayment::PayflowPro test',
346       amount         => '49.95',
347       invoice_number => '100100',
348       customer_id    => 'jsk',
349       name           => 'Jason Kohles',
350       address        => '123 Anystreet',
351       city           => 'Anywhere',
352       state          => 'GA',
353       zip            => '30004',
354       email          => 'ivan-payflowpro@420.am',
355       card_number    => '4111111111111111',
356       expiration     => '12/09',
357       cvv2           => '123',
358       order_number   => 'string',
359   );
360   
361   $tx->submit();
362   
363   if ( $tx->is_success() ) {
364       print(
365           "Card processed successfully: ", $tx->authorization, "\n",
366           "order number: ",                $tx->order_number,  "\n",
367           "CVV2 code: ",                   $tx->cvv2_code,     "\n",
368           "AVS code: ",                    $tx->avs_code,      "\n",
369       );
370   }
371   else {
372       my $info = "";
373       $info = " (CVV2 mismatch)" if ( $tx->result_code == 114 );
374       
375       print(
376           "Card was rejected: ", $tx->error_message, $info, "\n",
377           "order number: ",      $tx->order_number,         "\n",
378       );
379   }
380
381 =head1 DESCRIPTION
382
383 This module is a back end driver that implements the interface
384 specified by L<Business::OnlinePayment> to support payment handling
385 via the PayPal's Payflow Pro Internet payment solution.
386
387 See L<Business::OnlinePayment> for details on the interface this
388 modules supports.
389
390 =head1 Standard methods
391
392 =over 4
393
394 =item set_defaults()
395
396 This method sets the 'server' attribute to 'payflow.verisign.com' and
397 the port attribute to '443'.  This method also sets up the
398 L</Module specific methods> described below.
399
400 =item submit()
401
402 =back
403
404 =head1 Module specific methods
405
406 This module provides the following methods which are not currently
407 part of the standard Business::OnlinePayment interface:
408
409 =over 4
410
411 =item vendor()
412
413 =item partner()
414
415 =item L<order_number()|/order_number()>
416
417 =item L<avs_code()|/avs_code()>
418
419 =item L<cvv2_code()|/cvv2_code()>
420
421 =item L<expdate_mmyy()|/expdate_mmyy()>
422
423 =item L<requeset_id()/request_id()>
424
425 =item L<param()|/param()>
426
427 =item L<debug()|/debug()>
428
429 =item cert_path()
430
431 This method is deprecated and will be removed in the next release.
432 This method was used to support passing a path to PFProAPI.pm (a Perl
433 module/SDK from Verisign/Paypal) which is no longer used.
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