* Add enclair_db option to UserDB.pm. Allows logging of enclair password
[interchange.git] / lib / Vend / Payment / TCLink.pm
1 # Vend::Payment::TCLink - Interchange TrustCommerce TCLink support
2 #
3 # $Id: TCLink.pm,v 1.10 2009-03-16 19:34:01 jon Exp $
4 #
5 # Copyright (C) 2002-2007 Interchange Development Group
6 # Copyright (C) 2002 TrustCommerce <developer@trustcommerce.com>
7 #
8 # by Dan Helfman <dan@trustcommerce.com> with code reused and inspired by
9 #       Mark Stosberg <mark@summersault.com>
10 #       Mike Heins
11 #       webmaster@nameastar.net
12 #       Jeff Nappi <brage@cyberhighway.net>
13 #       Paul Delys <paul@gi.alaska.edu>
14
15 # This program is free software; you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation; either version 2 of the License, or
18 # (at your option) any later version.
19 #
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 # GNU General Public License for more details.
24 #
25 # You should have received a copy of the GNU General Public
26 # License along with this program; if not, write to the Free
27 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
28 # MA  02110-1301  USA.
29
30 package Vend::Payment::TCLink;
31
32 =head1 NAME
33
34 Vend::Payment::TCLink - Interchange TrustCommerce Support
35
36 =head1 SYNOPSIS
37
38     &charge=trustcommerce
39  
40         or
41  
42     [charge mode=trustcommerce param1=value1 param2=value2]
43
44 =head1 PREREQUISITES
45
46     Net::TCLink
47
48 http://www.trustcommerce.com/tclink.html and CPAN both have this module,
49 which actually does the bulk of the work. All that Vend::Payment::TCLink
50 does is to massage the payment data between Interchange and Net::TCLink.
51
52 =head1 DESCRIPTION
53
54 The Vend::Payment::TCLink module implements the trustcommerce() routine
55 for use with Interchange. It is compatible on a call level with the other
56 Interchange payment modules.
57
58 To enable this module, place this directive in C<interchange.cfg>:
59
60     Require module Vend::Payment::TCLink
61
62 This I<must> be in interchange.cfg or a file included from it.
63
64 Make sure CreditCardAuto is off (default in Interchange demos).
65
66 The mode can be named anything, but the C<gateway> parameter must be set
67 to C<trustcommerce>. To make it the default payment gateway for all credit
68 card transactions in a specific catalog, you can set in C<catalog.cfg>:
69
70     Variable MV_PAYMENT_MODE trustcommerce
71
72 It uses several of the standard settings from Interchange payment. Any time
73 we speak of a setting, it is obtained either first from the tag/call options,
74 then from an Interchange order Route named for the mode, then finally a
75 default global payment variable, For example, the C<id> parameter would
76 be specified by:
77
78     [charge mode=trustcommerce id=YourTrustCommerceID]
79
80 or
81
82     Route trustcommerce id YourTrustCommerceID
83
84 or 
85
86     Variable TRUSTCOMMERCE_ID YourTrustCommerceID
87
88 The active settings are:
89
90 =over 4
91
92 =item id
93
94 Your TrustCommerce customer ID, supplied by TrustCommerce when you sign up.
95 Global parameter is TRUSTCOMMERCE_ID.
96
97 =item secret
98
99 Your TrustCommerce customer password, supplied by TrustCommerce when you
100 sign up. Global parameter is TRUSTCOMMERCE_SECRET.
101
102 =item transaction
103
104 The type of transaction to be run. Valid values are:
105
106     Interchange         TrustCommerce
107     ----------------    -----------------
108         auth            preauth
109         return          credit
110         sale            sale
111         settle          postauth
112
113 Global parameter is TRUSTCOMMERCE_ACTION.
114
115 =item avs
116
117 Whether AVS (Address Verification System) is enabled. Valid values are "y"
118 for enabled and "n" for disabled. Global parameter is TRUSTCOMMERCE_AVS.
119
120 =item remap 
121
122 This remaps the form variable names to the ones needed by TrustCommerce. See
123 the C<Payment Settings> heading in the Interchange documentation for use.
124
125 =item test
126
127 Set this to C<TRUE> if you wish to operate in test mode, i.e. set the
128 TrustCommerce C<demo> query paramter to TRUE.
129
130 Examples: 
131
132     Route trustcommerce test TRUE
133         or
134     Variable TRUSTCOMMERCE_TEST TRUE
135         or 
136     [charge mode=trustcommerce test=TRUE]
137
138 =back
139
140 =head2 Troubleshooting
141
142 Try the instructions above, then enable test mode. A test order should complete.
143
144 Disable test mode, then test in various TrustCommerce error modes by
145 using the credit card number 4222 2222 2222 2222.
146
147 Then try a sale with the card number C<4111 1111 1111 1111>
148 and a valid expiration date. The sale should be denied, and the reason should
149 be in [data session payment_error].
150
151 If nothing works:
152
153 =over 4
154
155 =item *
156
157 Make sure you "Require"d the module in interchange.cfg:
158
159     Require module Vend::Payment::TrustCommerce
160
161 =item *
162
163 Make sure Net::TCLink is installed and working. You can test to see
164 whether your Perl thinks they are:
165
166     perl -MNet::TCLink -e 'print "It works.\n"'
167
168 If it "It works." and returns to the prompt you should be OK (presuming
169 they are in working order otherwise).
170
171 =item *
172
173 Check the error logs, both catalog and global.
174
175 =item *
176
177 Make sure you set your payment parameters properly.  
178
179 =item *
180
181 Try an order, then put this code in a page:
182
183     <XMP>
184     [calc]
185         my $string = $Tag->uneval( { ref => $Session->{payment_result} });
186         $string =~ s/{/{\n/;
187         $string =~ s/,/,\n/g;
188         return $string;
189     [/calc]
190     </XMP>
191
192 That should show what happened.
193
194 =item *
195
196 If all else fails, TrustCommerce consultants are available to help you out.
197
198 See http://www.trustcommerce.com/contact.html for more information, or email
199 developer@trustcommerce.com
200
201 =back
202
203 =head1 BUGS
204
205 There is actually nothing *in* Vend::Payment::TrustCommerce. It changes
206 packages to Vend::Payment and places things there.
207
208 =head1 AUTHORS
209
210 Dan Helfman <dan@trustcommerce.com>, based on code by Mark Stosberg
211 <mark@summersault.com>, which was based on original code by Mike Heins.
212
213 =head1 CREDITS
214
215     webmaster@nameastar.net
216     Jeff Nappi <brage@cyberhighway.net>
217     Paul Delys <paul@gi.alaska.edu>
218
219 =cut
220
221 BEGIN {
222         eval {
223                 package Vend::Payment;
224                 require Net::TCLink or die __PACKAGE__ . " requires Net::TCLink";
225         };
226
227         ::logGlobal("%s payment module initialized", __PACKAGE__)
228                 unless $Vend::Quiet or ! $Global::VendRoot;
229 }
230
231 package Vend::Payment;
232
233 sub trustcommerce {
234         my ($user, $amount) = @_;
235
236         my $opt;
237         if(ref $user) {
238                 $opt = $user;
239                 $user = $opt->{id} || $::Variable->{TRUSTCOMMERCE_ID} || undef;
240                 $secret = $opt->{secret} || $::Variable->{TRUSTCOMMERCE_SECRET} || undef;
241         }
242         else {
243                 $opt = {};
244         }
245         
246         my $actual;
247         if($opt->{actual}) {
248                 $actual = $opt->{actual};
249         }
250         else {
251                 my (%actual) = map_actual();
252                 $actual = \%actual;
253         }
254
255 #::logGlobal("actual map result: " . ::uneval($actual));
256         if (! $user ) {
257                 $user    =  charge_param('id')
258                                                 or return (
259                                                         MStatus => 'failure-hard',
260                                                         MErrMsg => errmsg('No customer id'),
261                                                         );
262         }
263         
264         $secret =  charge_param('secret') if ! $secret;
265
266         my $precision = $opt->{precision} || 2;
267
268         my $referer   =  $opt->{referer}
269                                         || charge_param('referer');
270
271         $actual->{mv_credit_card_exp_month} =~ s/\D//g;
272         $actual->{mv_credit_card_exp_year} =~ s/\D//g;
273         $actual->{mv_credit_card_exp_year} =~ s/\d\d(\d\d)/$1/;
274         $actual->{mv_credit_card_number} =~ s/\D//g;
275         $actual->{b_zip} =~ s/\D//g;
276
277         my $exp = sprintf '%02d%02d', $actual->{mv_credit_card_exp_month},
278                 $actual->{mv_credit_card_exp_year};
279
280         my $transtype = $opt->{transaction} || $::Variable->{TRUSTCOMMERCE_ACTION};
281         $transtype ||= 'sale';
282
283         my %type_map = (
284                 auth                    =>      'preauth',
285                 authorize               =>      'preauth',
286                 mauthcapture            =>      'sale',
287                 mauthonly               =>      'preauth',
288                 return                  =>      'credit',
289                 sale                    =>      'sale',
290                 settle                  =>      'postauth',
291                 settle_prior    =>      'postauth',
292         );
293         
294         if (defined $type_map{$transtype}) {
295                 $transtype = $type_map{$transtype};
296         }
297
298         $amount = $opt->{total_cost} if $opt->{total_cost};
299         
300         if(! $amount) {
301                 $amount = Vend::Interpolate::total_cost();
302                 $amount = Vend::Util::round_to_frac_digits($amount,$precision);
303         }
304         $amount =~ s/\D//g;
305
306         $order_id = gen_order_id($opt);
307
308         $name = $actual->{b_fname} . ' ' . $actual->{b_lname};
309
310         $avs = $opt->{avs} || $::Variable->{TRUSTCOMMERCE_AVS} || 'n';
311
312         my %query = (
313                 amount                  => $amount,
314                 cc                      => $actual->{mv_credit_card_number},
315                 exp                     => $exp,
316                 demo                    => $opt->{test} || charge_param('test') || $::Variable->{TRUSTCOMMERCE_TEST},
317                 name                    => $name,
318                 address1                => $actual->{b_address},
319                 city                    => $actual->{b_city},
320                 state                   => $actual->{b_state},
321                 zip                     => $actual->{b_zip},
322                 country                 => $actual->{b_country},
323                 action                  => $transtype,
324                 transid                 => $actual->{order_id},
325                 email                   => $actual->{email},
326                 phone                   => $actual->{phone_day},
327                 password                => $secret,
328                 custid                  => $user,
329                 avs                     => $avs
330         );
331
332         # delete query keys with undefined values
333         for (keys %query) {
334                 delete $query{$_} unless $query{$_};
335         }
336         delete $query{country} if $query{country} eq 'US';
337
338 #::logGlobal("trustcommerce query: " . ::uneval(\%query));
339
340         my %result = Net::TCLink::send(\%query);
341         
342         # Interchange names are on the left, TCLink on the right
343         my %result_map = ( qw/
344                 pop.status              status
345                 order-id                transid
346                 pop.order-id            transid
347                 pop.avs_code            avs
348         /
349         );
350
351 #::logGlobal("trustcommerce response: " . ::uneval(\%result));
352
353         for (keys %result_map) {
354                 $result{$_} = $result{$result_map{$_}}
355                         if defined $result{$result_map{$_}};
356         }
357
358         if ($result{status} eq 'approved') {
359                 $result{MStatus} = 'success';
360         }
361         else {
362                 $result{MStatus} = 'failure';
363                 delete $result{'order-id'};
364
365                 # NOTE: A lot more AVS codes could be checked for here.
366                 if ($result{avs} eq 'N') {
367                         my $msg = q{You must enter the correct billing address of your credit card.};
368                         $result{MErrMsg} = errmsg($msg);
369                 }
370                 else {
371                         my $msg = "TrustCommerce error: %s. Please call in your order or try again.";
372                         $result{MErrMsg} = errmsg($msg);
373                 }
374         }
375
376 #::logGlobal("result given to interchange " . ::uneval(\%result));
377
378         return (%result);
379 }
380
381 package Vend::Payment::TCLink;
382
383 1;