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