* Don't autovifivy @fields array entries.
[interchange.git] / lib / Vend / Payment / iTransact.pm
1 # Vend::Payment::iTransact - Interchange iTransact Support
2 #
3 # $Id: iTransact.pm,v 2.9 2009-03-16 19:34:01 jon Exp $
4 #
5 # Copyright (C) 2002-2007 Interchange Development Group
6 # Copyright (C) 1999-2002 Red Hat, Inc.
7 #
8 # Written by Cameron Prince and Mark Johnson, based on code by Mike Heins.
9
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public
21 # License along with this program; if not, write to the Free
22 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
23 # MA  02110-1301  USA.
24
25 package Vend::Payment::iTransact;
26
27 =head1 NAME
28
29 Vend::Payment::iTransact - Interchange iTransact Support
30
31 =head1 SYNOPSIS
32
33     &charge=itransact
34  
35         or
36  
37     [charge mode=itransact param1=value1 param2=value2]
38
39 =head1 PREREQUISITES
40
41   Net::SSLeay
42  
43     or
44   
45   LWP::UserAgent and Crypt::SSLeay
46
47 Only one of these need be present and working.
48
49 =head1 DESCRIPTION
50
51 The Vend::Payment::iTransact module implements the itransact() routine
52 for use with Interchange. It is compatible on a call level with the other
53 Interchange payment modules.
54
55 To enable this module, place this directive in C<interchange.cfg>:
56
57     Require module Vend::Payment::iTransact
58
59 This I<must> be in interchange.cfg or a file included from it.
60
61 Make sure CreditCardAuto is off (default in Interchange demos).
62
63 The mode can be named anything, but the C<gateway> parameter must be set
64 to C<itransact>. To make it the default payment gateway for all credit
65 card transactions in a specific catalog, you can set in C<catalog.cfg>:
66
67     Variable   MV_PAYMENT_MODE  itransact
68
69 It uses several of the standard settings from Interchange payment. Any time
70 we speak of a setting, it is obtained either first from the tag/call options,
71 then from an Interchange order Route named for the mode, then finally a
72 default global payment variable, For example, the C<id> parameter would
73 be specified by:
74
75     [charge mode=itransact id=YouriTransact]
76
77 or
78
79     Route itransact id YouriTransactID
80
81 or 
82
83     Variable MV_PAYMENT_ID      YouriTransactID
84
85 The active settings are:
86
87 =over 4
88
89 =item id
90
91 Your iTransact account ID, supplied by iTransact when you sign up.
92 Global parameter is MV_PAYMENT_ID.
93
94 =item home_page
95
96 The internet address of your site. Defaults to C<http://__SERVER_NAME__> if
97 not set. Global parameter is MV_PAYMENT_HOME_PAGE.
98
99 =item remap 
100
101 This remaps the form variable names to the ones needed by iTransact. See
102 the C<Payment Settings> heading in the Interchange documentation for use.
103
104 =back
105
106 =head2 Troubleshooting
107
108 Try the instructions above, then enable test mode. A test order should complete.
109
110 Then move to live mode and try a sale with the card number C<4111 1111 1111 1111>
111 and a valid expiration date. The sale should be denied, and the reason should
112 be in [data session payment_error].
113
114 If nothing works:
115
116 =over 4
117
118 =item *
119
120 Make sure you "Require"d the module in interchange.cfg:
121
122     Require module Vend::Payment::iTransact
123
124 =item *
125
126 Make sure either Net::SSLeay or Crypt::SSLeay and LWP::UserAgent are installed
127 and working. You can test to see whether your Perl thinks they are:
128
129     perl -MNet::SSLeay -e 'print "It works\n"'
130
131 or
132
133     perl -MLWP::UserAgent -MCrypt::SSLeay -e 'print "It works\n"'
134
135 If either one prints "It works." and returns to the prompt you should be OK
136 (presuming they are in working order otherwise).
137
138 =item *
139
140 Check the error logs, both catalog and global.
141
142 =item *
143
144 Make sure you set your account ID properly.  
145
146 =item *
147
148 Try an order, then put this code in a page:
149
150     <XMP>
151     [calc]
152         my $string = $Tag->uneval( { ref => $Session->{payment_result} });
153         $string =~ s/{/{\n/;
154         $string =~ s/,/,\n/g;
155         return $string;
156     [/calc]
157     </XMP>
158
159 That should show what happened.
160
161 =item *
162
163 If all else fails, consultants are available to help with integration for a fee.
164 See http://www.icdevgroup.org/
165
166 =back
167
168 =head1 BUGS
169
170 There is actually nothing *in* Vend::Payment::iTransact. It changes packages
171 to Vend::Payment and places things there.
172
173 =head1 AUTHORS
174
175 Mark Johnson and Cameron Prince, based on original code by Mike Heins.
176
177 =cut
178
179 BEGIN {
180
181         my $selected;
182         eval {
183                 package Vend::Payment;
184                 require Net::SSLeay;
185                 import Net::SSLeay qw(post_https make_form make_headers);
186                 $selected = "Net::SSLeay";
187         };
188
189         $Vend::Payment::Have_Net_SSLeay = 1 unless $@;
190
191         unless ($Vend::Payment::Have_Net_SSLeay) {
192
193                 eval {
194                         package Vend::Payment;
195                         require LWP::UserAgent;
196                         require HTTP::Request::Common;
197                         require Crypt::SSLeay;
198                         import HTTP::Request::Common qw(POST);
199                         $selected = "LWP and Crypt::SSLeay";
200                 };
201
202                 $Vend::Payment::Have_LWP = 1 unless $@;
203                 
204         }
205
206         unless ($Vend::Payment::Have_Net_SSLeay or $Vend::Payment::Have_LWP) {
207                 die __PACKAGE__ . " requires Net::SSLeay or Crypt::SSLeay";
208         }
209
210         ::logGlobal("%s payment module initialized, using %s", __PACKAGE__, $selected)
211                 unless $Vend::Quiet or ! $Global::VendRoot;
212
213 }
214
215 package Vend::Payment;
216
217 sub itransact {
218         my ($opt, $amount) = @_;
219
220         my $user = $opt->{id} || charge_param('id');
221
222         my $company = $opt->{company} || "$::Variable->{COMPANY} Order";
223
224         my %actual;
225         if($opt->{actual}) {
226                 %actual = %{$opt->{actual}};
227         }
228         else {
229                 %actual = map_actual();
230         }
231
232         $actual{mv_credit_card_exp_month} =~ s/\D//g;
233         $actual{mv_credit_card_exp_month} =~ s/^0+//;
234         $actual{mv_credit_card_exp_year} =~ s/\D//g;
235
236         my $exp_year = $actual{mv_credit_card_exp_year};
237         $exp_year += 2000 unless $exp_year =~ /\d{4}/;
238
239         $actual{mv_credit_card_number} =~ s/\D//g;
240
241         my @month = (qw/January
242                                    February
243                                    March
244                                    April
245                                    May
246                                    June
247                                    July
248                                    August
249                                    September
250                                    October
251                                    November
252                                    December/);
253
254         my $exp_month = @month[$actual{mv_credit_card_exp_month} - 1];
255         my $precision = $opt->{precision} || charge_param('precision') || 2;
256    
257         $amount = "$opt->{total_cost}" || undef;
258
259     if(! $amount) {
260                         $amount = Vend::Interpolate::total_cost();
261                         $amount = Vend::Util::round_to_frac_digits($amount,$precision);
262     }
263
264         my $gen;
265         if($opt->{test} and $gen = charge_param('generate_error') ) {
266 #::logDebug("trying to generate error");
267                 $actual{mv_credit_card_number} = '4111111111111112'
268                         if $gen =~ /number/;
269                 $exp_year = '2000'
270                         if $gen =~ /date/;
271         }
272
273         my %values = (
274                                   vendor_id   =>   $user,
275                                   ret_addr    =>   "success",
276                                   '1-qty'     =>   1,
277                                   '1-desc'    =>   $company,
278                                   '1-cost'        =>   $amount,
279                                   first_name  =>   $actual{b_fname},
280                                   last_name   =>   $actual{b_lname},
281                                   address     =>   $actual{b_address},
282                                   city        =>   $actual{b_city},
283                                   state       =>   $actual{b_state},
284                                   zip         =>   $actual{b_zip},
285                                   country     =>   $actual{b_country},
286                                   phone       =>   $actual{phone_day},
287                                   email       =>   $actual{email},
288                                   ccnum       =>   $actual{mv_credit_card_number},
289                                   ccmo        =>   $exp_month,
290                                   ccyr        =>   $exp_year,
291                                   ret_mode    =>   "redirect",
292                                  );
293
294         my $hp = $opt->{home_page}
295                   || charge_param('home_page')
296                   || $::Variable->{SERVER_NAME};
297         $hp = "http://$hp" unless $hp =~ /^\w+:/;
298         $values{home_page} = $hp;
299
300         $opt->{submit_url} ||= 'https://secure.itransact.com/cgi-bin/rc/ord.cgi';
301
302         my %result;
303
304 #::logDebug("sending query: " . ::uneval(\%values));
305
306         my $thing         = post_data($opt, \%values);
307         my $header_string = $thing->{header_string};
308         my $result_page   = $thing->{result_page};
309
310         ## check for errors
311         my $error;
312
313 #::logDebug("request returned: $result_page");
314
315         if ($header_string =~ m/^Location: success/mi) {
316                 $result{MStatus} = 'success';
317                 $result{'order-id'} = $opt->{order_id};
318         }
319         else {
320                 if ($result_page =~ m/BEGIN ERROR DESCRIPTION --\>(.*)\<\!-- END ERROR DESCRIPTION/s) {
321                   $error = $1;
322                   $error =~ s/\<.*?\>//g;
323                   $error =~ s/[^-A-Za-z_0-9 ]//g;
324                 }
325                 elsif($result_page =~ m{<title>\s*error\s*</title>}i) {
326                   $error = $result_page;
327                   $error =~ s/.*?<body.*?>//is;
328                   $error =~ s/<.*?>//g;
329                   $error =~ s/\s+/ /g;
330                   $error =~ s/\s+$//;
331                   $error =~ s/^\s+$//;
332                 }
333                 else {
334                   ## something very bad happened
335                   $error = ::errmsg("Unknown error");
336                 }
337
338 #::logDebug("iTransact Error: " . $error);
339                 $result{MStatus} = 'denied';
340                 $result{MErrMsg} = $error;
341         }
342
343         return %result;
344
345 }
346
347 package Vend::Payment::iTransact;
348
349 1;