1 # Vend::Payment::Worldpay - Interchange Worldpay support
3 # worldpay.pm, v 1.0.2, October 2010
5 # Copyright (C) 2009 Nimbus Designs Ltd T/A TVCables and
6 # Zolotek Resources Ltd All rights reserved.
8 # Authors: Andy Smith <andy@tvcables.co.uk>, http://www.tvcables.co.uk
9 # Lyn St George <info@zolotek.net, http://www.zolotek.net>
10 # Based on original code by Mike Heins <mheins@perusion.com> and others.
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public Licence as published by
13 # the Free Software Foundation; either version 2 of the Licence, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public Licence for more details.
21 # You should have received a copy of the GNU General Public
22 # Licence along with this program; if not, write to the Free
23 # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
28 package Vend::Payment::Worldpay;
30 =head1 Interchange Worldpay Support
32 Vend::Payment::Worldpay $Revision: 1.0.2 $
34 http://kiwi.zolotek.net is the home page with the latest version.
36 =head1 This package is for the 'Worldpay' payment system.
39 =head1 Quick Start Summary
41 1 Place this module in <IC_root>/lib/Vend/Payment/Worldpay.pm
43 2 Call it in interchange.cfg with:
44 Require module Vend::Payment::Worldpay.
46 3 Add a new route into catalog.cfg (options for the last entry in parentheses):
47 Route worldpay host https://secure.wp3.rbsworldpay.com/wcc/purchase (Live Payment URL)
48 Route worldpay testhost https://secure-test.wp3.rbsworldpay.com/wcc/dispatcher (Test payment URL)
49 Route worldpay instid 12345 (Your Worldpay instID)
50 Route worldpay currency GBP (defaults to GBP)
51 Route worldpay testmode 100 (Set to 100 for test mode 0 for live - default live)
52 Route worldpay callbackurl (The URL Worldpay will callback eg www.yourstore.co.uk/cgi-bin/yourname/wpcallback.html)
53 Route worldpay callpw (Callback password, set any password you like and set it the same in the WP Admin Panel)
54 Route worldpay fixcontact 1 (If set to 1 customers cannot ammend address details when they get to worldpay, 0 to allow changes)
55 Route worldpay desc 'Yourstore Order' (Text to send in the desc field eg 'Yourstore Order')
56 Route worldpay reporttitle 1 (If set to 1 will modifty order report title to include transaction ID)
57 Route worldpay update_status processing (Text to set order status on success eg processing, default pending)
58 Route worldpay wpcounter (Defines the counter for temporary order number, defaults to etc/username)
59 Route worldpay md5pw yourmd5secret (required in this version, will die without it)
62 Enter the following into the worldpay control panel:-
63 Your MD5 secret as set in catalog.cfg
64 In the signature fields box enter amount:instId:MC_affsubtotal:currency
66 4 Create a new locale setting for en_GB as noted in "item currency" below, and copy the
67 public space interchange/en_US/ directory to a new interchange/en_GB/ one. Ensure that any
68 other locales you might use have a correctly named directory as well. Ensure that this locale
69 is found in your version of locale.txt (and set up GB as opposed to US language strings to taste).
71 5 Add a new order profile in etc/profiles.order
89 &set=mv_payment worldpay
91 &set=mv_payment_route worldpay
92 &set=mv_order_route worldpay
94 &setcheck = payment_method worldpay
97 6 Add the following fields to the transactions table if they do not already exist (run from a mysql prompt)
99 ALTER TABLE `transactions` ADD `wp_transtime` VARCHAR( 64 ) CHARACTER SET utf8 COLLATE utf8_general_ci ,
100 ADD `wp_cardtype` VARCHAR( 64 ) CHARACTER SET utf8 COLLATE utf8_general_ci,
101 ADD `wp_countrymatch` VARCHAR( 64 ) CHARACTER SET utf8 COLLATE utf8_general_ci,
102 ADD `wp_avs` VARCHAR( 64 ) CHARACTER SET utf8 COLLATE utf8_general_ci,
103 ADD `wp_risk` VARCHAR( 64 ) CHARACTER SET utf8 COLLATE utf8_general_ci,
104 ADD `wp_authentication` VARCHAR( 64 ) CHARACTER SET utf8 COLLATE utf8_general_ci,
105 ADD `wp_authamount` VARCHAR( 64 ) CHARACTER SET utf8 COLLATE utf8_general_ci,
106 ADD `wp_order_number` VARCHAR( 64 ) CHARACTER SET utf8 COLLATE utf8_general_ci,
107 ADD `wp_transtime` VARCHAR( 64 ) CHARACTER SET utf8 COLLATE utf8_general_ci,
108 ADD `lead_source` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci,
109 ADD `referring_url` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci,
110 ADD `txtype` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_general_ci,
111 ADD `email_copy` CHAR(1) CHARACTER SET utf8 COLLATE utf8_general_ci,
112 ADD `mail_list` varCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci,
113 ADD `cartid` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_general_ci,
117 And run these to allow for temporary order numbers of greater than the default 14 character field type
118 ALTER TABLE `transactions` MODIFY `order_number` varchar(32);
119 ALTER TABLE `orderline` MODIFY `order_number` varchar(32);
121 Also add 'cartid' to the orderline table.
123 7. Add the following to etc/log_transaction just BEFORE [/import][/try]
125 lead_source: [data session source]
126 referring_url: [data session referer]
127 cart: [calc]uneval($Items)[/calc]
128 cartid: [value mv_order_number]
129 email_copy: [value email_copy]
130 mail_list: [value mail_list]
132 8. Still in etc/log_transaction, find the section that starts "Set order number in values: " and insert
135 NB/ this only applies if your IC is greater than v 5.2, otherwise skip sections 8 & 9
137 [if value mv_order_profile =~ /worldpay/]
138 [value name=mv_order_number set="[scratch purchaseID]" scratch=1]
140 and a closing [/else][/if] at the end of that section, just before the
141 "Set order number in session:"
142 line. The order number is generated by the module and passed to Worldpay at an early stage, and then
143 passed back to Interchange with a callback. This prevents Interchange generating another order number.
144 The module will not currently work with IC versions lower than 5.2 that use a tid counter defined in
145 catalog.cfg. The initial order number uses the username.counter number prefixed with 'WPtmp', and a normal
146 order number is created and the initial order number replaced only when Worldpay calls back that the card
147 has been charged. This is to avoid gaps in the order number sequence caused by customers abandoning the
150 9. In etc/log_transction, change the line:-
151 [elsif variable MV_PAYMENT_MODE]
153 [elsif value mv_order_profile =~ /worldpay/] add an OR if required
154 eg [elsif value mv_order_profile =~ /googlecheckout|worldpay/]
156 Then in the [calc] block immediately below insert this line:
158 undef $Session->{payment_result}{MStatus};
160 Within the same section change the following two instances of
161 [var MV_PAYMENT_MODE] to [value mv_payment_route]
163 10. Create a callback page in /pages called wpcallback.html or any name you prefer, set this page in
164 the Worldpay admin panel, the module also supports dynamic callback pages where different catalogs can
165 have different callback pages, if using this the callpage URL must be set in the route in catalog.cfg as
168 At the top of the callback page include the following line:-
169 [charge route="worldpay" worldpayrequest="callback"]
171 At the end of the charge process Worldpay do not allow redirection to a receipt page, if you do this they
172 claim they will disable the callback feature or even suspend your account, how nice! You can however re-direct if
173 the transaction is cancelled
175 Worldpay will suck the wpcallback page back to their server and display it for you, this can be used to display a receipt page.
176 The page will interpolate before being sucked to Worldpay so most items such as fname lname adress fields etc are usuable on the page.
177 To display banners and logos they need to be pre-loaded onto the Worldpay server
179 At the top of the callback page just below the [charge route="worldpay" worldpayrequest="callback"] you can test for a successful transaction as follows:-
181 [if type="cgi" term="transStatus" op="eq" compare="Y"]
182 [and type="cgi" term="callbackPW" op="eq" compare="yourcallbackpassword"]
184 Display a receipt page
188 Display a cancelled page or bounce the customer back to site etc
195 On your checkout page include a button that sets the route and submits the checkout form eg
203 mv_order_profile=worldpay
208 NB/ for IC versions 5.2 and older, make the button so as to call the 'wprequest.html' page:
216 mv_nextpage=ord/wprequest
226 LWP::UserAgent and Crypt::SSLeay
228 wget - a recent version built with SSL and supporting the 'connect' timeout function.
233 The Vend::Payment::Worldpay module implements the Worldpay() routine for use with
234 Interchange. It is _not_ compatible on a call level with the other Interchange
237 To enable this module, place this directive in <interchange.cfg>:
239 Require module Vend::Payment::Worldpay
241 This I<must> be in interchange.cfg or a file included from it.
243 The module collects the data from a checkout form and formats it with a re-direct to
244 the Worldpay payment server. The customers details and cart is logged in the database
245 before going to Worldpay with a temporary order number of the form WPtmpUxxxx where Uxxxx
246 is derived from the username counter
248 If the transaction is successful the module processes the callback response from Worlday, if
249 successful the temporary order number is converted to an Interchange order number and a final
250 route is run to send out the report and customer emails. Cancelled transactions remain in the
251 database with the temporary order numbers but are automatically archived.
253 The module will also optionally decrement the inventory on a successful, if used
254 the inventory decrement in log transaction should be disabled by setting the appropriate variable
256 =head1 The active settings.
258 The module uses several of the standard settings from the Interchange payment routes.
259 Any such setting, as a general rule, is obtained first from the tag/call options on
260 a page, then from an Interchange order Route named for the mode in catalog.cfg,
261 then a default global payment variable in products/variable.txt, and finally in
262 some cases a default will be hard-coded into the module.
268 Your installation id supplied by Worldpay, the module cannot be used without an instid, set in
273 Worldpay requires that a currency code be sent, using the 3 letter ISO currency code standard,
274 eg, GBP, EUR, USD. The value is taken firstly from the route parameter in catalog.cfg and
279 Sets whether the system runs test or live transactions, set to 0 (default) for live transactions,
280 or 100 for test transactions.
284 If using dynamic callback pages with Worldpay, set you callback page without the http eg:-
286 www.yourstore.co.uk/cgi-bin/yourstore/wpcallback.html
290 Sets the password to compare with the callback, set this the same as the password in the Worldpay
296 Sets the text for the desc field sent to Worldpay and will appear on the transaction reciper, eg
301 Fixes the information send to Worldpay so it cannot be modified by the customer at Worldpay, set to 1
302 to fix or 0 to allow the customer to edit address at Worldpay.
306 Set to 1 to change the email report title to include the Worldpay transaction ID, set to zero for
307 standard report email title
311 Allows the order status to be set to any desired value after a successful transaction, eg set to processing
312 and all successfull transactions will have status processing, defaults to pending
316 Set to 1 for module to decrement the inventory on a successful transaction, if used disable decrement via
324 Set testmode 100 in catalog.cfg
326 Add some items to the cart and place the order, the module will re-direct you
327 to Worldpay where you can select the card type to pay with. Enter some test card details and check
328 the order is logged in the database ok and emails sent out.
356 Visa Electron (UK only)
379 - v1.0.2 November 2011, added encryption from Andy to main request as defence against tampering
380 - v1.0.1 not released: October 2010, made work with IC v4.8.7 - needs to have 'use strict' commented out, and a redirection page
385 Andy Smith <andy@tvcables.co.uk> with help from and based on code by
386 Lyn St George <lyn@zolotek.net>, which in turn was based on original
387 code by Mike Heins <mike@perusion.com> and others.
395 package Vend::Payment;
397 import Net::SSLeay qw(post_https make_form make_headers);
398 $selected = "Net::SSLeay";
401 $Vend::Payment::Have_Net_SSLeay = 1 unless $@;
403 unless ($Vend::Payment::Have_Net_SSLeay) {
406 package Vend::Payment;
407 require LWP::UserAgent;
408 require HTTP::Request::Common;
409 require Crypt::SSLeay;
411 use Digest::MD5 qw(md5_hex);
413 import Encode qw(encode decode);
414 import HTTP::Request::Common qw(POST);
415 $selected = "LWP and Crypt::SSLeay";
418 $Vend::Payment::Have_LWP = 1 unless $@;
421 unless ($Vend::Payment::Have_Net_SSLeay or $Vend::Payment::Have_LWP) {
422 die __PACKAGE__ . " requires Net::SSLeay or Crypt::SSLeay, " . $@;
425 ::logGlobal("%s 1.0.2 payment module initialised, using %s", __PACKAGE__, $selected)
430 package Vend::Payment;
434 my ($amount, $actual, $opt, $worldpayrequest, $cart, $orderID, $purchaseID, %result, $dbh, $sql, $sth, $stho);
436 #::logDebug("WP".__LINE__.": amnt=$amount, req=$worldpayrequest, pID=$purchaseID");
437 # Amount to send with 2 decimals and no symbol
438 $amount = $::Values->{amount} || charge_param('amount') || Vend::Interpolate::total_cost();
439 $amount =~ s/^\D*//g;
441 $amount = sprintf '%.2f', $amount;
443 my $oldic = charge_param('oldic');
445 # Transaction variables to send to worldpay.
446 my $host = charge_param('host') || 'https://secure.wp3.rbsworldpay.com/wcc/purchase'; #Live
447 my $testhost = charge_param('testhost') || 'https://secure-test.wp3.rbsworldpay.com/wcc/purchase';#Test
448 my $instId = charge_param('instid');
449 my $accId1 = charge_param('accid1');
450 my $currency = charge_param('currency') || 'GBP';
451 my $charged = charge_param('authtype') || 'WP PreAuthed';
452 my $testMode = charge_param('testmode') || '0';
453 my $authMode = $::Values->{'authmode'} || charge_param('authmode') || 'E';
454 my $callbackurl = charge_param('callbackurl'); #URL on your server WP will callback, including .html extension
455 my $callpw = charge_param('callpw'); #Must be same as Worldpay admin panel callback password
456 my $desc; #Transaction description, set to CartID for easier reference
457 my $tmpPrefix = charge_param('tmporderprefix') || 'Cart';
458 my $fixcontact = charge_param('fixcontact') || '0'; #0=details editable at WP 1=details fixed as sent
459 my $affsubtotal = Vend::Interpolate::subtotal(); # This is used to send subtotal as as MC_ parameter to read back for affilate sales calculations
460 $affsubtotal =~ s/^\D*//g;
461 $affsubtotal =~ s/\,//g;
462 $affsubtotal = sprintf '%.2f', $affsubtotal;
464 #::logDebug("WP:".__LINE__.": Session = $Vend::Session->{id} Host = $host instid = $instId currency = $currency testmode = $testMode callbackurl = $callbackurl pw = $callpw desc = $desc fix = $fixcontact affsubtotal=$affsubtotal");
466 $host = $testhost if ($testMode > '0');# send to test url not live
468 my $ordernumber = charge_param('ordernumber') || 'etc/order.number';
469 my $wpcounter = charge_param('wpcounter') || 'etc/username.counter';
470 my $username = $Vend::Session->{'username'};
471 my $allowbilling = charge_param('allow_billing') || '';
472 my $md5pw = charge_param('md5pw') or die "No MD5 password set in route";
474 $worldpayrequest = charge_param('worldpayrequest') || $::Values->{worldpayrequest} || 'post';
478 #::logDebug("WP:".__LINE__.": Request = $worldpayrequest; un=$username, $::Values->{mv_username},");
479 $::Values->{'mv_order_number'} = '';
481 ##-----------Post Information and send customer to Worldpay------------##
482 if ($worldpayrequest eq 'post') {
483 #::logDebug("WP:".__LINE__.": Sending customer to Worldpay");
484 #::logDebug("WP:".__LINE__.": TestMode = $testMode : Host = $host");
486 my $separator = ' ';
487 my $name = "$::Values->{fname} $::Values->{lname}" || "$::Values->{b_fname} $::Values->{b_lname}";
488 my $address = "$::Values->{address1}%0A$::Values->{address2}%0A$::Values->{city}%0A$::Values->{state}" || "$::Values->{b_address1}%0A$::Values->{b_address2}%0A$::Values->{b_city}%0A$::Values->{b_state}"; #%0A is the line feed separator between address lines
489 my $postcode = "$::Values->{zip}" || $::Values->{b_zip};
490 my $country = "$::Values->{country}" || "$::Values->{b_country}";
491 my $email = "$::Values->{email}";
492 my $tel = "$::Values->{phone_night}" || "$::Values->{phone_day}"; #some may wish to use phone_day
494 $orderID = gen_order_id($opt);
495 $::Scratch->{orderID} = $orderID;
497 # Disable order number creation in log_transaction and create it here instead, unless IC is old
498 if ($::Values->{inv_no}) {
499 $purchaseID = $::Values->{inv_no};
502 # Use temporary number as the initial order number, and only replace upon successful order completion
503 $purchaseID = "$tmpPrefix".Vend::Interpolate::tag_counter("$wpcounter");
504 $Vend::Session->{mv_order_number} = $::Values->{mv_order_number} = $purchaseID if ($oldic == 1);# prevents early ICs setting order number prior to log_transaction
505 ::logDebug("WP:".__LINE__.": purchaseID=$purchaseID; $Vend::Session->{mv_order_number}");
508 my $cartId = $desc = $::Scratch->{purchaseID} = $purchaseID;
510 my $md5data = $md5pw . ":" . $amount . ":" . $instId . ":" . $affsubtotal . ":" . $currency;
511 my $signature = md5_hex($md5data);
512 #::logDebug("WP:".__LINE__.": md5pw = $md5pw md5data = $md5data signature =$signature");
515 my $redirecturl = "$host?signature=$signature&instId=$instId¤cy=$currency&testMode=$testMode&authMode=$authMode&amount=$amount&cartId=$cartId&desc=$desc&name=$name&address=$address";
516 $redirecturl .= "&postcode=$postcode&country=$country&email=$email&tel=$tel&MC_mv_order_number=$cartId&MC_callback=$callbackurl";
517 $redirecturl .= "&fixContact" if ($fixcontact == 1);
518 $redirecturl .= "&accId1=$accId1" if $accId1;
519 $redirecturl =~ s/(?:%0[da]|[\r\n]+)+//ig; ## "HTTP Response Splitting" Exploit Fix
521 $::Scratch->{'redirecturl'} = $redirecturl; # for old versions of IC needing a redirection page
522 #::logDebug("WP:".__LINE__.": redirectURL = $redirecturl");
524 # Fake the result so that IC can log the transaction
525 $result{Status} = 'success';
526 $result{MStatus} = 'success';
527 $result{'order-id'} = $orderID;
528 #::logDebug("WP".__LINE__.": resSt=$result{'Status'}; resMSt=$result{'MStatus'},resoid=$result{'order-id'}");
530 # Delete any stale baskets, ie with tmpID but without proper order numbers; only works if user is forced
531 # to login prior to ordering and uses same username
532 $dbh = dbconnectwp() or warn "###dbh failed\n";
533 $sql = "DELETE FROM transactions WHERE order_number LIKE '$tmpPrefix%' AND username='$username'";
534 $sth = $dbh->prepare("$sql") or warn "###sth failed\n";
535 $sth->execute() or die errmsg("###Transactions tbl failed") if $username;
536 $sql = "DELETE FROM orderline WHERE order_number LIKE '$tmpPrefix%' AND username='$username'";
537 $sth = $dbh->prepare("$sql") or warn "###sth failed\n";
538 $sth->execute() or die errmsg("###Transactions tbl failed") if $username;
540 $::Tag->tag({ op => 'header', body => <<EOB });
542 Location: $redirecturl
547 ####----------------Handle the callback from Worldpay--------------####
548 elsif ($worldpayrequest eq 'callback'){
550 my $newsess = $Vend::Session->{id};
551 #::logDebug("WP:".__LINE__.": Processing Callback Session = $newsess");
553 my $reporttitle = charge_param('reporttitle') || '';
554 my $update_status = charge_param('update_status') || 'pending';
555 my $dec_inventory = charge_param('dec_inventory') || '';
557 #Capture all callback fields
558 my ($transid, $check_testmode, $transstatus, $authamount, $transtime, $authcurrency, $rawauthcode, $rawauthmessage, $callbackpw, $cardtype, $countrymatch, $avs, $wafmerchmessage, $authentication, $ipaddress, $wp_order_number);
559 my $page = ::http()->{'entity'};
560 #::logDebug("WP".__LINE__.": page=\n$$page\n----------------------------------\n");
561 foreach my $line (split /\&/, $$page) {
562 $transid = $1 if ($line =~ /transId=(.*)/i); #transaction ID
563 $wp_order_number = $1 if ($line =~ /MC_mv_order_number=(.*)/);# temp CartID sent
564 $check_testmode = $1 if ($line =~ /testMode=(.*)/); #returns testmode value 0 live anything higher test mode
565 $transstatus = $1 if ($line =~ /transStatus=(.*)/); #Y=Sucess C=Cancelled
566 $authamount = $1 if ($line =~ /authAmount=(.*)/); #Authorised amount
567 $transtime = $1 if ($line =~ /transTime=(.*)/); #Time of transaction
568 $authcurrency = $1 if ($line =~ /authCurrency=(.*)/); #Currency of authorisation
569 $callbackpw = $1 if ($line =~ /callbackPW=(.*)/); #Callback password as set in admin panel
570 $rawauthmessage = $::Values->{'rawauthmessage'} = $1 if ($line =~ /rawAuthMessage=(.*)/); #Raw auth message
571 $rawauthcode = $::Values->{'rawauthcode'} = $1 if ($line =~ /rawAuthCode=(.*)/); #Raw auth message
572 $cardtype = $::Values->{'cardtype'} = $1 if ($line =~ /cardType=(.*)/); #Card type used
573 $countrymatch = $::Values->{'countrymatch'} = $1 if ($line =~ /countryMatch=(.*)/); #Y=Match N=No match B=Not available I=Country not supplied S=Country issue not available
574 $avs = $::Values->{'avs'} = $1 if ($line =~ /AVS=(.*)/); #AVS Results
575 $wafmerchmessage = $::Values->{'wafmerchmessage'} = $1 if ($line =~ /wafMerchMessage=(.*)/); #Risk result
576 $authentication = $::Values->{'authentication'} = $1 if ($line =~ /authentication=(.*)/); #VbyV or Mastercard Securecode authentication type
577 $ipaddress = $::Values->{'ipaddress'} = $1 if ($line =~ /ipAddress=(.*)/); #Shopper IP address
579 $::Values->{'cardtype'} = $cardtype =~ s/\+/ /g;
581 #::logDebug("WP:".__LINE__.": transid=$transid testmode=$check_testmode transstatus=$transstatus authamount=$authamount transtime=$transtime authcurrency=$authcurrency rawauthmessage=$rawauthmessage");
582 #::logDebug("WP:".__LINE__.": callbackpw=$callbackpw cardtype=$cardtype countrymatch=$countrymatch avs=$avs wafmerchmessage=$wafmerchmessage authentication=$authentication ipaddress=$ipaddress");
584 my $db = Vend::Data::dbref('transactions') or die errmsg("cannot open transactions table");
585 $dbh = $db->dbh() or die errmsg("cannot get handle for tbl 'transactions'");
587 #::logDebug("WP:".__LINE__.": Callback order number = $wp_order_number");
590 if (($transstatus eq 'Y') and ($callbackpw eq $callpw)) {
591 #::logDebug("WP:".__LINE__.": Transaction Successful");
593 $sth = $dbh->prepare("SELECT total_cost,email,txtype,order_number FROM transactions WHERE order_number='$wp_order_number'") or die errmsg("Cannot select from transactions tbl for $wp_order_number");
594 $sth->execute() or die errmsg("Cannot get data from transactions tbl");
595 my @d = $sth->fetchrow_array;
596 my $order_total = $d[0];
600 #generate the IC Order Number, and put in session to block old ICs from generating a second number
601 my $new_order_no = $::Values->{mv_order_number} = $Vend::Session->{mv_order_number} = Vend::Interpolate::tag_counter("$ordernumber");
602 #Check if transaction was in test mode
603 if ($check_testmode > '0') { # Transaction was in test mode
604 $update_status = $update_status .'-TEST'; #Append Test to end of order status to show order was made in test mode
605 $charged = $charged .'-TEST'; #Variable we write to txtype
607 #::logDebug("WP:".__LINE__.": new on = $new_order_no; Check testmode = $check_testmode Update Status = $update_status Set txtype = $charged");
609 #Replace temporary order number with IC order number
610 #::logDebug("WP:".__LINE__.": Replacing order number: Old TID = $old_tid with New Order No = $new_order_no");
611 $sth = $dbh->prepare("UPDATE transactions SET code='$new_order_no', order_number='$new_order_no', txtype='$charged', payment_method='Worldpay ($cardtype)', cartid='$wp_order_number' WHERE order_number='$wp_order_number'");
612 $sth->execute() or die errmsg("Cannot update transactions tbl for WP '$wp_order_number'");
613 $stho = $dbh->prepare("UPDATE orderline SET code=replace(code, '$old_tid', '$new_order_no'), order_number='$new_order_no' WHERE order_number='$old_tid'");
614 $stho->execute() or die errmsg("Cannot update transactions tbl for WP '$wp_order_number'");
616 #Log transaction information & change order status
617 #::logDebug("WP:".__LINE__.": Logging transaction details to tbl for order $new_order_no");
618 $sth = $dbh->prepare("UPDATE transactions SET status='$update_status', order_id='$transid', wp_transtime='$transtime', wp_cardtype='$cardtype', wp_countrymatch='$countrymatch', wp_avs='$avs', wp_risk='$wafmerchmessage', wp_authentication='$authentication', wp_authamount='$authamount' WHERE order_number='$new_order_no'");
619 $sth->execute() or die errmsg("Cannot update transactions tbl for worldpay order '$new_order_no'");
620 $stho = $dbh->prepare("UPDATE orderline SET status='$update_status' WHERE order_number='$new_order_no'");
621 $stho->execute() or die errmsg("Cannot update orderline tbl for worldpay order '$new_order_no'");
623 #Read the order details and cart from the database
624 $sth = $dbh->prepare("SELECT total_cost,email,order_number,fname,lname,company,address1,address2,city,state,zip,country,phone_day,fax,b_fname,b_lname,b_company,b_address1,b_address2,b_city,b_state,b_zip,b_country,shipmode,handling,order_date,lead_source,referring_url,txtype,locale,currency_locale,cart,session,salestax,shipping,b_phone,subtotal,cartid,email_copy,mail_list,free_sample FROM transactions WHERE order_number='$new_order_no'") or die errmsg("Cannot select from transactions tbl for $wp_order_number");
625 $sth->execute() or die errmsg("Cannot get data from transactions tbl");
626 my @d = $sth->fetchrow_array;
627 my $order_total = $::Values->{order_total} = $d[0];
628 my $email = $::Values->{email} = $d[1];
629 my $mv_order_number = $::Values->{mv_order_number} = $d[2];
630 my $fname = $::Values->{fname} = $d[3];
631 my $lname = $::Values->{lname} = $d[4];
632 my $company = $::Values->{company} = $d[5];
633 my $address1 = $::Values->{address1} = $d[6];
634 my $address2 = $::Values->{address2} = $d[7];
635 my $city = $::Values->{city} = $d[8];
636 my $state = $::Values->{state} = $d[9];
637 my $zip = $::Values->{zip} = $d[10];
638 my $country = $::Values->{country} = $d[11];
639 my $phone_day = $::Values->{phone_day} = $d[12];
640 my $fax = $::Values->{fax} = $d[13];
641 my $b_fname = $::Values->{b_fname} = $d[14];
642 my $b_lname = $::Values->{b_lname} = $d[15];
643 my $b_company = $::Values->{b_company} = $d[16];
644 my $b_address1 = $::Values->{b_address1} = $d[17];
645 my $b_address2 = $::Values->{b_address2} = $d[18];
646 my $b_city = $::Values->{b_city} = $d[19];
647 my $b_state = $::Values->{b_state} = $d[20];
648 my $b_zip = $::Values->{b_zip} = $d[21];
649 my $b_country = $::Values->{b_country} = $d[22];
650 my $shipmode = $::Values->{mv_shipmode} = $d[23];
651 my $handling = $::Values->{mv_handling} = $d[24];
652 my $order_date = $::Values->{order_date} = $d[25];
653 my $lead_source = $::Values->{lead_source} = $d[26];
654 my $referring_url = $::Values->{referring_url} = $d[27];
655 my $txtype = $::Values->{txtype} = $d[28];
656 my $mv_locale = $d[29];
657 my $mv_currency = $d[30] || 'GBP';
659 my $session = $d[32];
660 my $salestax = $d[33] || '0';
661 my $shipping = $d[34] || '0';
662 my $phone_night = $::Values->{phone_night} = $d[35];
663 my $subtotal = $d[36] || '0';
664 my $cartID = $::Values->{'cartid'} = $d[37];
665 my $email_copy = $::Values->{'email_copy'} = $d[38];
666 my $mail_list = $::Values->{'mail_list'} = $d[39];
667 my $free_sample = $::Values->{'free_sample'} = $d[40];
669 #todo add evening phone
670 $::Values->{'mv_shipmode'} ||= 'Standard';
671 $::Values->{mv_handling} = 1 if ($handling > '0');
672 $cartID = $wp_order_number unless defined $cartID;
673 Vend::Interpolate::tag_assign({ subtotal => "$subtotal", shipping => "$shipping", salestax => "$salestax" });
674 Vend::Interpolate::tag_assign({ handling => "$handling" }) if ($handling > '0');
680 $acart = eval ($cart);
681 #::logDebug("WP:".__LINE__.": cart=$cart Email=$email");
683 $::Values->{mv_payment} = 'Worldpay'." $::Values->{'cardtype'}";
684 $::Values->{wp_order_number} = $wp_order_number;
685 $::Session->{values}->{iso_currency_code} = $currency;
686 $::Session->{scratch}->{mv_locale} = $mv_locale;
687 $::Session->{scratch}->{mv_currency} = $mv_currency;
688 $::CGI::values{'mv_todo'} = 'submit';
689 $result{'MStatus'} = $result{'pop.status'} = 'success';
690 $result{'order-id'} = $orderID;
691 $result{'Status'} = 'OK';
692 $result{'WPStatus'} = 'success';
693 $Vend::Session->{'payment_result'} = \%result;
696 #::logDebug("WP:".__LINE__.": Shipmode = $shipmode Shipping = $shipping Tax = $salestax Handling = $handling");
699 #Set new report title with final order number and WP transaction ID
700 if ($reporttitle == '1') {
701 my $amt = sprintf '%.2f', $order_total;
702 $::Values->{mv_order_subject} = 'Order '.$new_order_no.' : CartID '.$cartID.' : WPID:'.$transid.' : '.$mv_currency.''.$amt.' : '.$charged;
706 if ($dec_inventory == '1') {
707 #Decrement item quantities in inventory table
708 my $dbi = Vend::Data::database_exists_ref('inventory') or die errmsg("cannot open inventory table");
709 my $dbhi = dbconnectwp() or die errmsg("cannot get handle for tbl 'inventory'");
710 my ($sthi, $itm, $qty);
712 foreach my $items (@{$acart}) {
713 $itm = $items->{'code'};
714 $qty = $items->{'quantity'};
715 $sthi = $dbh->prepare("UPDATE inventory SET quantity = quantity -'$qty' WHERE sku = '$itm'");
716 $sthi->execute() or die errmsg("Cannot update table inventory");
717 #::logDebug("WP:".__LINE__.": Decremented inventory for $itm by $qty");
721 # run custom final route which cascades 'copy_user' and 'main_entry', ie no receipt page.
722 Vend::Order::route_order("wp_final", @cart) if @cart;
724 undef $Vend::Session->{mv_order_number};
726 #::logDebug("WP:".__LINE__.": sid=$::Session->{'id'}; End worldpay transaction success");
729 elsif ($callbackpw ne $callpw) {
730 #This should never happen unless someone tries to simulate transactions without knowing the callback password or the password is entered incorrectly in catalog.cfg or the WP admin panel
731 #Transaction logged as txtype WP Pass Error and status passerror
732 #::logDebug("WP:".__LINE__.": Callback password was incorrect");
733 #::logDebug("WP:".__LINE__.": Logging transaction with callback password failure to tbl for order $wp_order_number");
734 $sql = "DELETE FROM transactions WHERE order_number='$wp_order_number'";
735 $sth = $dbh->prepare("$sql") or warn "sth failed\n";
736 $sth->execute() or die errmsg("Transactions tbl failed");
737 $sql = "DELETE FROM orderline WHERE order_number='$wp_order_number'";
738 $stho = $dbh->prepare("$sql") or warn "sth failed\n";
739 $stho->execute() or die errmsg("Orderline tbl failed");
743 #transaction has been cancelled
744 #::logDebug("WP:".__LINE__.": Transaction for order $wp_order_number was cancelled");
746 #log details of cancelled transaction & set archived to 1 so it won't show in admin panel
747 #::logDebug("WP:".__LINE__.": Deleting cancelled transaction details from tbl for order $wp_order_number");
748 # $dbh = dbconnectwp() or warn "dbh failed\n";
749 $sql = "DELETE FROM transactions WHERE order_number='$wp_order_number'";
750 $sth = $dbh->prepare("$sql") or warn "sth failed\n";
751 $sth->execute() or die errmsg("Transactions tbl failed");
752 $sql = "DELETE FROM orderline WHERE order_number='$wp_order_number'";
753 $sth = $dbh->prepare("$sql") or warn "sth failed\n";
754 $sth->execute() or die errmsg("Orderline tbl failed");
756 # $Vend::Session->{'payment_result'}{'MStatus'} = 'cancelled';
763 package Vend::Payment::Worldpay;