1 # Vend::Payment::ECHO - Interchange ECHO support
3 # $Id: ECHO.pm,v 1.10 2009-03-16 19:34:00 jon Exp $
5 # Copyright (C) 2002-2005
6 # Interchange Development Group
7 # Electric Pulp. <info@electricpulp.com>
8 # Kavod Technologies <info@kavod.com>
11 # + v1.1 08/06/2002 Fixed a problem with handling the return status from the
13 # + v1.2 08/17/2002 General clean up
14 # + v1.3 08/22/2002 Ported from globalsub to Vend::Payment
16 # http://www.openecho.com/
17 # http://www.echo-inc.com/
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.
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.
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,
34 package Vend::Payment::ECHO;
38 Vend::Payment::ECHO - Interchange ECHO Support
42 Michael Lehmkuhl <michael@electricpulp.com>.
44 Ported to Vend::Payment by Dan Browning <db@kavod.com>. Code reused and
45 inspired by Mike Heins <mike@perusion.com>.
49 Jim Darden <support@openecho.com>, Dan Browning <db@kavod.com>
57 [charge mode=echo param1=value1 param2=value2]
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:
65 http://www.echo-inc.com/echotestapp.php
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:
70 http://www.openecho.com/
71 http://www.echo-inc.com/
73 The OpenECHO.pm module itself has some additional prerequisites:
79 LWP::UserAgent and Crypt::SSLeay
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.
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.
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.
96 To enable this module, place this directive in C<interchange.cfg>:
98 Require module Vend::Payment::ECHO
100 This I<must> be in interchange.cfg or a file included from it.
102 NOTE: Make sure CreditCardAuto is off (default in Interchange demos).
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>:
108 Variable MV_PAYMENT_MODE echo
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
116 Route echo id Your_ECHO_ID
118 or (with only ECHO as a payment provider)
120 Variable MV_PAYMENT_ID Your_ECHO_ID
124 Variable ECHO_PAYMENT_ID Your_ECHO_ID
128 [charge mode=echo id=Your_ECHO_ID]
130 The active settings are:
136 Your account ID, supplied by ECHO when you sign up.
137 Global parameter is MV_PAYMENT_ID or ECHO_PAYMENT_ID.
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.
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
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
162 =head2 Example Configuration
164 This is an example configuration that one would add to catalog.cfg:
166 Variable MV_PAYMENT_ID Your_ECHO_ID
167 Variable MV_PAYMENT_SECRET Your_ECHO_secret
168 Variable MV_PAYMENT_MODE echo
170 =head2 Troubleshooting
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].
182 Make sure you "Require"d the module in interchange.cfg:
184 Require module Vend::Payment::ECHO
188 Make sure the ECHO C<OpenECHO.pm> module is available either in your
189 path or in /path_to_interchange/lib.
193 Check the error logs, both catalog and global.
197 Make sure you set your account ID and secret properly.
201 Try an order, then put this code in a page:
205 my $string = $Tag->uneval( { ref => $Session->{payment_result} });
207 $string =~ s/,/,\n/g;
212 That should show what happened.
216 If all else fails, Interchange consultants are available to help
217 with integration for a fee.
221 =head1 SECURITY CONSIDERATIONS
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.
229 There is actually nothing *in* Vend::Payment::ECHO. It changes packages
230 to Vend::Payment and places things there.
238 package Vend::Payment;
240 import Net::SSLeay qw(post_https make_form make_headers);
241 $selected = "Net::SSLeay";
244 $Vend::Payment::Have_Net_SSLeay = 1 unless $@;
246 unless ($Vend::Payment::Have_Net_SSLeay) {
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";
257 $Vend::Payment::Have_LWP = 1 unless $@;
261 unless ($Vend::Payment::Have_Net_SSLeay or $Vend::Payment::Have_LWP) {
262 die __PACKAGE__ . " requires Net::SSLeay or Crypt::SSLeay";
265 ::logGlobal("%s payment module initialized, using %s", __PACKAGE__, $selected)
266 unless $Vend::Quiet or ! $Global::VendRoot;
270 package Vend::Payment;
276 my ($user, $amount) = @_;
283 $user = $opt->{id} || undef;
284 $secret = $opt->{secret} || undef;
290 #::logDebug("echo called, args=" . ::uneval(\@_));
292 my (%actual) = map_actual();
295 # Required for validation
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.";
305 # Required for validation
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.";
315 if (scalar @errMsgs) {
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';
330 my $precision = $opt->{precision} ||
331 charge_param('precision') ||
332 $::Variable->{ECHO_PAYMENT_PRECISION} ||
333 $::Variable->{MV_PAYMENT_PRECISION} ||
334 $::Variable->{CYBER_PRECISION} ||
337 ##### ECHO SPECIFIC VARIABLES #####
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};
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};
348 ##########################
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/;
355 $actual{mv_credit_card_number} =~ s/\D//g;
357 my $exp = sprintf '%02d%02d',
358 $actual{mv_credit_card_exp_month},
359 $actual{mv_credit_card_exp_year};
361 # Using mv_payment_mode for compatibility with older versions, probably not
363 $actual{cyber_mode} = $actual{mv_payment_mode} || 'ES'
364 unless $actual{cyber_mode};
366 # Credit Card Transactions
367 # * AD (Address Verification)
368 # * AS (Authorization)
369 # * AV (Authorization with Address Verification)
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)
384 mauth_capture => 'ES',
387 CAPTURE_ONLY => 'DS',
390 PRIOR_AUTH_CAPTURE => 'DS',
393 if (defined $type_map{$actual{cyber_mode}}) {
394 $actual{cyber_mode} = $type_map{$actual{cyber_mode}};
397 $actual{cyber_mode} = 'ES';
401 $amount = Vend::Interpolate::total_cost();
402 $amount = Vend::Util::round_to_frac_digits($amount,$precision);
406 my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(time());
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();
415 ### Set up the OpenECHO instance
417 my $openecho = new OpenECHO or push @errMsgs, "Couldn't make instance of OpenECHO.";
418 if (scalar @errMsgs) {
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);
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);
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});
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);
481 $openecho->set_debug($debug);
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);
491 ### Send payment request
492 #print($openecho->get_version() . "<BR>");
493 #::logDebug("openecho submitting <urldata>%s</urldata>", $openecho->getURLData());
496 #::logDebug("The ECHO response is <echo_response>%s</echo_response>", $openecho->{'EchoResponse'});
498 #::logDebug("The ECHO type 2 response is <echotype2>%s</echotype2>", $openecho->{'echotype2'});
500 #::logDebug("The avs_result field is <avs_result>%s</avs_result>", $openecho->{avs_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};
516 $result{MStatus} = 'failure';
517 $Vend::Session->{MStatus} = 'failure';
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'};
524 $result{MErrMsg} = $openecho->{'echotype2'};
526 $Vend::Session->{payment_error} = $result{MErrMsg};
527 #::logDebug("openecho oops: ".$Vend::Session->{payment_error});
533 package Vend::Payment::ECHO;