* Add enclair_db option to UserDB.pm. Allows logging of enclair password
[interchange.git] / lib / Vend / Payment / ECHO.pm
1 # Vend::Payment::ECHO - Interchange ECHO support
2 #
3 # $Id: ECHO.pm,v 1.10 2009-03-16 19:34:00 jon Exp $
4 #
5 # Copyright (C) 2002-2005
6 # Interchange Development Group
7 # Electric Pulp. <info@electricpulp.com> 
8 # Kavod Technologies <info@kavod.com>
9 #
10 # VERSION HISTORY
11 # + v1.1 08/06/2002 Fixed a problem with handling the return status from the
12 #   OpenECHO module.
13 # + v1.2 08/17/2002 General clean up
14 # + v1.3 08/22/2002 Ported from globalsub to Vend::Payment
15 #
16 #       http://www.openecho.com/
17 #       http://www.echo-inc.com/
18 #
19 # This program is free software; you can redistribute it and/or modify
20 # it under the terms of the GNU General Public License as published by
21 # the Free Software Foundation; either version 2 of the License, or
22 # (at your option) any later version.
23 #
24 # This program is distributed in the hope that it will be useful,
25 # but WITHOUT ANY WARRANTY; without even the implied warranty of
26 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27 # GNU General Public License for more details.
28 #
29 # You should have received a copy of the GNU General Public
30 # License along with this program; if not, write to the Free
31 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
32 # MA  02110-1301  USA.
33
34 package Vend::Payment::ECHO;
35
36 =head1 NAME
37
38 Vend::Payment::ECHO - Interchange ECHO Support
39
40 =head1 AUTHOR
41
42 Michael Lehmkuhl <michael@electricpulp.com>.
43
44 Ported to Vend::Payment by Dan Browning <db@kavod.com>.  Code reused and 
45 inspired by Mike Heins <mike@perusion.com>.
46
47 =head1 SPECIAL THANKS
48
49 Jim Darden <support@openecho.com>, Dan Browning <db@kavod.com>
50
51 =head1 SYNOPSIS
52
53     &charge=echo
54  
55         or
56  
57     [charge mode=echo param1=value1 param2=value2]
58
59 =head1 PREREQUISITES
60
61 If you have not done so already, you will need to sign up for an ECHO account.
62 You will be provided an ID and a PIN (also known as 'secret').  You may also
63 sign up for a test account at the following URL:
64
65     http://www.echo-inc.com/echotestapp.php
66
67 This subroutine uses the OpenECHO module.  Make sure OpenECHO.pm is in your @INC
68 array.  It is available for download, see the following URLs:
69   
70     http://www.openecho.com/
71     http://www.echo-inc.com/
72   
73 The OpenECHO.pm module itself has some additional prerequisites:
74
75     Net::SSLeay
76  
77         or
78   
79     LWP::UserAgent and Crypt::SSLeay
80
81 Only one of these need be present and working.  Net::SSLeay is preferred as some
82 have reported problems using LWP::UserAgent and Crypt::SSLeay.
83
84     URL::Escape
85
86 This module is used to write some of the URLs used by the OpenECHO module.  It
87 is recommended that you read the documention for the OpenECHO module itself in
88 addition to this document.
89
90 =head1 DESCRIPTION
91
92 The Vend::Payment::ECHO module implements the echo() routine
93 for use with Interchange. It is compatible on a call level with the other
94 Interchange payment modules.
95
96 To enable this module, place this directive in C<interchange.cfg>:
97
98     Require module Vend::Payment::ECHO
99
100 This I<must> be in interchange.cfg or a file included from it.
101
102 NOTE: Make sure CreditCardAuto is off (default in Interchange demos).
103
104 The mode can be named anything, but the C<gateway> parameter must be set
105 to C<echo>. To make it the default payment gateway for all credit
106 card transactions in a specific catalog, you can set in C<catalog.cfg>:
107
108     Variable MV_PAYMENT_MODE  echo
109
110 It uses several of the standard settings from Interchange payment. Any time
111 we speak of a setting, it is obtained either first from the tag/call options,
112 then from an Interchange order Route named for the mode, then finally a
113 default global payment variable, For example, the C<id> parameter would
114 be specified by:
115
116     Route echo id Your_ECHO_ID
117
118 or  (with only ECHO as a payment provider)
119     
120      Variable MV_PAYMENT_ID     Your_ECHO_ID
121
122 or
123
124      Variable ECHO_PAYMENT_ID   Your_ECHO_ID
125
126 or
127  
128      [charge mode=echo id=Your_ECHO_ID]
129
130 The active settings are:
131
132 =over 4
133
134 =item id
135
136 Your account ID, supplied by ECHO when you sign up.
137 Global parameter is MV_PAYMENT_ID or ECHO_PAYMENT_ID.
138
139 =item secret
140
141 Your account password, selected by you or provided by ECHO when you sign up.
142 Global parameter is MV_PAYMENT_SECRET or ECHO_PAYMENT_SECRET.
143
144 =item others...
145
146 If planning to do AUTH_ONLY or other with special admin page
147 Variable MV_PAYMENT_REMAP order_id=mv_order_id auth_code=mv_auth_code
148
149     Variable ECHO_PAYMENT_ORDER_TYPE         S
150             # S for "self-service" orders
151             # F for hosted or ISP orders
152     Variable ECHO_PAYMENT_ISP_ECHO_ID        123<4567890
153     Variable ECHO_PAYMENT_ISP_PIN            12345608
154     Variable ECHO_PAYMENT_MERCHANT_EMAIL     merchant@merchant.com
155     Variable ECHO_PAYMENT_DEBUG              F
156             # C causes ECHO to return a statement of conformity
157             # T or TRUE causes ECHO to return additional debug information
158             # Any other value turns off ECHO debugging
159
160 =back 
161
162 =head2 Example Configuration
163
164 This is an example configuration that one would add to catalog.cfg: 
165
166     Variable MV_PAYMENT_ID      Your_ECHO_ID
167     Variable MV_PAYMENT_SECRET  Your_ECHO_secret
168     Variable MV_PAYMENT_MODE    echo
169
170 =head2 Troubleshooting
171
172 Try a sale with the card number C<4111 1111 1111 1111> and a valid expiration 
173 date. The sale should be denied, and the reason should be in 
174 [data session payment_error].
175
176 If nothing works:
177
178 =over 4
179
180 =item *
181
182 Make sure you "Require"d the module in interchange.cfg:
183
184     Require module Vend::Payment::ECHO
185
186 =item *
187
188 Make sure the ECHO C<OpenECHO.pm> module is available either in your
189 path or in /path_to_interchange/lib.
190
191 =item *
192
193 Check the error logs, both catalog and global.
194
195 =item *
196
197 Make sure you set your account ID and secret properly.  
198
199 =item *
200
201 Try an order, then put this code in a page:
202
203     <XMP>
204     [calc]
205         my $string = $Tag->uneval( { ref => $Session->{payment_result} });
206         $string =~ s/{/{\n/;
207         $string =~ s/,/,\n/g;
208         return $string;
209     [/calc]
210     </XMP>
211
212 That should show what happened.
213
214 =item *
215
216 If all else fails, Interchange consultants are available to help
217 with integration for a fee.
218
219 =back
220
221 =head1 SECURITY CONSIDERATIONS
222
223 Because this library calls an executable, you should ensure that no
224 untrusted users have write permission on any of the system directories
225 or Interchange software directories.
226
227 =head1 NOTES
228
229 There is actually nothing *in* Vend::Payment::ECHO. It changes packages
230 to Vend::Payment and places things there.
231
232 =cut
233
234 BEGIN {
235
236         my $selected;
237         eval {
238                 package Vend::Payment;
239                 require Net::SSLeay;
240                 import Net::SSLeay qw(post_https make_form make_headers);
241                 $selected = "Net::SSLeay";
242         };
243
244         $Vend::Payment::Have_Net_SSLeay = 1 unless $@;
245
246         unless ($Vend::Payment::Have_Net_SSLeay) {
247
248                 eval {
249                         package Vend::Payment;
250                         require LWP::UserAgent;
251                         require HTTP::Request::Common;
252                         require Crypt::SSLeay;
253                         import HTTP::Request::Common qw(POST);
254                         $selected = "LWP and Crypt::SSLeay";
255                 };
256
257                 $Vend::Payment::Have_LWP = 1 unless $@;
258
259         }
260
261         unless ($Vend::Payment::Have_Net_SSLeay or $Vend::Payment::Have_LWP) {
262                 die __PACKAGE__ . " requires Net::SSLeay or Crypt::SSLeay";
263         }
264
265 ::logGlobal("%s payment module initialized, using %s", __PACKAGE__, $selected)
266                 unless $Vend::Quiet or ! $Global::VendRoot;
267
268 }
269
270 package Vend::Payment;
271
272 use OpenECHO;
273
274 sub echo {
275
276         my ($user, $amount) = @_;
277
278         my $opt;
279         my $secret;
280         
281         if(ref $user) {
282                 $opt = $user;
283                 $user = $opt->{id} || undef;
284                 $secret = $opt->{secret} || undef;
285         }
286         else {
287                 $opt = {};
288         }
289
290 #::logDebug("echo called, args=" . ::uneval(\@_));
291         
292         my (%actual) = map_actual();
293         
294         my @errMsgs = ();
295         # Required for validation
296         if (! $user) {
297                 $user      = $opt->{id} || 
298                              charge_param('id') ||  
299                              $::Variable->{ECHO_PAYMENT_ID} ||
300                              $::Variable->{MV_PAYMENT_ID} ||
301                         $::Variable->{CYBER_ID}
302                         or push @errMsgs, "No payment ID found.";
303         }
304         
305         # Required for validation
306         if (! $secret) {
307                 $secret    = $opt->{secret} ||
308                              charge_param('secret') ||
309                                                  $::Variable->{ECHO_PAYMENT_SECRET} ||
310                                                  $::Variable->{MV_PAYMENT_SECRET} ||
311                              $::Variable->{CYBER_SECRET}
312                              or push @errMsgs, "No payment secret found.";
313         }
314
315         if (scalar @errMsgs) {
316                 for (@errMsgs) {
317                         ::logError($_);
318                 }
319                 return 0;
320         }
321         @errMsgs = ();
322
323    my $server     = $opt->{server} ||
324                      charge_param('server') ||
325                                                         $::Variable->{ECHO_PAYMENT_SERVER} ||
326                                                         $::Variable->{MV_PAYMENT_SERVER} ||
327                      $::Variable->{CYBER_SERVER} ||
328                      'https://wwws.echo-inc.com/scripts/INR200.EXE';
329
330         my $precision  =  $opt->{precision} ||
331                      charge_param('precision') ||
332                                                         $::Variable->{ECHO_PAYMENT_PRECISION} ||
333                                                         $::Variable->{MV_PAYMENT_PRECISION} ||
334                      $::Variable->{CYBER_PRECISION} ||
335                      2;
336
337         ##### ECHO SPECIFIC VARIABLES #####
338
339         my $order_type = $::Variable->{ECHO_PAYMENT_ORDER_TYPE} || 'S';
340         my $isp_echo_id = $::Variable->{ECHO_PAYMENT_ISP_ECHO_ID};
341         my $isp_pin = $::Variable->{ECHO_PAYMENT_ISP_PIN};
342         my $merchant_email = $::Variable->{ECHO_PAYMENT_MERCHANT_EMAIL};
343
344         # Set to 'C' for Certify mode to check compliance with the ECHO spec on a
345         # transaction-by-transaction basis.  'T' or 'TRUE' for full ECHO debugging.
346         my $debug = $::Variable->{ECHO_PAYMENT_DEBUG};
347
348         ##########################
349
350         $actual{mv_credit_card_exp_month} =~ s/\D//g;
351         $actual{mv_credit_card_exp_month} =~ s/^0+//;
352         $actual{mv_credit_card_exp_year} =~ s/\D//g;
353         $actual{mv_credit_card_exp_year} =~ s/\d\d(\d\d)/$1/;
354
355         $actual{mv_credit_card_number} =~ s/\D//g;
356
357         my $exp = sprintf '%02d%02d',
358                         $actual{mv_credit_card_exp_month},
359                         $actual{mv_credit_card_exp_year};
360
361         # Using mv_payment_mode for compatibility with older versions, probably not
362         # necessary.
363         $actual{cyber_mode} = $actual{mv_payment_mode} || 'ES'
364         unless $actual{cyber_mode};
365
366         # Credit Card Transactions 
367         # *     AD (Address Verification) 
368         # *     AS (Authorization) 
369         # *     AV (Authorization with Address Verification) 
370         # *     CR (Credit) 
371         # *     DS (Deposit) 
372         # *     ES (Authorization and Deposit) 
373         # *     EV (Authorization and Deposit with Address Verification) 
374         # *     CK (System check) 
375         # Credit Card Transactions Enhanced by CyberSource 
376         # *     CI (AV Transaction with CyberSource Internet Fraud Screen) 
377         # *     CE (AV Transaction with CyberSource Export Compliance) 
378         # *     CB (AV Transaction with CyberSource Internet Fraud Screen and Export Compliance) 
379         # Electronic Check Transactions 
380         # *     DV (Electronic Check Verification) 
381         # *     DD (Electronic Check Debit) 
382         # *     DC (Electronic Check Credit)
383         my %type_map = (
384                 mauth_capture                   =>      'ES',
385                 EV                                              =>  'EV',
386                 mauthonly                               =>      'AS',
387                 CAPTURE_ONLY                    =>  'DS',
388                 CREDIT                                  =>      'CR',
389                 AUTH_ONLY                               =>      'AS',
390                 PRIOR_AUTH_CAPTURE              =>      'DS',
391         );
392         
393         if (defined $type_map{$actual{cyber_mode}}) {
394         $actual{cyber_mode} = $type_map{$actual{cyber_mode}};
395     }
396     else {
397         $actual{cyber_mode} = 'ES';
398     }
399
400     if(! $amount) {
401         $amount = Vend::Interpolate::total_cost();
402         $amount = Vend::Util::round_to_frac_digits($amount,$precision);
403     }
404
405     my($orderID);
406     my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(time());
407
408     ### Make an order ID based on date, time, and Interchange session
409     # $mon is the month index where Jan=0 and Dec=11, so we use
410     # $mon+1 to get the more familiar Jan=1 and Dec=12
411     #$orderID = sprintf("%04d%02d%02d%02d%02d%05d%s",
412     #        $year + 1900,$mon + 1,$mday,$hour,$min,$$,$Vend::SessionName);
413         $orderID = Vend::Payment::gen_order_id();
414
415         ### Set up the OpenECHO instance
416         use OpenECHO;
417         my $openecho = new OpenECHO or push @errMsgs, "Couldn't make instance of OpenECHO.";
418         if (scalar @errMsgs) {
419                 for (@errMsgs) {
420                         ::logError($_);
421                 }
422                 return 0;
423         }
424         @errMsgs = ();
425
426         ### Connection info
427         $openecho->set_EchoServer("https://wwws.echo-inc.com/scripts/INR200.EXE");
428         $openecho->set_transaction_type($actual{cyber_mode});
429         $openecho->set_order_type($order_type);
430
431         ### Merchant/ISP info
432         $openecho->set_merchant_echo_id($user);
433         $openecho->set_merchant_pin($secret);
434         $openecho->set_isp_echo_id($isp_echo_id);
435         $openecho->set_isp_pin($isp_pin);
436         $openecho->set_merchant_email($merchant_email);
437
438         ### Billing info
439         my $billing_first_name = $actual{b_fname} || $actual{fname};
440         my $billing_last_name = $actual{b_lname} || $actual{lname};
441         my $billing_address1 = $actual{b_address1} || $actual{address1};
442         my $billing_address2 = $actual{b_address2} || $actual{address2};
443         my $billing_city = $actual{b_city} || $actual{city};
444         my $billing_state = $actual{b_state} || $actual{state};
445         my $billing_zip = $actual{b_zip} || $actual{zip};
446         my $billing_country = $actual{b_country} || $actual{country};
447         my $billing_phone = $actual{phone_day} || $actual{phone_night};
448         $openecho->set_billing_ip_address($Vend::Session->{ohost});             # aka [data session ohost] aka REMOTE_HOST
449         #$openecho->set_billing_prefix($actual{prefix});
450         $openecho->set_billing_first_name($billing_first_name);
451         $openecho->set_billing_last_name($billing_last_name);
452         #$openecho->set_billing_company_name($actual{company_name});
453         $openecho->set_billing_address1($billing_address1);
454         $openecho->set_billing_address2($billing_address2);
455         $openecho->set_billing_city($billing_city);
456         $openecho->set_billing_state($billing_state);
457         $openecho->set_billing_zip($billing_zip);
458         $openecho->set_billing_country($billing_country);
459         $openecho->set_billing_phone($billing_phone);
460         #$openecho->set_billing_fax($actual{fax});
461         $openecho->set_billing_email($actual{email});
462
463         ### Electronic check payment info if supplied...
464         #$openecho->set_ec_bank_name($ec_bank_name);
465         #$openecho->set_ec_first_name($billing_first_name);
466         #$openecho->set_ec_last_name($billing_last_name);
467         #$openecho->set_ec_address1($billing_address1);
468         #$openecho->set_ec_address2($billing_address2);
469         #$openecho->set_ec_city($billing_city);
470         #$openecho->set_ec_state($billing_state);
471         #$openecho->set_ec_zip($billing_zip);
472         #$openecho->set_ec_rt($ec_rt);
473         #$openecho->set_ec_account($ec_account);
474         #$openecho->set_ec_serial_number($ec_serial_number);
475         #$openecho->set_ec_payee($ec_payee);
476         #$openecho->set_ec_id_state($ec_id_state);
477         #$openecho->set_ec_id_number($ec_id_number);
478         #$openecho->set_ec_id_type($ec_id_type);
479
480         ### Debug on/off
481         $openecho->set_debug($debug);
482         
483         ### Payment details
484         $openecho->set_cc_number($actual{mv_credit_card_number});
485         $openecho->set_grand_total($amount);
486         $openecho->set_ccexp_month($actual{mv_credit_card_exp_month});
487         $openecho->set_ccexp_year($actual{mv_credit_card_exp_year});
488         $openecho->set_counter($openecho->getRandomCounter());
489         $openecho->set_merchant_trace_nbr($orderID);
490         
491         ### Send payment request
492         #print($openecho->get_version() . "<BR>");
493 #::logDebug("openecho submitting <urldata>%s</urldata>", $openecho->getURLData());
494         $openecho->Submit();
495
496 #::logDebug("The ECHO response is <echo_response>%s</echo_response>", $openecho->{'EchoResponse'});
497
498 #::logDebug("The ECHO type 2 response is <echotype2>%s</echotype2>", $openecho->{'echotype2'});
499
500 #::logDebug("The avs_result field is <avs_result>%s</avs_result>", $openecho->{avs_result});
501
502         my %result;
503         if ($openecho->{EchoSuccess} != 0) {
504                 $result{'MStatus'} = 'success';
505                 $result{'pop.status'} = 'success';
506                 $result{'MErrMsg'} = $openecho->{'echotype2'};
507                 $result{'pop.error-message'} = $openecho->{'echotype2'};
508                 $result{'order-id'} = $openecho->{order_number} || 1;
509                 $result{'pop.order-id'} = $openecho->{order_number} || 1;
510                 $result{'auth_code'} = $openecho->{auth_code};
511                 $result{'pop.auth_code'} = $openecho->{auth_code};
512                 $result{'avs_code'} = $openecho->{avs_result};
513                 $result{'pop.avs_code'} = $openecho->{avs_result};
514         }
515         else {
516                 $result{MStatus} = 'failure';
517                 $Vend::Session->{MStatus} = 'failure';
518                 
519                 # NOTE: A lot more AVS codes could be checked for here.
520                 if ($result{avs_code} eq 'N') {
521                         $result{MErrMsg} = "You must enter the correct billing address of your credit card. The bank returned the following error: " . $openecho->{'avs_result'};
522                 }
523                 else {
524                         $result{MErrMsg} = $openecho->{'echotype2'};
525                 }
526                 $Vend::Session->{payment_error} = $result{MErrMsg};
527 #::logDebug("openecho oops: ".$Vend::Session->{payment_error});
528         }
529
530     return (%result);
531 }
532
533 package Vend::Payment::ECHO;
534
535 return 1;