1 # Vend::Payment::Protx2 - Interchange Protx Direct payment support
3 # $Id: Protx2.pm,v 1.2 2008-04-10 23:44:45 jon Exp $
4 # Based on Protx2.pm, v 2.1.2, July 2007
6 # Copyright (C) 2008 Interchange Development Group
7 # Copyright (C) 2007 Zolotek Resources Ltd. All rights reserved.
9 # Author: 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 # License along with this program; if not, write to the Free
23 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
26 package Vend::Payment::Protx2;
30 Interchange Protx Direct payment system interface
36 LWP::UserAgent and Crypt::SSLeay
38 wget - a recent version built with SSL and supporting the 'connect' timeout function.
40 =head1 QUICK START SUMMARY
42 1. Call this module in interchange.cfg with:
44 Require module Vend::Payment::Protx2
46 2. Add into products/variable.txt (tab separated):
50 3. Add a new route into catalog.cfg (options for the last entry in parentheses):
52 Route protx id YourProtxID
53 Route protx host ukvps.protx.com (ukvpstest.protx.com)
54 Route protx currency GBP (USD, EUR, others, defaults to GBP)
55 Route protx txtype PAYMENT (AUTHENTICATE, DEFERRED)
56 Route protx available yes (no, empty)
57 Route protx logzero yes (no, empty)
58 Route protx double_pay yes (no, empty)
59 Route protx logdir "path/to/log/dir"
60 Route protx protxlog yes (no, empty)
61 Route protx applyavscv2 '0': if enabled then check, and if rules apply use.
62 '1': force checks even if not enabled; if rules apply use.
63 '2': force NO checks even if enabled on account.
64 '3': force checks even if not enabled; do NOT apply rules.
65 Route protx giftaidpayment 0 (1 to donate tax to Gift Aid)
67 or put these vars into products/variable.txt instead:
69 MV_PAYMENT_ID YourProtxID Payment
70 MV_PAYMENT_MODE protx Payment
71 MV_PAYMENT_HOST ukvps.protx.com Payment
72 MV_PAYMENT_CURRENCY GBP Payment
74 and the rest as above.
76 4. Create a new locale setting for en_UK as noted in "item currency" below, and copy the
77 public space interchange/en_US/ directory to a new interchange/en_UK/ one. Ensure that any
78 other locales you might use have a correctly named directory as well. Ensure that this locale
79 is found in your version of locale.txt (and set up UK as opposed to US language strings to taste).
81 5. Create entry boxes on your checkout page for: 'mv_credit_card_issue_number', 'mv_credit_card_start_month',
82 'mv_credit_card_start_year', 'mv_credit_card_type', and optionally 'mv_credit_card_cvv2'.
86 The Vend::Payment::Protx module implements the Protx() routine for use with
87 Interchange. It is not compatible on a call level with the other Interchange
88 payment modules - Protx does things rather differently. We need to save four of
89 the returned codes for re-use when doing a RELEASE, REPEAT, or REFUND.
91 To enable this module, place this directive in C<interchange.cfg>:
93 Require module Vend::Payment::Protx2
95 This I<must> be in interchange.cfg or a file included from it.
97 Make sure CreditCardAuto is off (default in Interchange demos).
99 Note that the Protx 'Direct' system is the only one which leaves the customer on
100 your own site and takes payment in real time. Their other systems, eg Terminal
101 or Server, do not require this module.
103 Note also that Maestro cards can only be taken by the 3DSecure version of this module, not by this
104 version, as Mastercard have decreed that Maestro cards will no longer be accepted without 3DSecure.
106 While PREAUTH is still in this module, it is scheduled to be dropped on the 1st August 2007 or shortly
107 thereafter, and is only here as a backup during the changeover to AUTHENTICATE.
109 =head2 The active settings
111 The module uses several of the standard settings from the Interchange payment routes.
112 Any such setting, as a general rule, is obtained first from the tag/call options on
113 a page, then from an Interchange order Route named for the mode in catalog.cfg,
114 then a default global payment variable in products/variable.txt, and finally in
115 some cases a default will be hard-coded into the module.
121 The mode can be named anything, but the C<gateway> parameter must be set
122 to C<protx>. To make it the default payment gateway for all credit card
123 transactions in a specific catalog, you can set in C<catalog.cfg>:
125 Variable MV_PAYMENT_MODE protx
129 MV_PAYMENT_MODE protx (tab separated)
131 if you want this to cooperate with other payment systems, eg PaypalExpress or GoogleCheckout, then see
132 the documentation that comes with that system - it should be fully explained there (essentially, you
133 don't run the charge route from profiles.order but from log_transaction).
137 Your Protx vendor ID, supplied by Protx when you sign up. Various ways to state this:
141 MV_PAYMENT_ID YourProtxID Payment
143 or in catalog.cfg either of:
145 Route protx id YourProtxID
146 Variable MV_PAYMENT_ID YourProtxID
150 The transaction type is one of: PAYMENT, AUTHENTICATE or DEFERRED for an initial purchase
151 through the catalogue, and then can be one of: REFUND, RELEASE, REPEAT for payment
152 operations through the virtual terminal.
154 The transaction type is taken firstly from a dynamic variable in the page, meant
155 primarily for use with the 'virtual payment terminal', viz: 'transtype' in a select box
156 though this could usefully be taken from a possible entry in the products database
157 if you have different products to be sold on different terms; then falling back to
158 a 'Route txtype AUTHENTICATE' entry in catalog.cfg; then falling back to a global
159 variable in variable.txt, eg 'MV_PAYMENT_TXTYPE AUTHENTICATE Payment'; and finally
160 defaulting to 'PAYMENT' hard-coded into the module. This variable is returned to
161 the module and logged using the value returned from Protx, rather than a value from
162 the page which possibly may not exist.
166 If 'yes', then the module will check that the gateway is responding before sending the transaction.
167 If it fails to respond within 9 seconds, then the module will go 'off line' and log the transaction
168 as though this module had not been called. It will also log the txtype as 'OFFLINE' so that you
169 know you have to put the transaction through manually later (you will need to capture the card
170 number to do this). The point of this is that your customer has the transaction done and dusted,
171 rather than being told to 'try again later' and leaving for ever. If not explicitly 'yes',
172 defaults to 'no'. NB: if you set this to 'yes', then add into the etc/report that is sent to you:
173 Txtype = [calc]($Session->{payment_result} || {})->{TxType};[/calc]. Note that you need to have
174 a recent version of wget which supports '--connect-timeout' to run this check. Note also that,
175 as this transaction has not been logged anywhere on the Protx server, you cannot use their
176 terminal to process it. You must use the PTIPM which includes a function for this purpose; ie,
177 it updates the existing order number with the new payment information returned from Protx. Note
178 further that if you have Protx set up to require the CV2 value, then the PTIPM will disable
179 CV2 checking at run-time by default for such a transaction (logging the CV2 value breaks Visa/MC
180 rules and so it can't be legally available for this process).
184 If 'yes', then the module will log a transaction even if the amount sent is zero (which the
185 gateway would normally reject). The point of this is to allow a zero amount in the middle of a
186 subscription billing series for audit purposes. If not explicitly 'yes', defaults to 'no'.
187 Note: this is only useful if you are using an invoicing system or the Payment Terminal, both of which
188 by-pass the normal IC processes. IC will allow an item to be processed at zero total price but simply
189 bypasses the gateway when doing so.
193 If 'yes, then if the response from Protx is read as empty (ie, zero bytes) the result is forced to
194 'success' and the transaction logged as though the customer has paid. There are two markers set to
197 $Session->{payment_result}{TxType} will be NULL,
199 $Session->{payment_result}{StatusDetail} will be: 'UNKNOWN status - check with Protx before dispatching goods'
201 and you should include these into the report emailed to you.
205 Protx requires that the card type be sent. Valid types are: VISA, MC, AMEX, DELTA, SOLO, UKE,
206 JCB, DINERS (UKE is Visa Electron issued in the UK). MAESTRO is no longer accepted without 3DSecure.
207 This may optionally be determined by the module using regexes, or you may use a select box on the page.
208 If there is an error in the regex match in this module due to a change in card ranges or some other
209 fault, then Protx will refuse the transaction and return an error message to the page. Using a select
210 box on the page automatically overrides use of the internal option. In the interests of robust
211 reliability it is *strongly* recommended that you use a select box.
213 You may display a select box on the checkout page like so:
215 <select name="mv_credit_card_type">
217 option=mv_credit_card_type
230 <option value="[loop-code]">[loop-param label]</option>
236 Protx requires that a currency code be sent, using the 3 letter ISO standard,
237 eg, GBP, EUR, USD. The value is taken firstly from either a page setting or a
238 possible value in the products database, viz 'iso_currency_code'; then falling back
239 to the locale setting - for this you need to add to locale.txt:
241 code en_UK en_EUR en_US
242 iso_currency_code GBP EUR USD
244 It then falls back to a 'Route protx currency EUR' type entry in catalog.cfg;
245 then falls back to a global variable (eg MV_PAYMENT_CURRENCY EUR Payment); and
246 finally defaults to GBP hard-coded into the module. This variable is returned to
247 the module and logged using the value returned from Protx, rather than a value from
248 the page which possibly may not exist.
252 This is sent to Protx as mv_credit_card_cvv2. Put this on the checkout page:
254 CVV2: <input type=text name=mv_credit_card_cvv2 value='' size=6>
256 but note that under PCI rules you must not log this value anywhere.
260 This is used for some debit cards, and taken from an input box on the checkout page:
262 Card issue number: <input type=text name=mv_credit_card_issue_number value='' size=6>
266 This is used for some debit cards, and is taken from select boxes on the
267 checkout page in a similar style to those for the card expiry date. The labels to be
268 used are: 'mv_credit_card_start_month', 'mv_credit_card_start_year'. Eg:
270 <select name="mv_credit_card_start_year">
271 [loop option=start_date_year lr=1 list=`
272 my $year = $Tag->time( '', { format => '%Y' }, '%Y' );
274 for ($year - 7 .. $year) {
277 $out .= "$last_two\t$_\n";
281 <option value="[loop-code]">[loop-pos 1]</option>
287 To choose the directory used for logging both the Protx latency log and the double
288 payment safeguard record, set in catalog.cfg:
290 Route protx logdir "path/to/log/dir"
292 It must be relative to the catalog root directory if you have
293 NoAbsolute set for this catalog in interchange.cfg.
295 If logdir is not set, it defaults to the system /tmp.
297 A somewhat dangerous option allows the payment page to specify the
298 logdir in a form variable, like this:
300 <input type="hidden" name="logdir" value='your_choice_here'>
302 This allows an individual user to have his own logs in a shared hosting
303 environment. However, it also allows a creative end-user to create
304 arbitrary empty files or update timestamps of existing files.
306 Because of the potential for abuse, this option is not allowed unless you set
307 a special route variable indicating you want it:
309 Route protx logdir_from_user_allowed 1
311 =item Protx API v2.22 extra functions
315 0 = If AVS/CV2 enabled then check them. If rules apply, use rules. (default)
316 1 = Force AVS/CV2 checks even if not enabled for the account. If rules apply, use rules.
317 2 = Force NO AVS/CV2 checks even if enabled on account.
318 3 = Force AVS/CV2 checks even if not enabled for the account but DON'T apply any rules.
319 You may pass this value from the page as 'applyavscv2' to override the payment route setting.
321 CustomerName: optional, may be different to the cardholder name
325 GiftAidPayment: set to -
326 0 = This transaction is not a Gift Aid charitable donation(default)
327 1 = This payment is a Gift Aid charitable donation and the customer has AGREED to donate the tax.
328 You may pass this value from the page as 'giftaidpayment'
330 ClientIPAddress: will show in Protx reports, and they will attempt to Geo-locate the IP.
332 =item Encrypted email with card info
334 If you want to add the extra fields (issue no, start date) to the PGP message
335 emailed back to you, then set the following in catalog.cfg:
337 Variable<tab>MV_CREDIT_CARD_INFO_TEMPLATE Card type: {MV_CREDIT_CARD_TYPE}; Card no: {MV_CREDIT_CARD_NUMBER}; Expiry: {MV_CREDIT_CARD_EXP_MONTH}/{MV_CREDIT_CARD_EXP_YEAR}; Issue no: {MV_CREDIT_CARD_ISSUE_NUMBER}; StartDate: {MV_CREDIT_CARD_START_MONTH}/{MV_CREDIT_CARD_START_YEAR}
341 The Protx test site is ukvpstest.protx.com, and their live site is
342 ukvps.protx.com. Enable one of these in MV_PAYMENT_HOST in variable.txt
343 (*without* any leading https://) or as 'Route protx host ukvpstest.protx.com' in
348 NB: Protx have removed PREAUTH from their protocol and replaced it with AUTHENTICATE/AUTHORISE.
350 An AUTHENTICATE will validate the card and store the card details on Protx's system for up to 90 days.
351 Against this you may AUTHORISE for any amount up to 115% of the original value.
353 A DEFERRED will place a shadow ('block') on the funds for seven days (or so, depending
354 on the acquiring bank). Against a DEFERRED you may do a RELEASE to settle the transaction.
356 A PAYMENT will take the funds immediately. Against a PAYMENT, you may do a
359 A RELEASE is performed to settle a DEFERRED. Payment of the originally specified
360 amount is guaranteed if the RELEASE is performed within the seven days for which
361 the card-holder's funds are 'blocked'.
363 A REFUND may be performed against a PAYMENT, RELEASE, AUTHORISE or REPEAT. It may be for a
364 partial amount or the entire amount, and may be repeated with several partial
365 REFUNDs so long as the total does not exceed the original amount.
367 A DIRECTREFUND sends funds from your registered bank account to the nominated credit card.
368 This does not need to refer to any previous transaction codes, and is useful if you need to
369 make a refund but the customer's card has changed or the original purchase was not made by card.
373 =head2 Virtual Payment Terminal
375 This has now been split out from this module, and may be found as the rather pretentiously named
376 Payment Terminal Interchange Plug-in Module (PTIPM), also on http://kiwi.zolotek.net. The PTIPM
377 does refunds and repeats, directrefunds, and converts offline transactions to online ones. Being a
378 plugin to the Interchange Admin Panel it integrates these operations into your database.
380 =head1 TROUBLESHOOTING
382 Only the test card numbers given below will be successfully
383 authorised (all other card numbers will be declined).
385 VISA 4929 0000 0000 6
386 MASTERCARD 5404 0000 0000 0001
387 DELTA 4462000000000003
388 SOLO 6334900000000005 issue 1
389 DOMESTIC MAESTRO 5641 8200 0000 0005 issue 01 (should be rejected now)
390 AMEX 3742 0000 0000 004
391 ELECTRON 4917 3000 0000 0008
392 JCB 3569 9900 0000 0009
393 DINERS 3600 0000 0000 08
395 You'll also need to supply the following values for CV2, Billing Address Numbers and Billing Post Code
396 Numbers. These are the only values which will return as Matched on the test server. Any other values
397 will return a Not Matched on the test server.
400 Billing Address Numbers 88
401 Billing Post Code Numbers 412
409 Make sure you "Require"d the module in interchange.cfg:
411 Require module Vend::Payment::Protx2
415 Make sure either Net::SSLeay or Crypt::SSLeay and LWP::UserAgent are installed
416 and working. You can test to see whether your Perl thinks they are:
418 perl -MNet::SSLeay -e 'print "It works\n"'
420 perl -MLWP::UserAgent -MCrypt::SSLeay -e 'print "It works\n"'
422 If either one prints "It works." and returns to the prompt you should be OK
423 (presuming they are in working order otherwise).
427 Check the error logs, both catalogue and global. Make sure you set your payment
428 parameters properly. Try an order, then put this code in a page:
432 my $string = $Tag->uneval( { ref => $Session->{payment_result} });
434 $string =~ s/,/,\n/g;
439 That should show what happened.
443 If you have unexplained and unlogged errors then check you have allowed the new database fields to
444 be NULL. If MySQL tries to write to a field that is marked NOT NULL then it will fail silently.
448 If you have a PGP/GPG failure when placing an order through your catalogue
449 then this may cause the module to be immediately re-run. As the first run would
450 have been successful, meaning that both the basket and the credit card information
451 would have been emptied, the second run will fail. The likely error message within
452 the catalogue will be:
453 "Can't figure out credit card expiration". Fixing PGP/GPG will fix this error.
455 If you get the same error message within the Virtual Terminal, then you haven't
456 set the order route as noted above.
460 If all else fails, Zolotek and other consultants are available to help
461 with integration for a fee.
467 http://kiwi.zolotek.net is the home page with the latest version. Also to be found on
468 Kevin Walsh's excellent Interchange site, http://interchange.rtfm.info.
472 Lyn St George <info@zolotek.net>, based on original code by Mike Heins
473 <mike@perusion.com> and others.
477 Hillary Corney (designersilversmiths.co.uk), Jamie Neil (versado.net),
478 Andy Mayer (andymayer.net) for testing and suggestions.
492 package Vend::Payment;
494 import Net::SSLeay qw(post_https make_form make_headers);
495 $selected = "Net::SSLeay";
498 $Vend::Payment::Have_Net_SSLeay = 1 unless $@;
500 unless ($Vend::Payment::Have_Net_SSLeay) {
503 package Vend::Payment;
504 require LWP::UserAgent;
505 require HTTP::Request::Common;
506 require Crypt::SSLeay;
507 import HTTP::Request::Common qw(POST);
508 $selected = "LWP and Crypt::SSLeay";
511 $Vend::Payment::Have_LWP = 1 unless $@;
514 unless ($Vend::Payment::Have_Net_SSLeay or $Vend::Payment::Have_LWP) {
515 die __PACKAGE__ . " requires Net::SSLeay or Crypt::SSLeay";
518 ::logGlobal("%s v2.1.2 payment module initialised, using %s", __PACKAGE__, $selected)
523 package Vend::Payment;
527 my ($vendor, $amount, $actual, $opt);
529 # Amount sent to Protx, in 2 decimal places with any cruft removed.
530 # Defaults to 'amount' from the Accounts IPM or an invoicing system, falling back to IC input
531 $amount = $::Values->{amount} || Vend::Interpolate::total_cost();
532 $amount =~ s/^\D+//g;
534 $amount = sprintf '%.2f', $amount;
536 # Transaction type sent to Protx.
537 my $txtype = $::Values->{transtype} || charge_param('txtype') || $::Variable->{MV_PAYMENT_TRANSACTION} || 'PAYMENT';
538 my $accountType = $::Values->{account_type} || charge_param('account_type') || 'E';
539 my $payID = $::Values->{inv_no} || $::Session->{mv_transaction_id} || $::Session->{id}.$amount;
543 # is logdir allowed to come from user?
544 if (charge_param('logdir_from_user_allowed')) {
545 $logdir = $::Values->{logdir};
547 elsif ($::Values->{logdir}) {
548 ::logError("%s: user-specified logdir not allowed without route logdir_from_user 1", __PACKAGE__);
551 # was logdir specified in route?
552 $logdir ||= charge_param('logdir');
553 my $default_logdir = '/tmp';
555 $logdir = $default_logdir;
557 # validate logdir is valid
558 elsif (! Vend::File::allowed_file("$logdir/TEST_FILE_NAME")) {
559 ::logError("%s: using logdir %s instead of disallowed %s", __PACKAGE__, $default_logdir, $logdir);
560 $logdir = $default_logdir;
562 $logdir = Vend::File::make_absolute_file($logdir);
564 my $logzero = charge_param('logzero') || 'no';
565 my $available = charge_param('available') || 'no';
566 my $logempty = $::Values->{logempty} || charge_param('logempty') || 'no';
567 my $double_pay = $::Values->{double_pay} || charge_param('double_pay') || 'no';
568 my $findcard = charge_param('find_card_type') || 'no'; # yes for auto, page for input, no for IC
569 my $description = charge_param('description') || $::Variable->{COMPANY};
570 $description = substr($description,0,99);
571 my $applyAVSCV2 = $::Values->{applyavscv2} || charge_param('applyavscv2') || '0';
573 # if payment is logged as made, raise an error message and exit
575 if ($txtype =~ /DEFERRED/i) {
576 $marker = "$logdir/pre-$payID";
579 $marker = "$logdir/paid-$payID";
584 # check for double payment only if using the payment terminal or making an invoice payment. Allow the
585 # payment terminal to override this check to 'off' and allow identical amounts to be processed within
587 if (($::Values->{mv_order_route} =~ /ptipm_route|protx_vt_route/i) and (-e $marker) and ($double_pay eq 'yes')) {
588 unless ($txtype =~ /REFUND|VOID|ABORT/) {
589 $result{MErrMsg} = "Payment for this transaction $marker has already been made - thank you";
593 # wrap around everything to bottom
595 my %actual = map_actual();
599 #::logDebug("actual map result: " . ::uneval($actual));
600 $vendor = $opt->{id} || charge_param('id') || $::Variable->{MV_PAYMENT_ID};
601 $opt->{host} = charge_param('host') || $::Variable->{MV_PAYMENT_HOST} || 'ukvpstest.protx.com';
602 $opt->{use_wget} = charge_param('use_wget') || '1';
603 $opt->{port} = '443';
605 if ($txtype =~ /DEFERRED|PAYMENT|AUTHENTICATE|PREAUTH/i) {
606 $opt->{script} = '/vspgateway/service/vspdirect-register.vsp';
608 elsif ($txtype =~ /RELEASE/i) {
609 $opt->{script} = '/vspgateway/service/release.vsp';
611 elsif ($txtype =~ /DIRECTREFUND/i) {
612 $opt->{script} = '/vspgateway/service/directrefund.vsp';
614 elsif ($txtype =~ /REFUND/i) {
615 $opt->{script} = '/vspgateway/service/refund.vsp';
617 elsif ($txtype =~ /VOID/i) {
618 $opt->{script} = '/vspgateway/service/void.vsp';
620 elsif ($txtype =~ /CANCEL/i) {
621 $opt->{script} = '/vspgateway/service/cancel.vsp';
623 elsif ($txtype =~ /ABORT/i) {
624 $opt->{script} = '/vspgateway/service/abort.vsp';
626 elsif ($txtype =~ /MANUAL/i) {
627 $opt->{script} = '/vspgateway/service/manualpayment.vsp';
629 elsif ($txtype =~ /REPEAT|REPEATDEFERRED/i) {
630 $opt->{script} = '/vspgateway/service/repeat.vsp';
632 elsif ($txtype =~ /AUTHORISE/i) {
633 $opt->{script} = '/vspgateway/service/authorise.vsp';
638 mv_credit_card_exp_month
639 mv_credit_card_exp_year
640 mv_credit_card_start_month
641 mv_credit_card_start_year
642 mv_credit_card_issue_number
643 mv_credit_card_number
646 next unless defined $opt->{$_};
647 $actual->{$_} = $opt->{$_};
650 my $ccnum = $actual->{mv_credit_card_number};
652 $actual->{mv_credit_card_exp_month} =~ s/\D//g;
653 $actual->{mv_credit_card_exp_year} =~ s/\D//g;
654 $actual->{mv_credit_card_exp_year} =~ s/\d\d(\d\d)/$1/;
656 my $startDateMonth = $actual->{mv_credit_card_start_month} || $::Values->{mv_credit_card_start_month} || $::Values->{start_date_month} || 01;
657 $startDateMonth =~ s/\D//g;
659 my $startDateYear = $actual->{mv_credit_card_start_year} || $::Values->{mv_credit_card_start_year} || $::Values->{start_date_year} || 06;
660 $startDateYear =~ s/\D//g;
661 $startDateYear =~ s/\d\d(\d\d)/$1/;
663 my $issue = $actual->{mv_credit_card_issue_number} || $::Values->{mv_credit_card_issue_number} || $::Values->{card_issue_number};
666 my $cvv2 = $actual->{mv_credit_card_cvv2} || $::Values->{cvv2};
669 # overide the configured AVSCV2 setting
670 if($txtype =~ /REPEAT|RELEASE|REFUND/i) {
674 my $exp = sprintf '%02d%02d', $actual->{mv_credit_card_exp_month}, $actual->{mv_credit_card_exp_year};
677 if (!$startDateMonth) {
681 $startDate = sprintf '%02d%02d', $startDateMonth, $startDateYear;
686 if ($::Values->{mv_credit_card_type}) {
687 $cardType = $::Values->{mv_credit_card_type};
690 if ($ccnum =~ /^4(?:5085[0-9]|91880)\d{10}$/) {$cardType = 'UKE'}
691 elsif ($ccnum =~ /^4917(?:3[0-3]|4(?:[0-2]|[9])|5[28])\d{10}$/) {$cardType = 'UKE'}
693 elsif ($ccnum =~ /^4462[0-9][0-9]\d{10}$/) {$cardType = 'Delta'}
694 elsif ($ccnum =~ /^45(?:397[89]|4313|443[2-5])\d{10}$/) {$cardType = 'Delta'}
695 elsif ($ccnum =~ /^4547(?:[2][5-9]|[3][0-9]|[4][0-5])\d{10}$/) {$cardType = 'Delta'}
696 elsif ($ccnum =~ /^49(?:09[67][0-9]|218[12]|8824)\d{10}$/) {$cardType = 'Delta'}
698 elsif ($ccnum =~ /^6011\d{12}$/) {$cardType = 'Discover'}
699 elsif ($ccnum =~ /^3(?:6\d{12}|0[0-5]\d{11})$/) {$cardType = 'Dinersclub'}
700 elsif ($ccnum =~ /^38\d{12}$/) {$cardType = 'Carteblanche'}
701 elsif ($ccnum =~ /^2(?:014|149)\d{11}$/) {$cardType = 'Enroute'}
702 elsif ($ccnum =~ /^(?:3\d{15}|2131\d{11}|1800\d{11})$/) {$cardType = 'JCB'}
704 elsif ($ccnum =~ /^490(?:30[2-9]|33[5-9]|340|52[5-9])\d{10,12}$/) {$cardType = 'MAESTRO'}
705 elsif ($ccnum =~ /^4911(?:0[0-2]|7[4-9]|8[0-2])\d{12,14}$/) {$cardType = 'MAESTRO'}
706 elsif ($ccnum =~ /^4936[0-9][0-9]\d{10,13}$/) {$cardType = 'MAESTRO'}
707 elsif ($ccnum =~ /^564182\d{10}$/) {$cardType = 'MAESTRO'}
708 elsif ($ccnum =~ /^633(?:110|3[0-9][0-9]|461)\d{10,12,13}$/) {$cardType = 'MAESTRO'}
709 elsif ($ccnum =~ /^6759\d{12,14,15}$/) {$cardType = 'MAESTRO'}
711 elsif ($ccnum =~ /^49030[2-9]\d{12}$/) {$cardType = 'Solo'}
712 elsif ($ccnum =~ /^63345[0-9]\d{10}$/) {$cardType = 'Solo'}
713 elsif ($ccnum =~ /^63346([0]|[2-9])\d{10}$/) {$cardType = 'Solo'}
714 elsif ($ccnum =~ /^6334[7-9][0-9]\d{10,12,13}$/) {$cardType = 'Solo'}
715 elsif ($ccnum =~ /^6767[0-9][0-9]\d{10,12,13}$/) {$cardType = 'Solo'}
717 elsif ($ccnum =~ /^4(?:\d{12}|\d{15})$/) {$cardType = 'Visa'}
718 elsif ($ccnum =~ /^5[1-5]\d{14}$/) {$cardType = 'MC'}
719 elsif ($ccnum =~ /^3[47]\d{13}$/) {$cardType = 'Amex'}
722 # Mastercard require Maestro to use 3ds now.
723 if ($cardType =~ /Switch|Maestro/i) {
724 $result{MStatus} = $result{'pop.status'} = 'failed';
725 $result{MErrMsg} = "Sorry, we do not accept Maestro cards";
729 my $cardRef = $actual->{mv_credit_card_number};
730 $cardRef =~ s/^(\d\d).*(\d\d\d\d)$/$1****$2/;
732 # Prefer billing values but fall back to shipping values.
733 my $billingAddress = sprintf '%s, %s, %s, %s',
734 $actual->{b_address} || $actual->{address},
735 $actual->{b_city} || $actual->{city},
736 $actual->{b_state} || $actual->{state},
737 $actual->{b_country} || $actual->{country};
739 my $deliveryAddress = sprintf '%s, %s, %s, %s',
745 my $cardHolder = $actual->{b_name} || '$actual->{b_fname} $actual->{b_lname}'
746 || $actual->{name} || '$actual->{fname} $actual->{lname}';
748 my $billingPostCode = $actual->{b_zip} || $actual->{zip};
749 my $deliveryPostCode = $actual->{zip} || $actual->{b_zip};
750 my $customerName = $actual->{name} || '$actual->{fname} $actual->{lname}' || $cardHolder;
751 my $contactNumber = $actual->{phone_day} || $actual->{phone_night};
752 my $customerEmail = $actual->{email};
753 my $contactFax = $::Values->{fax} || '';
754 my $giftAidPayment = $::Values->{giftaidpayment} || charge_param('giftaidpayment') || '0';
755 my $authCode = $::Values->{authcode} || '';
756 my $clientIPAddress = $CGI::remote_addr;
758 # VendorTxCode generated here.
760 my $order_id = gen_order_id($opt);
761 if ($txtype =~ /RELEASE|VOID|ABORT/i) {
762 $vendorTxCode = $::Values->{OrigVendorTxCode};
765 $vendorTxCode = $order_id;
768 # ISO currency code sent to Protx, from the page or fall back to config files.
769 my $currency = $::Values->{iso_currency_code} || $::Values->{currency_code} || $Vend::Cfg->{Locale}{iso_currency_code}
770 || charge_param('currency') || $::Variable->{MV_PAYMENT_CURRENCY} || 'GBP';
772 my $psp_host = $opt->{host};
774 # The string sent to Protx.
777 VendorTxCode => $vendorTxCode,
779 AccountType => $accountType,
780 VPSProtocol => '2.22',
781 Apply3DSecure => '2',
783 if ($txtype =~ /REFUND|REPEAT|AUTHORISE/) {
784 $query{RelatedVPSTxID} = $::Values->{RelatedVPSTxID};
785 $query{RelatedVendorTxCode} = $::Values->{RelatedVendorTxCode};
786 $query{RelatedSecurityKey} = $::Values->{RelatedSecurityKey};
787 $query{Description} = $description;
788 $query{Amount} = $amount;
790 if ($txtype =~ /REFUND|REPEAT/) {
791 $query{RelatedTxAuthNo} = $::Values->{RelatedTxAuthNo};
792 $query{Currency} = $currency;
794 if ($txtype =~ /VOID|ABORT|CANCEL/i) {
795 $query{VPSTxID} = $::Values->{OrigVPSTxID};
796 $query{SecurityKey} = $::Values->{OrigSecurityKey};
798 if ($txtype =~ /VOID|ABORT/i) {
799 $query{TxAuthNo} = $::Values->{OrigTxAuthNo};
801 if ($txtype =~ /DIRECTREFUND|DEFERRED|PAYMENT|PREAUTH|AUTHENTICATE|MANUAL/i) {
802 $query{CardType} = $cardType;
803 $query{CardNumber} = $ccnum;
804 $query{IssueNumber} = $issue;
805 $query{CardHolder} = $cardHolder;
806 $query{Description} = $description;
807 $query{Amount} = $amount;
808 $query{Currency} = $currency;
809 $query{StartDate} = $startDate;
810 $query{ExpiryDate} = $exp
812 if ($txtype =~ /PAYMENT|DEFERRED|PREAUTH|AUTHENTICATE|MANUAL/i) {
813 $query{BillingAddress} = $billingAddress;
814 $query{DeliveryAddress} = $deliveryAddress;
815 $query{BillingPostCode} = $billingPostCode;
816 $query{DeliveryPostCode} = $deliveryPostCode;
817 $query{CustomerName} = $customerName;
818 $query{ContactNumber} = $contactNumber;
819 $query{ContactFax} = $contactFax;
820 $query{CustomerEmail} = $customerEmail;
821 $query{GiftAidPayment} = $giftAidPayment;
822 $query{ClientIPAddress} = $clientIPAddress;
823 $query{AuthCode} = $authCode;
826 if ($txtype =~ /PAYMENT|DEFERRED|PREAUTH|AUTHENTICATE|AUTHORISE/i) {
827 $query{ApplyAVSCV2} = $applyAVSCV2;
830 #::logDebug("Sent to Protx: " . ::uneval(\%query));
832 # Test for gateway availability, and if not available optionally go off-line and complete
833 # transaction for manual processing later. Also go off-line if amount is zero, so as to log the
834 # transaction and email a receipt for audit purposes (useful mainly for subscription billing).
838 #::logDebug("Protx809: available=$available, amount=$amount, order_route=$::Values->{mv_order_route}, logzero=$logzero\n");
840 if (($available eq 'yes') and ($amount > 0) and ($::Values->{mv_order_route} !~ /ptipm_route|protx_vt_route/i)) {
841 my $CMD = '/usr/bin/wget -nv --spider -T9 -t1';
842 open (my $in, "$CMD https://$psp_host 2>&1 |") || die "Could not open pipe to wget: $!\n";
845 # $in = 'test'; # testing only, will force offline mode
846 if ($in =~ /^200 OK$/) {
850 $request = 'offline';
853 elsif (($::Values->{mv_order_route} =~ /ptipm_route|protx_vt_route/i) and ($amount > 0)) {
856 elsif (($available ne 'yes') and ($amount > 0)) {
859 elsif (($amount == 0) and ($logzero eq 'yes')) {
863 if ($request eq 'psp') {
865 my $post = post_data($opt, \%query);
866 my $response = $post->{status_line};
867 my $page = $post->{result_page};
869 #::logDebug("Response from Protx:\n$page \nend of response from Protx\n\n");
871 $result{TxType} = $txtype;
872 $result{Currency} = $currency;
873 $result{CardRef} = $cardRef;
874 $result{CardType} = $cardType;
875 $result{ExpAll} = $exp;
876 $result{amount} = $amount;
877 $result{request} = $request;
878 $result{IP} = $clientIPAddress;
879 if ($cardType eq 'UKE') { $result{CardType} = 'Electron'; }
880 $result{CardInfo} = "$result{CardType}, $cardRef, $actual->{mv_credit_card_exp_month}/$actual->{mv_credit_card_exp_year}";
882 for my $line (split /\r\n/, $page) {
883 if ($line =~ /VPSTxID=(.*)/i) { $result{VPSTxID} = $1; }
884 if ($line =~ /^Status=(.*)/i) { $result{Status} = $1; }
885 if ($line =~ /StatusDetail=(.*)/i) { $result{StatusDetail} = $1; }
886 if ($line =~ /TxAuthNo=(.*)/i) { $result{TxAuthNo} = $1;}
887 if ($line =~ /SecurityKey=(.*)/i) { $result{SecurityKey} = $1; }
888 if ($line =~ /AVSCV2=(.*)/i) { $result{AVSCV2} = $1; }
889 if ($line =~ /VPSProtocol=(.*)/i) { $result{VPSProtocol} = $1; }
890 if ($line =~ /AddressResult=(.*)/i) { $result{AddressResult} = $1; }
891 if ($line =~ /PostCodeResult=(.*)/i) { $result{PostCodeResult} = $1; }
892 if ($line =~ /CV2Result=(.*)/i) { $result{CV2Result} = $1; }
895 if ($txtype =~ /PAYMENT|DEFERRED|MANUAL|PREAUTH|AUTHENTICATE|AUTHORISE/i) {
897 if ($result{Status} =~ /OK$|REGISTERED/i) {
898 $result{MStatus} = $result{'pop.status'} = 'success';
899 $result{'order-id'} ||= $opt->{order_id};
901 elsif ($result{Status} !~ /OK$|REGISTERED/i) {
902 $result{MStatus} = $result{'pop.status'} = 'failed';
903 if ($result{StatusDetail} =~ /AVS/i) {
904 $result{MErrMsg} = "Data mismatch<br>Please ensure that your address matches the address on your credit-card statement, and that the card security number matches the code on the back of your card\n";
907 $result{MErrMsg} = "$result{StatusDetail} $result{Status}";
910 elsif (!$result{Status}) {
911 # If the status response is read as empty, the customer will try to repeat the order. This next
912 # will force the transaction to success in this case, thus preventing any attempt to repeat the
913 # order, but flag it so that the merchant knows to manually check with Protx to see if they have
914 # a valid transaction. There are different views on this approach - use with care.
915 if ($logempty eq 'yes') {
916 $result{MStatus} = $result{'pop.status'} = 'success';
917 $result{'order-id'} ||= $opt->{order_id};
918 $result{TxType} = 'NULL';
919 $result{StatusDetail} = 'UNKNOWN status - check with Protx before dispatching goods';
923 $result{MStatus} = $result{'pop.status'} = 'failed';
924 $result{MErrMsg} = "$result{StatusDetail}";
928 # these next can only be done through a virtual terminal
929 elsif($txtype =~ /RELEASE|REPEAT|REFUND|DIRECTREFUND|VOID|ABORT/i ) {
930 if ($result{Status} =~ /OK|REGISTERED/i) {
931 $result{MStatus} = $result{'pop.status'} = 'success';
932 $result{'order-id'} ||= $opt->{order_id};
935 $result{MStatus} = $result{'pop.status'} = 'failed';
936 $result{MErrMsg} = "$result{Status} $result{StatusDetail}";
940 elsif ($request eq 'offline') {
941 # end of PSP request, now for either OFFLINE or LOG
942 # force result to 'success' so that transaction completes off-line
943 $result{MStatus} = 'success';
944 $result{'order-id'} ||= $opt->{order_id};
945 $result{TxType} = 'OFFLINE';
947 elsif ($request eq 'log') {
948 # force result to 'success' so that transaction completes off-line
949 $result{MStatus} = 'success';
950 $result{'order-id'} ||= $opt->{order_id};
951 $result{TxType} = 'LOG';
954 # if payment is confirmed as OK by Protx, log this for checking above
955 # if ($result{Status} =~ /OK|REGISTERED/i) {
956 if (open my $touch, '>>', $marker) {
960 ::logError("%s: error updating timestamp of %s: %s", __PACKAGE__, $marker, $!);
964 } # close double payment marker
969 package Vend::Payment::Protx2;