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