- switch to using Test::More
[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 # PayflowPRO SDK from PayPal/Verisign
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 order_number cert_path 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
32                    'authorization only'   => 'A', #Authorization
33                    'credit'               => 'C', #Credit (refund)
34                    'post authorization'   => 'D', #Delayed Capture
35                    'void'                 => 'V',
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       ACCT        => 'card_number',
106       EXPDATE     => \( $month.$year ),
107       AMT         => 'amount',
108       USER        => 'login',
109       # (BUG?) VENDOR B::OP:PayflowPro < 0.05 backward compatibility.  If
110       # vendor not set use login (although test indicate undef vendor is ok)
111       VENDOR      => $self->vendor ? \( $self->vendor ) : 'login',
112       PARTNER     => \( $self->partner ),
113       PWD         => 'password',
114       TRXTYPE     => 'action',
115       TENDER      => 'type',
116
117       STREET      => 'address',
118       ZIP         => \$zip,
119
120       CITY        => 'city',
121       COMMENT1    => 'description',
122       COMMENT2    => 'invoice_number',
123       COMPANYNAME => 'company',
124       COUNTRY     => 'country',
125       FIRSTNAME   => 'first_name',
126       LASTNAME    => 'last_name',
127       NAME        => 'name',
128       EMAIL       => 'email',
129       STATE       => 'state',
130
131       CVV2        => 'cvv2',
132       ORIGID      => 'order_number'
133
134     );
135
136     my @required = qw( TRXTYPE TENDER PARTNER VENDOR USER PWD );
137     if (  $self->transaction_type() eq 'C' ) { #credit card
138       if ( $content{'action'} =~ /^[CDV]$/
139            && defined($content{'ORIGID'})
140            && length($content{'ORIGID'})
141          )
142       {
143         push @required, qw(ORIGID);
144       } else {
145         push @required, qw(AMT ACCT EXPDATE);
146       }
147     }
148     $self->required_fields(@required);
149
150     my %params = $self->get_fields(qw(
151       ACCT EXPDATE AMT USER VENDOR PARTNER PWD TRXTYPE TENDER
152       STREET ZIP CITY COMMENT1 COMMENT2 COMPANYNAME COUNTRY
153       FIRSTNAME LASTNAME NAME EMAIL STATE
154       CVV2 ORIGID
155     ));
156
157     $ENV{'PFPRO_CERT_PATH'} = $self->cert_path;
158     my( $response, $resultstr ) = pfpro( \%params, $self->server, $self->port );
159     # PNREF (aka transaction id) is set on success and failure
160     $self->order_number( $response->{'PNREF'} );
161
162     if ( $response->{'RESULT'} eq '0' ) { #want an explicit zero, not just
163                                           #numerically equal
164       $self->is_success(1);
165       $self->result_code(   $response->{'RESULT'}   );
166       $self->error_message( $response->{'RESPMSG'}  );
167       $self->authorization( $response->{'AUTHCODE'} );
168       my $avs_code = '';
169       if ( exists $response->{AVSADDR} || exists $response->{AVSZIP} ) {
170         if ( $response->{AVSADDR} eq 'Y' && $response->{AVSZIP} eq 'Y' ) {
171           $avs_code = 'Y';
172         } elsif ( $response->{AVSADDR} eq 'Y' ) {
173           $avs_code = 'A';
174         } elsif ( $response->{AVSZIP} eq 'Y' ) {
175           $avs_code = 'Z';
176         } elsif ( $response->{AVSADDR} eq 'N' || $response->{AVSZIP} eq 'N' ) {
177           $avs_code = 'N';
178         }
179       }
180       $self->avs_code(      $avs_code               );
181       $self->cvv2_code(     $response->{'CVV2MATCH'});
182     } else {
183       $self->is_success(0);
184       $self->result_code(   $response->{'RESULT'}  );
185       $self->error_message( $response->{'RESPMSG'} );
186     }
187
188 }
189
190 1;
191
192 __END__
193
194 =head1 NAME
195
196 Business::OnlinePayment::PayflowPro - Verisign PayflowPro backend for Business::OnlinePayment
197
198 =head1 SYNOPSIS
199
200   use Business::OnlinePayment;
201
202   my $tx = new Business::OnlinePayment( 'PayflowPro',
203     'vendor'    => 'your_vendor',
204     'partner'   => 'your_partner',
205     'cert_path' => '/path/to/your/certificate/file/', #just the dir
206   );
207
208   $tx->content(
209       type           => 'VISA',
210       action         => 'Normal Authorization',
211       description    => 'Business::OnlinePayment test',
212       amount         => '49.95',
213       invoice_number => '100100',
214       customer_id    => 'jsk',
215       name           => 'Jason Kohles',
216       address        => '123 Anystreet',
217       city           => 'Anywhere',
218       state          => 'UT',
219       zip            => '84058',
220       email          => 'ivan-payflowpro@420.am',
221       card_number    => '4007000000027',
222       expiration     => '09/04',
223
224       #advanced params
225       cvv2           => '420',
226       order_number   => 'string', # returned by $tx->order_number() from an
227                                   # "authorization only" or
228                                   # "normal authorization" action, used by a
229                                   # "credit", "void", or "post authorization"
230   );
231   $tx->submit();
232
233   if($tx->is_success()) {
234       print "Card processed successfully: ", $tx->authorization, "\n";
235       print "order number: ", $tx->order_number, "\n";
236       print "AVS code: ", $tx->avs_code, "\n"; # Y - Address and ZIP match
237                                                # A - Address matches but not ZIP
238                                                # Z - ZIP matches bu tnot address
239                                                # N - no match
240                                                # E - AVS error or unsupported
241                                                # (null) - AVS error
242       print "CVV2 code: ", $tx->cvv2_code, "\n";
243
244   } else {
245       print "Card was rejected: ", $tx->error_message;
246       print " (CVV2 mismatch)" if $tx->result_code == 114;
247       print "\n";
248   }
249
250 =head1 SUPPORTED TRANSACTION TYPES
251
252 =head2 Visa, MasterCard, American Express, JCB, Discover/Novus, Carte blanche/Diners Club, CC
253
254 =head1 SUPPORTED ACTIONS
255
256 =head2 Normal Authorization, Authorization Only, Post Authorization, Credit, Void
257
258 =head1 DESCRIPTION
259
260 For detailed information see L<Business::OnlinePayment>.
261
262 =head1 COMPATIBILITY
263
264 This module implements an interface to the PayflowPro Perl API, which
265 can be downloaded at https://manager.verisign.com/ with a valid login.
266
267 =head1 BUGS
268
269 =head1 AUTHOR
270
271 Ivan Kohler <ivan-payflowpro@420.am>
272
273 Based on Busienss::OnlinePayment::AuthorizeNet written by Jason Kohles.
274
275 =head1 SEE ALSO
276
277 perl(1), L<Business::OnlinePayment>.
278
279 =cut