Fixed bug that broke expiration dates >2009.
[Business-OnlinePayment-Skipjack.git] / Skipjack.pm
1 ## Business::OnlinePayment::Skipjack
2 ##
3 ## Original Skipjack.pm developed by New York Connect Net (http://nyct.net)
4 ## Michael Bacarella <mbac@nyct.net>
5 ##
6 ## Modified for GetCareer.com by Slipstream.com
7 ## Troy Davis <troy@slipstream.com>
8 ##
9 ## 'Adapted' (completely rewritten) for Business::OnlinePayment 
10 ## by Fire2Wire Internet Services (http://www.fire2wire.com)
11 ## Mark Wells <mark@pc-intouch.com>
12 ## Kristian Hoffmann <khoff@pc-intouch.com>
13 ## James Switzer <jamess@fire2wire.com>
14
15 ## Required packages:
16 ## Net::SSLeay
17 ## Text::CSV
18 ## Business::OnlinePayment
19
20
21 package Business::OnlinePayment::Skipjack;
22
23 use strict;
24 use Business::OnlinePayment;
25 use Net::SSLeay qw(post_https make_form);
26 use Text::CSV;
27 use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION);
28
29 $VERSION="0.1.1";
30
31 require Exporter;
32
33 @ISA = qw(Exporter AutoLoader Business::OnlinePayment);
34 # Items to export into callers namespace by default. Note: do not export
35 # names by default without a very good reason. Use EXPORT_OK instead.
36 # Do not simply export all your public functions/methods/constants.
37 @EXPORT = qw();
38 @EXPORT_OK = qw();
39
40 my %CC_ERRORS = (
41         '-1'    =>      'Invalid length (-1)',
42         '-35'   =>      'Invalid credit card number (-35)',
43         '-37'   =>      'Failed communication (-37)',
44         '-39'   =>      'Serial number is too short (-39)',
45         '-51'   =>      'The zip code is invalid',
46         '-52'   =>      'The shipto zip code is invalid',
47         '-53'   =>      'Length of expiration date (-53)',
48         '-54'   =>      'Length of account number date (-54)',
49         '-55'   =>      'Length of street address (-55)',
50         '-56'   =>      'Length of shipto street address (-56)',
51         '-57'   =>      'Length of transaction amount (-57)',
52         '-58'   =>      'Length of name (-58)',
53         '-59'   =>      'Length of location (-59)',
54         '-60'   =>      'Length of state (-60)',
55         '-61'   =>      'Length of shipto state (-61)',
56         '-62'   =>      'Length of order string (-62)',
57         '-64'   =>      'Invalid phone number (-64)',
58         '-65'   =>              'Empty name (-65)', 
59         '-66'   =>      'Empty email (-66)',
60         '-67'   =>      'Empty street address (-66)',
61         '-68'   =>      'Empty city (-68)',
62         '-69'   =>      'Empty state (-69)',
63         '-70'   =>      'Empty zip code (-70)',
64         '-71'   =>      'Empty order number (-71)',
65         '-72'   =>      'Empty account number (-72)',
66         '-73'   =>      'Empty expiration month (-73)',
67         '-74'   =>      'Empty expiration year (-74)',
68         '-75'   =>      'Empty serial number (-75)',
69         '-76'   =>      'Empty transaction amount (-76)',
70         '-79'   =>      'Length of customer name (-79)',
71         '-80'   =>      'Length of shipto customer name (-80)',
72         '-81'   =>      'Length of customer location (-81)',
73         '-82'   =>      'Length of customer state (-82)',
74         '-83'   =>      'Length of shipto phone (-83)',
75         '-84'   =>      'Pos Error duplicate ordernumber (-84)',
76         '-91'   =>      'Pos Error CVV2 (-91)',
77         '-92'   =>      'Pos Error Approval Code (-92)',
78         '-93'   =>      'Pos Error Blind Credits Not Allowed (-93)',
79         '-94'   =>      'Pos Error Blind Credits Failed (-94)',
80         '-95'   =>      'Pos Error Voice Authorizations Not Allowed (-95)',
81         );
82
83 my %AVS_CODES = (
84         'X' => 'Exact match, 9 digit zip', 
85         'Y' => 'Exact match, 5 digit zip', 
86         'A' => 'Address match only', 
87         'W' => '9 digit match only', 
88         'Z' => '5 digit match only', 
89         'N' => 'No address or zip match', 
90         'U' => 'Address unavailable', 
91         'R' => 'Issuer system unavailable', 
92         'E' => 'Not a mail/phone order', 
93         'S' => 'Service not supported' 
94         );
95
96 my %FIELDS = (
97         name    => 'sjname',
98         email   => 'Email',
99         address => 'Streetaddress',
100         city    => 'City',
101         state   => 'State',
102         zip     => 'Zipcode',
103         invoice_number  => 'Ordernumber',
104         card_number     => 'Accountnumber',
105         exp_month       => 'Month',
106         exp_year        => 'Year',
107         amount  => 'Transactionamount',
108         orderstring     => 'Orderstring',
109         phone   => 'Shiptophone',
110         login   => 'Serialnumber',
111         );
112
113
114 sub _gen_ordernum { return int(rand(4000000000)); }
115
116 sub set_defaults
117 {
118   my $self = shift;
119
120   # For production
121   $self->server('www.skipjackic.com');
122   $self->path('/scripts/evolvcc.dll?AuthorizeAPI');
123
124   # For auth testing
125   #$self->server('developer.skipjackic.com'); # test mode
126   #$self->path('/scripts/evolvcc.dll?AuthorizeAPI');
127
128   # For comms testing
129   #$self->server('www.skipjackic.com');
130   #$self->path('/secure/echo.asp');
131
132   $self->port(443);
133
134   return;
135 }
136
137
138 sub submit
139 {
140   my $self = shift;
141   my %c = $self->content;
142   my (%input, %output);
143   my ($page, $response, %reply_headers);
144
145 #  if($c{type} and $c{type} ne 'normal authorization') {
146   if($c{type} eq 'credit') {
147     warn 'Business::OnlinePayment::Skipjack does not support "' . 
148         $c{type}. '"transactions';
149     return;
150   }
151
152   # FIXME!
153   # This should be set from %processor_info.
154
155   # Or, if we want, from $c{login}.
156
157   $c{expiration} =~ /(\d\d?)\D*(\d\d?)/; # Slightly less crude way to extract the exp date.
158   $c{exp_month} = sprintf('%02d',$1);
159   $c{exp_year} = sprintf('%02d',$2);
160
161   $c{invoice_number} = _gen_ordernum unless $c{invoice_number};
162
163   $c{orderstring} = '0~'.$c{description}.'~'.$c{amount}.'~1~N~||'
164       unless $c{orderstring};
165
166   %input = map { ($FIELDS{$_} || $_), $c{$_} } keys(%c);
167
168   ($page, $response, %reply_headers) = 
169       post_https($self->server,
170                  $self->port,
171                  $self->path,
172                  '',
173                  make_form(%input));
174
175
176   %output = parse_Authorize_API($page);
177
178   $self->{_result} = \%output;
179   $self->authorization($output{'AUTHCODE'});
180   return;
181 }
182
183 sub is_success
184 {
185   my $self = shift;
186
187   return ($self->{_result}->{'szIsApproved'} == 1);
188 }
189
190 sub error_message
191 {
192   my $self = shift;
193   my $r;
194
195   if($self->is_success) { return ''; }
196   if(($r = $self->{_result}->{'szReturnCode'}) < 0) { return $CC_ERRORS{$r}; }
197   if($r = $self->{_result}->{'szAVSResponseMessage'}) { return $r; }
198   if($r = $self->{_result}->{'szAuthorizationDeclinedMessage'}) { return $r; }
199 }
200
201 sub parse_Authorize_API
202 {
203
204   my $page = shift;
205   my %output;
206   my $csv_keys = new Text::CSV;
207   my $csv_values = new Text::CSV;
208
209   my ($keystring, $valuestring) = split(/\r\n/, $page);
210   $csv_keys->parse($keystring);
211   $csv_values->parse($valuestring);
212   @output{$csv_keys->fields()} = $csv_values->fields();
213
214   return %output;
215
216 }
217
218 __END__
219
220 1;
221