- tests updated 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);
5 use Carp qw(croak);
6 use base qw(Business::OnlinePayment);
7
8 # Payflow Pro SDK
9 use PFProAPI qw( pfpro );
10
11 $VERSION = '0.06';
12 $VERSION = eval $VERSION;
13
14 sub set_defaults {
15     my $self = shift;
16
17     $self->server('payflow.verisign.com');
18     $self->port('443');
19
20     $self->build_subs(
21         qw(
22           vendor partner cert_path order_number avs_code cvv2_code
23           )
24     );
25 }
26
27 sub map_fields {
28     my ($self) = @_;
29
30     my %content = $self->content();
31
32     #ACTION MAP
33     my %actions = (
34         'normal authorization' => 'S',    # Sale transaction
35         'credit'               => 'C',    # Credit (refund)
36         'authorization only'   => 'A',    # Authorization
37         'post authorization'   => 'D',    # Delayed Capture
38         'void'                 => 'V',    # Void
39     );
40
41     $content{'action'} = $actions{ lc( $content{'action'} ) }
42       || $content{'action'};
43
44     # TYPE MAP
45     my %types = (
46         'visa'             => 'C',
47         'mastercard'       => 'C',
48         'american express' => 'C',
49         'discover'         => 'C',
50         'cc'               => 'C',
51         #'check'            => 'ECHECK',
52     );
53
54     $content{'type'} = $types{ lc( $content{'type'} ) } || $content{'type'};
55
56     $self->transaction_type( $content{'type'} );
57
58     # stuff it back into %content
59     $self->content(%content);
60 }
61
62 sub remap_fields {
63     my ( $self, %map ) = @_;
64
65     my %content = $self->content();
66     foreach ( keys %map ) {
67         $content{ $map{$_} } = $content{$_};
68     }
69     $self->content(%content);
70 }
71
72 sub revmap_fields {
73     my ( $self, %map ) = @_;
74     my %content = $self->content();
75     foreach ( keys %map ) {
76         $content{$_} =
77           ref( $map{$_} )
78           ? ${ $map{$_} }
79           : $content{ $map{$_} };
80     }
81     $self->content(%content);
82 }
83
84 sub submit {
85     my ($self) = @_;
86
87     $self->map_fields();
88
89     my %content = $self->content;
90
91     my ( $month, $year, $zip );
92
93     if ( $self->transaction_type() ne 'C' ) {
94         croak( "PayflowPro can't (yet?) handle transaction type: "
95               . $self->transaction_type() );
96     }
97
98     if ( defined( $content{'expiration'} ) && length( $content{'expiration'} ) )
99     {
100         $content{'expiration'} =~ /^(\d+)\D+\d*(\d{2})$/
101           or croak "unparsable expiration $content{expiration}";
102
103         ( $month, $year ) = ( $1, $2 );
104         $month = '0' . $month if $month =~ /^\d$/;
105     }
106
107     ( $zip = $content{'zip'} ) =~ s/[^[:alnum:]]//g;
108
109     $self->server('test-payflow.verisign.com') if $self->test_transaction;
110
111     $self->revmap_fields(
112
113         # (BUG?) VENDOR B::OP:PayflowPro < 0.05 backward compatibility.  If
114         # vendor not set use login (although test indicate undef vendor is ok)
115         VENDOR      => $self->vendor ? \( $self->vendor ) : 'login',
116         PARTNER     => \( $self->partner ),
117         USER        => 'login',
118         PWD         => 'password',
119         TRXTYPE     => 'action',
120         TENDER      => 'type',
121         ORIGID      => 'order_number',
122         COMMENT1    => 'description',
123         COMMENT2    => 'invoice_number',
124
125         ACCT        => 'card_number',
126         CVV2        => 'cvv2',
127         EXPDATE     => \( $month . $year ), # MM/YY from 'expiration'
128         AMT         => 'amount',
129
130         FIRSTNAME   => 'first_name',
131         LASTNAME    => 'last_name',
132         NAME        => 'name',
133         EMAIL       => 'email',
134         COMPANYNAME => 'company',
135         STREET      => 'address',
136         CITY        => 'city',
137         STATE       => 'state',
138         ZIP         => \$zip,               # 'zip' with non-alnums removed
139         COUNTRY     => 'country',
140     );
141
142     my @required = qw( TRXTYPE TENDER PARTNER VENDOR USER PWD );
143     if ( $self->transaction_type() eq 'C' ) {    # credit card
144         if (   $content{'action'} =~ /^[CDV]$/
145             && defined( $content{'ORIGID'} )
146             && length( $content{'ORIGID'} ) )
147         {
148             push @required, qw(ORIGID);
149         }
150         else {
151             # never get here, we croak above if transaction_type ne 'C'
152             push @required, qw(AMT ACCT EXPDATE);
153         }
154     }
155     $self->required_fields(@required);
156
157     my %params = $self->get_fields(
158         qw(
159           VENDOR PARTNER USER PWD TRXTYPE TENDER ORIGID COMMENT1 COMMENT2
160           ACCT CVV2 EXPDATE AMT
161           FIRSTNAME LASTNAME NAME EMAIL COMPANYNAME
162           STREET CITY STATE ZIP COUNTRY
163           )
164     );
165
166     $ENV{'PFPRO_CERT_PATH'} = $self->cert_path;
167     my ( $response, $resultstr ) =
168       pfpro( \%params, $self->server, $self->port );
169
170     # AVS and CVS values may be set on success or failure
171     my $avs_code;
172     if ( exists $response->{AVSADDR} || exists $response->{AVSZIP} ) {
173         if ( $response->{AVSADDR} eq 'Y' && $response->{AVSZIP} eq 'Y' ) {
174             $avs_code = 'Y';
175         }
176         elsif ( $response->{AVSADDR} eq 'Y' ) {
177             $avs_code = 'A';
178         }
179         elsif ( $response->{AVSZIP} eq 'Y' ) {
180             $avs_code = 'Z';
181         }
182         elsif ( $response->{AVSADDR} eq 'N' || $response->{AVSZIP} eq 'N' ) {
183             $avs_code = 'N';
184         }
185         else {
186             $avs_code = '';
187         }
188     }
189
190     $self->avs_code($avs_code);
191     $self->cvv2_code( $response->{'CVV2MATCH'} );
192     $self->result_code( $response->{'RESULT'} );
193     $self->order_number( $response->{'PNREF'} );
194     $self->error_message( $response->{'RESPMSG'} );
195     $self->authorization( $response->{'AUTHCODE'} );
196
197     # RESULT must be an explicit zero, not just numerically equal
198     if ( $response->{'RESULT'} eq '0' ) {
199         $self->is_success(1);
200     }
201     else {
202         $self->is_success(0);
203     }
204 }
205
206 1;
207
208 __END__
209
210 =head1 NAME
211
212 Business::OnlinePayment::PayflowPro - Payflow Pro backend for Business::OnlinePayment
213
214 =head1 SYNOPSIS
215
216   use Business::OnlinePayment;
217   
218   my $tx = new Business::OnlinePayment(
219       'PayflowPro',
220       'vendor'    => 'your_vendor',
221       'partner'   => 'your_partner',
222       'cert_path' => '/path/to/your/certificate/file/',    # just the dir
223   );
224   
225   # See the module documentation for details of content()
226   $tx->content(
227       type           => 'VISA',
228       action         => 'Normal Authorization',
229       description    => 'Business::OnlinePayment::PayflowPro test',
230       amount         => '49.95',
231       invoice_number => '100100',
232       customer_id    => 'jsk',
233       name           => 'Jason Kohles',
234       address        => '123 Anystreet',
235       city           => 'Anywhere',
236       state          => 'GA',
237       zip            => '30004',
238       email          => 'ivan-payflowpro@420.am',
239       card_number    => '4111111111111111',
240       expiration     => '12/09',
241       cvv2           => '123',
242       order_number   => 'string',
243   );
244   
245   $tx->submit();
246   
247   if ( $tx->is_success() ) {
248       print(
249           "Card processed successfully: ", $tx->authorization, "\n",
250           "order number: ",                $tx->order_number,  "\n",
251           "CVV2 code: ",                   $tx->cvv2_code,     "\n",
252           "AVS code: ",                    $tx->avs_code,      "\n",
253       );
254   }
255   else {
256       my $info = "";
257       $info = " (CVV2 mismatch)" if ( $tx->result_code == 114 );
258       
259       print(
260           "Card was rejected: ", $tx->error_message, $info, "\n",
261           "order number: ",      $tx->order_number,         "\n",
262       );
263   }
264
265 =head1 DESCRIPTION
266
267 This module is a back end driver that implements the interface
268 specified by L<Business::OnlinePayment> to support payment handling
269 via the PayPal's Payflow Pro Internet payment solution.
270
271 See L<Business::OnlinePayment> for details on the interface this
272 modules supports.
273
274 =head1 Module specific methods
275
276 This module provides the following methods which are not currently
277 part of the standard Business::OnlinePayment interface:
278
279 =over 4
280
281 =item vendor()
282
283 =item partner()
284
285 =item cert_path()
286
287 =item L<order_number()|/order_number()>
288
289 =item L<avs_code()|/avs_code()>
290
291 =item L<cvv2_code()|/cvv2_code()>
292
293 =back
294
295 =head1 Settings
296
297 The following default settings exist:
298
299 =over 4
300
301 =item server
302
303 payflow.verisign.com or test-payflow.verisign.com if
304 test_transaction() is TRUE
305
306 =item port
307
308 443
309
310 =back
311
312 =head1 Handling of content(%content)
313
314 The following rules apply to content(%content) data:
315
316 =head2 action
317
318 If 'action' matches one of the following keys it is replaced by the
319 right hand side value:
320
321   'normal authorization' => 'S', # Sale transaction
322   'credit'               => 'C', # Credit (refund)
323   'authorization only'   => 'A', # Authorization
324   'post authorization'   => 'D', # Delayed Capture
325   'void'                 => 'V',
326
327 If 'action' is 'C', 'D' or 'V' and 'order_number' is not set then
328 'amount', 'card_number' and 'expiration' must be set.
329
330 =head2 type
331
332 If 'type' matches one of the following keys it is replaced by the
333 right hand side value:
334
335   'visa'               => 'C',
336   'mastercard'         => 'C',
337   'american express'   => 'C',
338   'discover'           => 'C',
339   'cc'                 => 'C',
340
341 The value of 'type' is used to set transaction_type().  Currently this
342 module only supports a transaction_type() of 'C' any other values will
343 cause Carp::croak() to be called in submit().
344
345 Note: Payflow Pro supports multiple credit card types, including:
346 American Express/Optima, Diners Club, Discover/Novus, Enroute, JCB,
347 MasterCard and Visa.
348
349 =head1 Setting Payflow Pro parameters from content(%content)
350
351 The following rules are applied to map data to Payflow Pro parameters
352 from content(%content):
353
354       # PFP param => $content{<key>}
355       VENDOR      => $self->vendor ? \( $self->vendor ) : 'login',
356       PARTNER     => \( $self->partner ),
357       USER        => 'login',
358       PWD         => 'password',
359       TRXTYPE     => 'action',
360       TENDER      => 'type',
361       ORIGID      => 'order_number',
362       COMMENT1    => 'description',
363       COMMENT2    => 'invoice_number',
364
365       ACCT        => 'card_number',
366       CVV2        => 'cvv2',
367       EXPDATE     => \( $month.$year ), # MM/YY from 'expiration'
368       AMT         => 'amount',
369
370       FIRSTNAME   => 'first_name',
371       LASTNAME    => 'last_name',
372       NAME        => 'name',
373       EMAIL       => 'email',
374       COMPANYNAME => 'company',
375       STREET      => 'address',
376       CITY        => 'city',
377       STATE       => 'state',
378       ZIP         => \$zip, # 'zip' with non-alphanumerics removed
379       COUNTRY     => 'country',
380
381 The required Payflow Pro parameters for credit card transactions are:
382
383   TRXTYPE TENDER PARTNER VENDOR USER PWD ORIGID
384
385 =head1 Mapping Payflow Pro transaction responses to object methods
386
387 The following methods provides access to the transaction response data
388 resulting from a Payflow Pro request (after submit()) is called:
389
390 =head2 order_number()
391
392 This order_number() method returns the PNREF field, also known as the
393 PayPal Reference ID, which is a unique number that identifies the
394 transaction.
395
396 =head2 result_code()
397
398 The result_code() method returns the RESULT field, which is the
399 numeric return code indicating the outcome of the attempted
400 transaction.
401
402 A RESULT of 0 (zero) indicates the transaction was approved and
403 is_success() will return '1' (one/TRUE).  Any other RESULT value
404 indicates a decline or error and is_success() will return '0'
405 (zero/FALSE).
406
407 =head2 error_message()
408
409 The error_message() method returns the RESPMSG field, which is a
410 response message returned with the transaction result.
411
412 =head2 authorization()
413
414 The authorization() method returns the AUTHCODE field, which is the
415 approval code obtained from the processing network.
416
417 =head2 avs_code()
418
419 The avs_code() method returns a combination of the AVSADDR and AVSZIP
420 fields from the transaction result.  The value in avs_code is as
421 follows:
422
423   Y     - Address and ZIP match
424   A     - Address matches but not ZIP
425   Z     - ZIP matches but not address
426   N     - no match
427   undef - AVS values not available
428
429 =head2 cvv2_code()
430
431 The cvv2_code() method returns the CVV2MATCH field, which is a
432 response message returned with the transaction result.
433
434 =head1 COMPATIBILITY
435
436 This module implements an interface to the Payflow Pro Perl API, which
437 can be downloaded at https://manager.paypal.com/ with a valid login.
438
439 =head1 AUTHORS
440
441 Ivan Kohler <ivan-payflowpro@420.am>
442
443 Phil Lobbes E<lt>phil at perkpartners.comE<gt>
444
445 Based on Business::OnlinePayment::AuthorizeNet written by Jason Kohles.
446
447 =head1 SEE ALSO
448
449 perl(1), L<Business::OnlinePayment>, L<Carp>, and the PayPal
450 Integration Center Payflow Pro resources at
451 L<https://www.paypal.com/IntegrationCenter/ic_payflowpro.html>
452
453 =cut