0.05!
[Business-OnlinePayment-Jettis.git] / Jettis.pm
1 package Business::OnlinePayment::Jettis;
2
3 use strict;
4 use Carp;
5 use Business::OnlinePayment;
6 #use Business::CreditCard;
7 use Net::SSLeay qw( make_form post_https make_headers );
8 use URI;
9 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $DEBUG);
10
11 require Exporter;
12
13 @ISA = qw(Exporter AutoLoader Business::OnlinePayment);
14 @EXPORT = qw();
15 @EXPORT_OK = qw();
16 $VERSION = '0.05';
17
18 $DEBUG = 0;
19
20 my %error = (
21     0 => "Success",
22     1 => "Missing Input",
23     2 => "Missing CC Number",
24     3 => "Missing First Name",
25     4 => "Missing Last Name",
26     5 => "Missing Zip Code",
27     6 => "Missing Expiration Month",
28     7 => "Missing Expiration Year",
29     8 => "Missing Username",
30     9 => "Missing V Password",
31    10 => "No Agree Terms",
32    11 => "No Agree Age",
33    12 => "Missing City",
34    13 => "Missing State",
35    14 => "Username Length",
36    15 => "Username Invalid Chars",
37    16 => "Password Length",
38    17 => "Different V Password",
39    18 => "Same V Password",
40    19 => "Invalid Email",
41    20 => "Missing Address",
42    21 => "Invalid Phone Number",
43    22 => "Failed Mod 10",
44    23 => "Invalid Expiration",
45    24 => "Negative Database",
46    25 => "Missing Data",
47    26 => "Database Error",
48    27 => "Username Exists",
49    28 => "Invalid Store Params",
50    29 => "Unknown",
51    30 => "Procedure",
52    31 => "CC Number",
53    32 => "Invalid Version",
54    33 => "Invalid Country Code",
55    34 => "Country Bin",
56    35 => "High Fraud Country",
57    36 => "Different Country IP",
58    37 => "No Account",
59    38 => "IP Fraud",
60    39 => "Password Maintenance",
61    40 => "Password Invalid Chars",
62    41 => "Duplicate Membership",
63    42 => "Velocity",
64    43 => "Too many consecutive errors",
65    44 => "Missing Password",
66    45 => "Zip Code Quotes",
67    46 => "State Quotes",
68    47 => "Street Quotes",
69    48 => "Missing Country",
70    49 => "Country Invalid Chars",
71    50 => "Missing Quantity",
72    51 => "Quantity Invalid Chars", 
73    52 => "Missing IP Address",
74    53 => "Invalid IP Address",  
75    54 => "Merchant Text Area Quotes",
76    55 => "First Name Length",
77    56 => "Last Name Length",
78    57 => "Zip Length",
79    58 => "City Length",
80    59 => "State Length",
81    60 => "Email Length",
82    61 => "Address Length",
83    62 => "Merch Area Length",
84    63 => "Quantity Limit Per Day Exceeded",
85    64 => "Amount Limit Per Day Exceeded",
86    65 => "Quantity Limit Per Month Exceeded",
87    66 => "Amount Limit Per Month Exceeded",
88    67 => "Credit CC Num Mismatch",
89    68 => "Credit Price Mismatch",
90    69 => "Merch ID Mismatch",
91    70 => "Credit Prod ID Mismatch",
92    71 => "Invalid Bill Item ID",
93    72 => "Invalid Prod ID",
94    73 => "Invalid Merch ID",
95    74 => "Fraud Scrubbing", 
96    75 => "Already Credited",
97    76 => "Credit Card BIN Exclusion",
98    77 => "Email Exclusion",
99    78 => "IP not reversible",
100    79 => "Invalid Bill ID",
101    80 => "Auth already settled",
102    81 => "Invalid Account Num",
103    82 => "Mail Zip Code Exclusion",
104    83 => "Missing IP Code",
105    84 => "Username Mismatch",
106    85 => "Password Mismatch",
107   101 => "Bank Timeout",
108   102 => "Invalid Request",
109   103 => "Incomplete",
110   104 => "Memory Allocation",
111   105 => "Bugcheck",
112   106 => "Inhibited",
113   108 => "Reject",
114   110 => "CC Number",
115   111 => "Expiration Date",
116   112 => "Prefix",
117   113 => "Amount",
118   114 => "Linkdown",
119   115 => "SENO",
120   116 => "Merchant Number",
121   117 => "Request",
122   118 => "Merchant Bank Down",
123   119 => "Invalid Transaction Type",
124   120 => "Call Center",
125   121 => "Pickup",
126   122 => "Declined",
127   123 => "Account Declined",
128   124 => "Fraud Alert",
129   125 => "Overlimit",
130   126 => "Too Small",
131   127 => "Pin Error",
132   128 => "Card Expired",
133   129 => "Bank Invalid Email",
134   130 => "Batch Unbalanced",
135   131 => "Batch Unopened",
136   140 => "Control Invalid",
137   141 => "Control Readonly",
138   142 => "Control Bad",
139   150 => "Duplicate Address",
140   151 => "Unknown Address",
141   160 => "Duplicate Merchant Number",
142   161 => "Merchant Busy",
143   162 => "Merchant Inhibit",
144   170 => "AVS",
145   171 => "AVS Unmatched Void",
146   172 => "AVS Void Failure",
147   180 => "Invalid IP code",
148   181 => "Invalid CVV2",
149   182 => "Invalid Original Transaction Date",
150   198 => "Server Timeout",
151   199 => "Unrecognized",
152   300 => "Re-Presented Check",
153   301 => "Invalid ID",
154   400 => "Failed Routing Mod 10",
155   401 => "Missing Bank Name",
156   402 => "Bank Name Quotes",
157   403 => "Missing Bank Account Number",
158   404 => "Invalid Bank Account Number", 
159   405 => "Missing Bank Routing Number",
160   406 => "Invalid Bank Routing Number",  
161   407 => "Missing Check Number",
162   408 => "Unsupported Transaction Type",
163   409 => "Invalid Bank Name",
164   410 => "ACH Verification Declined",
165   435 => "Missing Drivers License",
166   436 => "Missing Drivers License State",
167   437 => "Invalid Drivers License",
168   999 => "Unknown Error",
169 );
170
171 sub set_defaults {
172     my $self = shift;
173     $self->server('join.billingservices.com');
174     $self->port('443');
175     $self->path('/psys/txnUrl');
176     $self->build_subs(qw( product_id merchant_id order_number ));
177 }
178
179 sub revmap_fields {
180     my($self, %map) = @_;
181     my %content = $self->content();
182
183     # ACTION MAP
184     my %actions = ('normal authorization' => 'PURCHASE',
185                    'credit'               => 'CREDIT',
186                    );
187     $content{'action'} = $actions{lc($content{'action'})}
188       if exists( $actions{lc($content{'action'})} );
189
190     if ($content{'customer_ssn'} =~ /^(\d{3})-(\d{2})-(\d{4})$/)
191     {
192         $content{'ssn4'} = $3;
193     }
194
195     foreach(keys %map) {
196         $content{$_} = ref($map{$_})
197                          ? ${ $map{$_} }
198                          : $content{$map{$_}};
199     }
200     $self->content(%content);
201 }
202
203 sub submit {
204     my $self = shift;
205     my %content = $self->content();
206
207     my $type = lc($content{'type'});
208     if ( $type =~ /^e?check$/ ) {
209     } else {
210       croak "$type not (yet) supported";
211     }
212
213     my $action = $content{'action'};
214     croak "$action not currently supported"
215       unless $action =~ /^(PURCHASE|CREDIT)$/;
216
217     $self->revmap_fields(
218         SUCCESS_URL     => \'https://secure.suicidegirls.com/',
219         PS_TXN_TYPE     => 'action',
220         PRODUCT_ID      => \($self->product_id()),
221         MERCHANT_ID     => 'login',
222         VERSION         => \'1.0',
223         SOR             => \'N',
224         REMOTE_ADDR     => \'10.0.0.1',
225         TERMS_AGREE     => \'Y',
226         CHECK_AGE       => \'Y',
227         PAY_METHOD_ID   => \'A', # ACH
228         PRICE           => 'amount',
229         QTY             => \'1',
230         CHECK_NUM       => \'1000',
231         BANK_ACCT_NUM   => 'account_number',
232         BANK_ROUT_NUM   => 'routing_code',
233         BANK_NAME       => 'bank_name',
234         EMAIL           => 'email',
235         FIRST_NAME      => 'first_name',
236         LAST_NAME       => 'last_name',
237         SSN4            => 'ssn4',
238         ADDR_STREET_1   => 'address',
239         ADDR_CITY       => 'city',
240         ADDR_STATE      => 'state',
241         ADDR_ZIP        => 'zip',
242         ADDR_COUNTRY    => \'840', # US
243         MERCH_TEXT_AREA => 'description',
244         BILL_ITEM_ID    => 'order_number',
245         DL_NUM          => \'B01111111111', #XXX state_id (license_num?)
246         DL_STATE        => 'state',         #XXX state_id_state
247     );
248
249     my %post_data = $self->get_fields(qw(
250         SUCCESS_URL PS_TXN_TYPE PRODUCT_ID MERCHANT_ID VERSION SOR REMOTE_ADDR
251         TERMS_AGREE CHECK_AGE PAY_METHOD_ID PRICE QTY CHECK_NUM BANK_ACCT_NUM
252         BANK_ROUT_NUM BANK_NAME EMAIL FIRST_NAME LAST_NAME SSN4 ADDR_STREET_1
253         ADDR_CITY ADDR_STATE ADDR_ZIP ADDR_COUNTRY MERCH_TEXT_AREA BILL_ITEM_ID
254         DL_NUM DL_STATE
255     ));
256
257     my $pd = make_form(%post_data);
258     my $s = $self->server();
259     my $p = $self->port();
260     my $t = $self->path();
261     my $headers = make_headers('Referer' => $content{'referer'} );
262     my($page,$server_response,%headers) = post_https($s,$p,$t,$headers,$pd);
263
264 #    warn join('-',%headers);
265
266     my $uri = new URI $headers{'LOCATION'} or die "no LOCATION: header!";
267     my %response = $uri->query_form or die "no response in LOCATION: header!";
268
269     if ( $response{'RESULT_MAIN'} eq '0' ) {
270       $self->is_success(1);
271       $self->result_code('0');
272       $self->authorization($response{'AUTHORIZATION_CODE'});
273       $self->order_number($response{'BILL_ITEM_ID'});
274     } else {
275       $self->is_success(0);
276       $self->result_code($response{'RESULT_MAIN'});
277       $self->error_message($error{$response{'RESULT_MAIN'}});
278     }
279
280 }
281
282 1;
283
284 __END__
285
286 =head1 NAME
287
288 Business::OnlinePayment::Jettis - Jettis backend for Business::OnlinePayment
289
290 =head1 SYNOPSIS
291
292   use Business::OnlinePayment;
293
294   my $tx = new Business::OnlinePayment("Jettis");
295   $tx->content(
296       type           => 'ECHECK',
297       login          => 'test', #ClientID
298       action         => 'Normal Authorization',
299       description    => 'Business::OnlinePayment test',
300       amount         => '49.95',
301       invoice_number => '100100',
302       name           => 'Tofu Beast',
303       account_number => '12345',
304       routing_code   => '123456789',
305       bank_name      => 'First National Test Bank',
306   );
307   $tx->submit();
308
309   if($tx->is_success()) {
310       print "Check processed successfully: ".$tx->authorization."\n";
311   } else {
312       print "Check was rejected: ".$tx->error_message."\n";
313   }
314
315 =head1 DESCRIPTION
316
317 For detailed information see L<Business::OnlinePayment>.
318
319 =head1 NOTE
320
321 This module only implements 'ECHECK' (ACH) functionality at this time.  Credit
322 card transactions are not (yet) supported.
323
324 =head1 COMPATIBILITY
325
326 This module implements an interface to Jettis.com's HTTPS API.  Unfortunately,
327 no documentation is publicly available.  Jettis won't even send their full
328 manual to their customers - they insist on sending only few-page snippets at a
329 time.
330
331 =head1 AUTHOR
332
333 Steve Simitzis <steve@saturn5.com>
334 Ivan Kohler <ivan-jettis@420.am>
335
336 =head1 SEE ALSO
337
338 perl(1). L<Business::OnlinePayment>
339
340 =cut
341