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