improve error messages (particularly for checks)
[Business-OnlinePayment-LinkPoint.git] / LinkPoint.pm
1 package Business::OnlinePayment::LinkPoint;
2
3 # $Id: LinkPoint.pm,v 1.28 2007-05-01 21:37:17 jeff Exp $
4
5 use strict;
6 use vars qw($VERSION @ISA $DEBUG @EXPORT @EXPORT_OK);
7 use Carp qw(croak);
8 use AutoLoader;
9 use Business::OnlinePayment;
10
11 require Exporter;
12
13 @ISA = qw(Exporter AutoLoader Business::OnlinePayment);
14 @EXPORT = qw();
15 @EXPORT_OK = qw();
16 $VERSION = '0.08';
17 $DEBUG = 0;
18
19 use lpperl; #3;  #lpperl.pm from LinkPoint
20 $LPPERL::VERSION =~ /^(\d+\.\d+)/
21   or die "can't parse lpperl.pm version: $LPPERL::VERSION";
22 die "lpperl.pm minimum version 3 required\n" unless $1 >= 3;
23
24 sub set_defaults {
25     my $self = shift;
26
27     #$self->server('staging.linkpt.net');
28     $self->server('secure.linkpt.net');
29     $self->port('1129');
30
31     $self->build_subs(qw(order_number avs_code));
32
33 }
34
35 sub map_fields {
36     my($self) = @_;
37
38     my %content = $self->content();
39
40     #ACTION MAP
41     my %actions = ('normal authorization' => 'SALE',
42                    'authorization only'   => 'PREAUTH',
43                    'credit'               => 'CREDIT',
44                    'post authorization'   => 'POSTAUTH',
45                    'void'                 => 'VOID',
46                   );
47     $content{'action'} = $actions{lc($content{'action'})} || $content{'action'};
48
49     #ACCOUNT TYPE MAP
50     my %account_types = ('personal checking' => 'pc',
51                          'personal savings'  => 'ps',
52                          'business checking' => 'bc',
53                          'business savings'  => 'bs',
54                         );
55     $content{'account_type'} = $account_types{lc($content{'account_type'})}
56                                || $content{'account_type'};
57
58     # stuff it back into %content
59     $self->content(%content);
60 }
61
62 sub build_subs {
63     my $self = shift;
64     foreach(@_) {
65         #no warnings; #not 5.005
66         local($^W)=0;
67         eval "sub $_ { my \$self = shift; if(\@_) { \$self->{$_} = shift; } return \$self->{$_}; }";
68     }
69 }
70
71 sub remap_fields {
72     my($self,%map) = @_;
73
74     my %content = $self->content();
75     foreach(keys %map) {
76         $content{$map{$_}} = $content{$_};
77     }
78     $self->content(%content);
79 }
80
81 sub revmap_fields {
82     my($self, %map) = @_;
83     my %content = $self->content();
84     foreach(keys %map) {
85 #    warn "$_ = ". ( ref($map{$_})
86 #                         ? ${ $map{$_} }
87 #                         : $content{$map{$_}} ). "\n";
88         $content{$_} = ref($map{$_})
89                          ? ${ $map{$_} }
90                          : $content{$map{$_}};
91     }
92     $self->content(%content);
93 }
94
95 sub get_fields {
96     my($self,@fields) = @_;
97
98     my %content = $self->content();
99     my %new = ();
100     foreach( grep defined $content{$_}, @fields) { $new{$_} = $content{$_}; }
101     return %new;
102 }
103
104 sub submit {
105     my($self) = @_;
106
107     $self->map_fields();
108
109     my %content = $self->content;
110
111     my($month, $year);
112     unless ( $content{action} eq 'POSTAUTH'
113              || ( $content{'action'} =~ /^(CREDIT|VOID)$/
114                   && exists $content{'order_number'} )
115              || $self->transaction_type() =~ /^e?check$/i
116            ) {
117
118         if (  $self->transaction_type() =~
119                 /^(cc|visa|mastercard|american express|discover)$/i
120            ) {
121         } else {
122             Carp::croak("LinkPoint can't handle transaction type: ".
123                         $self->transaction_type());
124         }
125
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     $content{'address'} =~ /^(\S+)\s/;
134     my $addrnum = $1;
135
136     my $result = $content{'result'};
137     if ( $self->test_transaction) {
138       $result ||= 'GOOD';
139       #$self->server('staging.linkpt.net');
140     } else {
141       $result ||= 'LIVE';
142     }
143
144     #docs disagree with lpperl.pm here
145     $content{'voidcheck'} = 1       
146       if ($self->transaction_type() =~ /^e?check$/i
147           &&  $content{'action'} =~ /^VOID$/);
148
149     $self->revmap_fields(
150       host         => \( $self->server ),
151       port         => \( $self->port ),
152       #storename    => \( $self->storename ),
153       configfile   => \( $self->storename ),
154       keyfile      => \( $self->keyfile ),
155       addrnum      => \$addrnum,
156       result       => \$result,
157       cardnumber   => 'card_number',
158       cardexpmonth => \$month,
159       cardexpyear  => \$year,
160       chargetotal  => 'amount',
161       oid          => 'order_number',
162       bankname     => 'bank_name',
163       bankstate    => 'bank_state',
164       routing      => 'routing_code',
165       account      => 'account_number',
166       accounttype  => 'account_type',
167       name         => 'account_name',
168       dl           => 'state_id',
169       dlstate      => 'state_id_state',
170     );
171
172     my $lperl = new LPPERL;
173
174     my @required_fields = qw(host port configfile keyfile amount);
175     if ($self->transaction_type() =~ /^(cc|visa|mastercard|american express|discover)$/i) {
176       push @required_fields, qw(cardnumber cardexpmonth cardexpyear);
177     }elsif ($self->transaction_type() =~ /^e?check$/i) {
178       push @required_fields, qw(
179         dl dlstate routing account accounttype bankname bankstate name
180                                );
181     }
182     $self->required_fields(@required_fields);
183
184     my %post_data = $self->get_fields(qw/
185       host port configfile keyfile
186       result
187       chargetotal cardnumber cardexpmonth cardexpyear
188       name email phone addrnum city state zip country
189       oid
190       dl dlstate routing account accounttype bankname bankstate name void
191
192     /);
193
194     $post_data{'ordertype'} = $content{action};
195
196     if ( $content{'cvv2'} ) { 
197       $post_data{cvmindicator} = 'provided';
198       $post_data{cvmvalue} = $content{'cvv2'};
199     }
200
201     if ( $DEBUG ) {
202       warn "$_ => $post_data{$_}\n" foreach keys %post_data;
203     }
204
205     my %response;
206     #{
207     #  local($^W)=0;
208     #  %response = $lperl->$action(\%post_data);
209     #}
210     %response = $lperl->curl_process(\%post_data);
211
212     if ( $DEBUG ) {
213       warn "$_ => $response{$_}\n" for keys %response;
214     }
215
216     if ( $response{'r_approved'} eq 'APPROVED' ) {
217       $self->is_success(1);
218       $self->result_code($response{'r_code'});
219       $self->authorization($response{'r_ref'});
220       $self->order_number($response{'r_ordernum'});
221       $self->avs_code($response{'r_avs'});
222     } else {
223       $self->is_success(0);
224       $self->result_code('');
225       $response{'r_error'} =~/\S/
226         ? $self->error_message($response{'r_error'})
227         : $self->error_message($response{'r_approved'}); # no r_error for checks
228     }
229
230 }
231
232 1;
233 __END__
234
235 =head1 NAME
236
237 Business::OnlinePayment::LinkPoint - LinkPoint (Cardservice) backend for Business::OnlinePayment
238
239 =head1 SYNOPSIS
240
241   use Business::OnlinePayment;
242
243   my $tx = new Business::OnlinePayment( 'LinkPoint',
244     'storename' => 'your_store_number',
245     'keyfile'   => '/path/to/keyfile.pem',
246   );
247
248   $tx->content(
249       type           => 'VISA',
250       action         => 'Normal Authorization',
251       description    => 'Business::OnlinePayment test',
252       amount         => '49.95',
253       invoice_number => '100100',
254       customer_id    => 'jsk',
255       name           => 'Jason Kohles',
256       address        => '123 Anystreet',
257       city           => 'Anywhere',
258       state          => 'UT',
259       zip            => '84058',
260       email          => 'ivan-linkpoint@420.am',
261       card_number    => '4007000000027',
262       expiration     => '09/99',
263   );
264   $tx->submit();
265
266   if($tx->is_success()) {
267       print "Card processed successfully: ".$tx->authorization."\n";
268   } else {
269       print "Card was rejected: ".$tx->error_message."\n";
270   }
271
272 =head1 SUPPORTED TRANSACTION TYPES
273
274 =head2 Visa, MasterCard, American Express, JCB, Discover/Novus, Carte blanche/Diners Club
275
276 =head1 DESCRIPTION
277
278 For detailed information see L<Business::OnlinePayment>.
279
280 =head1 COMPATIBILITY
281
282 This module implements an interface to the LinkPoint Perl Wrapper
283 http://www.linkpoint.com/product_solutions/internet/lperl/lperl_main.html
284
285 Versions 0.4 and on of this module support the LinkPoint Perl Wrapper version
286 3.5.
287
288 =head1 BUGS
289
290 =head1 AUTHOR
291
292 Ivan Kohler <ivan-linkpoint@420.am>
293
294 Based on Busienss::OnlinePayment::AuthorizeNet written by Jason Kohles.
295
296 =head1 SEE ALSO
297
298 perl(1), L<Business::OnlinePayment>.
299
300 =cut
301