bryan d foy said to "try quoting the version number." in PREREQ_PM. thanks!
[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.01';
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   999 => "Unknown Error",
165 );
166
167 sub set_defaults {
168     my $self = shift;
169     $self->server('join.billingservices.com');
170     $self->port('443');
171     $self->path('/psys/txnUrl');
172     $self->build_subs(qw( product_id merchant_id ));
173 }
174
175 sub revmap_fields {
176     my($self, %map) = @_;
177     my %content = $self->content();
178     foreach(keys %map) {
179         $content{$_} = ref($map{$_})
180                          ? ${ $map{$_} }
181                          : $content{$map{$_}};
182     }
183     $self->content(%content);
184 }
185
186 sub submit {
187     my $self = shift;
188     my %content = $self->content();
189
190     my $action = lc($content{'action'});
191     if ( $action eq 'normal authorization' ) {
192     } else {
193       croak "$action not (yet) supported";
194     }
195     
196     my $type = lc($content{'type'});
197     if ( $type eq 'echeck' ) {
198     } else {
199       croak "$type not (yet) supported";
200     }
201
202     $self->revmap_fields(
203         SUCCESS_URL     => \'https://secure.suicidegirls.com/',
204         PRODUCT_ID      => \($self->product_id()),
205         MERCHANT_ID     => 'login',
206         VERSION         => \'1.0',
207         SOR             => \'Y',
208         REMOTE_ADDR     => \'10.0.0.1',
209         TERMS_AGREE     => \'Y',
210         CHECK_AGE       => \'Y',
211         PAY_METHOD_ID   => \'A', # ACH
212         PRICE           => 'amount',
213         QTY             => \'1',
214         CHECK_NUM       => \'1000',
215         BANK_ACCT_NUM   => 'account_number',
216         BANK_ROUT_NUM   => 'routing_code',
217         BANK_NAME       => 'bank_name',
218         EMAIL           => 'email',
219         FIRST_NAME      => 'first_name',
220         LAST_NAME       => 'last_name',
221         ADDR_STREET_1   => 'address',
222         ADDR_CITY       => 'city',
223         ADDR_STATE      => 'state',
224         ADDR_ZIP        => 'zip',
225         ADDR_COUNTRY    => \'840', # US
226         MERCH_TEXT_AREA => 'description',
227     );
228
229     my %post_data = $self->get_fields(qw(
230         SUCCESS_URL PRODUCT_ID MERCHANT_ID VERSION SOR REMOTE_ADDR
231         TERMS_AGREE CHECK_AGE PAY_METHOD_ID PRICE QTY CHECK_NUM BANK_ACCT_NUM
232         BANK_ROUT_NUM BANK_NAME EMAIL FIRST_NAME LAST_NAME ADDR_STREET_1
233         ADDR_CITY ADDR_STATE ADDR_ZIP ADDR_COUNTRY MERCH_TEXT_AREA
234     ));
235
236     my $pd = make_form(%post_data);
237     my $s = $self->server();
238     my $p = $self->port();
239     my $t = $self->path();
240     my $headers = make_headers('Referer' => $content{'referer'} );
241     my($page,$server_response,%headers) = post_https($s,$p,$t,$headers,$pd);
242
243 #    warn join('-',%headers);
244
245     my $uri = new URI $headers{'LOCATION'} or die "no LOCATION: header!";
246     my %response = $uri->query_form or die "no response in LOCATION: header!";
247
248     if ( $response{'RESULT_MAIN'} eq '0' ) {
249       $self->is_success(1);
250       $self->result_code('0');
251       $self->authorization($response{'AUTHORIZATION_CODE'});
252     } else {
253       $self->is_success(0);
254       $self->result_code($response{'RESULT_MAIN'});
255       $self->error_message($error{$response{'RESULT_MAIN'}});
256     }
257
258 }
259
260 1;
261 __END__
262
263 =head1 NAME
264
265 Business::OnlinePayment::Jettis - Jettis backend for Business::OnlinePayment
266
267 =head1 SYNOPSIS
268
269   use Business::OnlinePayment;
270
271   my $tx = new Business::OnlinePayment("Jettis");
272   $tx->content(
273       type           => 'ECHECK',
274       login          => 'test', #ClientID
275       action         => 'Normal Authorization',
276       description    => 'Business::OnlinePayment test',
277       amount         => '49.95',
278       invoice_number => '100100',
279       name           => 'Tofu Beast',
280       card_number    => '4007000000027',
281       expiration     => '09/02',
282   );
283   $tx->submit();
284
285   if($tx->is_success()) {
286       print "Check processed successfully: ".$tx->authorization."\n";
287   } else {
288       print "Check was rejected: ".$tx->error_message."\n";
289   }
290
291 =head1 DESCRIPTION
292
293 For detailed information see L<Business::OnlinePayment>.
294
295 =head1 NOTE
296
297 This module only implements 'ECHECK' (ACH) functionality at this time.  Credit
298 card transactions are not (yet) supported.
299
300 =head1 COMPATIBILITY
301
302 This module implements an interface to Jettis.com's HTTPS API.  Unfortunately,
303 no documentation is publicly available.  Jettis won't even send their full
304 manual to their customers - they insist on sending only few-page snippets at a
305 time.
306
307 =head1 AUTHOR
308
309 Steve Simitzis <steve@saturn5.com>
310 Ivan Kohler <ivan-jettis@420.am>
311
312 =head1 SEE ALSO
313
314 perl(1). L<Business::OnlinePayment>
315
316 =cut
317