0.04
[Business-OnlinePayment-PayflowPro.git] / PayflowPro.pm
1 package Business::OnlinePayment::PayflowPro;
2
3 use strict;
4 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
5 use Carp qw(croak);
6 use AutoLoader;
7 use Business::OnlinePayment;
8
9 #PayflowPRO SDK from Verisign
10 use PFProAPI qw( pfpro );
11
12 require Exporter;
13
14 @ISA = qw(Exporter AutoLoader Business::OnlinePayment);
15 @EXPORT = qw();
16 @EXPORT_OK = qw();
17 $VERSION = '0.04';
18
19 sub set_defaults {
20     my $self = shift;
21
22     $self->server('payflow.verisign.com');
23     $self->port('443');
24
25     $self->build_subs(qw(
26       vendor partner order_number cert_path avs_code cvv2_code
27     ));
28
29 }
30
31 sub map_fields {
32     my($self) = @_;
33
34     my %content = $self->content();
35
36     #ACTION MAP
37     my %actions = ('normal authorization' => 'S', #Sale
38                    'authorization only'   => 'A', #Authorization
39                    'credit'               => 'C', #Credit (refund)
40                    'post authorization'   => 'D', #Delayed Capture
41                    'void'                 => 'V',
42                   );
43     $content{'action'} = $actions{lc($content{'action'})} || $content{'action'};
44
45     # TYPE MAP
46     my %types = ('visa'               => 'C',
47                  'mastercard'         => 'C',
48                  'american express'   => 'C',
49                  'discover'           => 'C',
50                  'cc'                 => 'C'
51                  #'check'              => 'ECHECK',
52                 );
53     $content{'type'} = $types{lc($content{'type'})} || $content{'type'};
54     $self->transaction_type($content{'type'});
55
56     # stuff it back into %content
57     $self->content(%content);
58 }
59
60 sub build_subs {
61     my $self = shift;
62     foreach(@_) {
63         #no warnings; #not 5.005
64         local($^W)=0;
65         eval "sub $_ { my \$self = shift; if(\@_) { \$self->{$_} = shift; } return \$self->{$_}; }";
66     }
67 }
68
69 sub remap_fields {
70     my($self,%map) = @_;
71
72     my %content = $self->content();
73     foreach(keys %map) {
74         $content{$map{$_}} = $content{$_};
75     }
76     $self->content(%content);
77 }
78
79 sub revmap_fields {
80     my($self, %map) = @_;
81     my %content = $self->content();
82     foreach(keys %map) {
83 #    warn "$_ = ". ( ref($map{$_})
84 #                         ? ${ $map{$_} }
85 #                         : $content{$map{$_}} ). "\n";
86         $content{$_} = ref($map{$_})
87                          ? ${ $map{$_} }
88                          : $content{$map{$_}};
89     }
90     $self->content(%content);
91 }
92
93 sub get_fields {
94     my($self,@fields) = @_;
95
96     my %content = $self->content();
97     my %new = ();
98     foreach( grep defined $content{$_}, @fields) { $new{$_} = $content{$_}; }
99     return %new;
100 }
101
102 sub submit {
103     my($self) = @_;
104
105     $self->map_fields();
106
107     my %content = $self->content;
108
109     my($month, $year, $zip);
110
111     #unless ( $content{action} eq 'BillOrders' ) {
112
113         if (  $self->transaction_type() eq 'C' ) {
114         } else {
115             Carp::croak("PayflowPro can't (yet?) handle transaction type: ".
116                         $self->transaction_type());
117         }
118
119       if ( exists($content{'expiration'}) && defined($content{'expiration'})
120            && length($content{'expiration'})                                 ) {
121         $content{'expiration'} =~ /^(\d+)\D+\d*(\d{2})$/
122           or croak "unparsable expiration $content{expiration}";
123
124         ( $month, $year ) = ( $1, $2 );
125         $month = '0'. $month if $month =~ /^\d$/;
126       }
127
128       ( $zip = $content{'zip'} ) =~ s/\D//g;
129     #}
130
131     #$content{'address'} =~ /^(\S+)\s/;
132     #my $addrnum = $1;
133
134     $self->server('test-payflow.verisign.com') if $self->test_transaction;
135
136     $self->revmap_fields(
137       ACCT       => 'card_number',
138       EXPDATE     => \( $month.$year ),
139       AMT         => 'amount',
140       USER        => 'login',
141       #VENDOR      => \( $self->vendor ),
142       VENDOR      => 'login',
143       PARTNER     => \( $self->partner ),
144       PWD         => 'password',
145       TRXTYPE     => 'action',
146       TENDER      => 'type',
147
148       STREET      => 'address',
149       ZIP         => \$zip,
150
151       CITY        => 'city',
152       COMMENT1    => 'description',
153       COMMENT2    => 'invoice_number',
154       COMPANYNAME => 'company',
155       COUNTRY     => 'country',
156       FIRSTNAME   => 'first_name',
157       LASTNAME    => 'last_name',
158       NAME        => 'name',
159       EMAIL       => 'email',
160       STATE       => 'state',
161
162       CVV2        => 'cvv2',
163       ORIGID      => 'order_number'
164
165     );
166
167     my @required = qw( TRXTYPE TENDER PARTNER VENDOR USER PWD );
168     if (  $self->transaction_type() eq 'C' ) { #credit card
169       if ( $content{'action'} =~ /^[CDV]$/ && exists($content{'ORIGID'})
170            && defined($content{'ORIGID'}) && length($content{'ORIGID'}) ) {
171         push @required, qw(ORIGID);
172       } else {
173         push @required, qw(AMT ACCT EXPDATE);
174       }
175     }
176     $self->required_fields(@required);
177
178     my %params = $self->get_fields(qw(
179       ACCT EXPDATE AMT USER VENDOR PARTNER PWD TRXTYPE TENDER
180       STREET ZIP
181       CITY COMMENT1 COMMENT2 COMPANYNAME COUNTRY FIRSTNAME LASTNAME NAME EMAIL
182         STATE
183       CVV2 ORIGID
184     ));
185
186     #print "$_ => $params{$_}\n" foreach keys %params;
187
188     $ENV{'PFPRO_CERT_PATH'} = $self->cert_path;
189     my( $response, $resultstr ) = pfpro( \%params, $self->server, $self->port );
190
191     #if ( $response->{'RESULT'} == 0 ) {
192     if ( $response->{'RESULT'} eq '0' ) { #want an explicit zero, not just
193                                           #numerically equal
194       $self->is_success(1);
195       $self->result_code(   $response->{'RESULT'}   );
196       $self->error_message( $response->{'RESPMSG'}  );
197       $self->authorization( $response->{'AUTHCODE'} );
198       $self->order_number(  $response->{'PNREF'}    );
199       my $avs_code = '';
200       if ( exists $response->{AVSADDR} || exists $response->{AVSZIP} ) {
201         if ( $response->{AVSADDR} eq 'Y' && $response->{AVSZIP} eq 'Y' ) {
202           $avs_code = 'Y';
203         } elsif ( $response->{AVSADDR} eq 'Y' ) {
204           $avs_code = 'A';
205         } elsif ( $response->{AVSZIP} eq 'Y' ) {
206           $avs_code = 'Z';
207         } elsif ( $response->{AVSADDR} eq 'N' || $response->{AVSZIP} eq 'N' ) {
208           $avs_code = 'N';
209         }
210       }
211       $self->avs_code(      $avs_code               );
212       $self->cvv2_code(     $response->{'CVV2MATCH'});
213     } else {
214       $self->is_success(0);
215       $self->result_code(   $response->{'RESULT'}  );
216       $self->error_message( $response->{'RESPMSG'} );
217     }
218
219 }
220
221 1;
222 __END__
223
224 =head1 NAME
225
226 Business::OnlinePayment::PayflowPro - Verisign PayflowPro backend for Business::OnlinePayment
227
228 =head1 SYNOPSIS
229
230   use Business::OnlinePayment;
231
232   my $tx = new Business::OnlinePayment( 'PayflowPro',
233     'vendor'    => 'your_vendor',
234     'partner'   => 'your_partner',
235     'cert_path' => '/path/to/your/certificate/file/', #just the dir
236   );
237
238   $tx->content(
239       type           => 'VISA',
240       action         => 'Normal Authorization',
241       description    => 'Business::OnlinePayment test',
242       amount         => '49.95',
243       invoice_number => '100100',
244       customer_id    => 'jsk',
245       name           => 'Jason Kohles',
246       address        => '123 Anystreet',
247       city           => 'Anywhere',
248       state          => 'UT',
249       zip            => '84058',
250       email          => 'ivan-payflowpro@420.am',
251       card_number    => '4007000000027',
252       expiration     => '09/04',
253
254       #advanced params
255       cvv2           => '420',
256       order_number   => 'string', # returned by $tx->order_number() from an
257                                   # "authorization only" or
258                                   # "normal authorization" action, used by a
259                                   # "credit", "void", or "post authorization"
260   );
261   $tx->submit();
262
263   if($tx->is_success()) {
264       print "Card processed successfully: ".$tx->authorization."\n";
265       print "order number: ". $tx->order_number. "\n";
266       print "AVS code: ". $tx->avs_code. "\n"; # Y - Address and ZIP match
267                                                # A - Address matches but not ZIP
268                                                # Z - ZIP matches bu tnot address
269                                                # N - no match
270                                                # E - AVS error or unsupported
271                                                # (null) - AVS error
272       print "CVV2 code: ". $tx->cvv2_code. "\n";
273
274   } else {
275       print "Card was rejected: ".$tx->error_message;
276       print " (CVV2 mismatch)" if $tx->result_code == 114;
277       print "\n";
278   }
279
280 =head1 SUPPORTED TRANSACTION TYPES
281
282 =head2 Visa, MasterCard, American Express, JCB, Discover/Novus, Carte blanche/Diners Club, CC
283
284 =head1 SUPPORTED ACTIONS
285
286 =head2 Normal Authorization, Authorization Only, Post Authorization, Credit, Void
287
288 =head1 DESCRIPTION
289
290 For detailed information see L<Business::OnlinePayment>.
291
292 =head1 COMPATIBILITY
293
294 This module implements an interface to the PayflowPro Perl API, which can
295 be downloaded at https://manager.verisign.com/ with a valid login.
296
297 =head1 BUGS
298
299 =head1 AUTHOR
300
301 Ivan Kohler <ivan-payflowpro@420.am>
302
303 Based on Busienss::OnlinePayment::AuthorizeNet written by Jason Kohles.
304
305 =head1 SEE ALSO
306
307 perl(1), L<Business::OnlinePayment>.
308
309 =cut
310