Writing credit-card interfaces
The number of credit card gateways supported by Interchange is large and growing, but so seem to be the number of companies providing gateway services, each with their own protocol for submitting transactions. If you're building an Interchange site from scratch and haven't yet chosen your payment processor, the best thing to do is pick from those already supported by Interchange out-of-the-box, since they'll have been tested for longer. However, if you already have an account with a payment gateway not already supported, then you'll need to develop a module so Interchange can speak their language.
The quick-and-dirty way to get what you want is not to start from scratch: make a copy of one of the existing payment modules that uses the same or
similar type of connection to the gateway, and then adjust it to match the new protocol as described by the gateway company's documentation.
Woah! Hold the phone for a second... what are you doing here? Before you start using this tutorial to write code that directly handles credit card numbers you should know what you're doing first. Card numbers are sensitive information, so the following rules apply:
There are two basic types of credit card processor:
- Do not store the numbers anywhere on disk. If your application needs to store numbers, and you are CISP compliant, you should do it outside of the payment interface module.
- Do not transmit numbers unencrypted. All of the reputable payment gateways accept only secure connections (usually HTTPS on port 443), but even so, make sure you are not accidentally connecting in insecure mode. The Interchange Payment infrastructure helps you take care of this automatically.
- Do not pass the number or data containing the number to unknown third-party code. Only use code that came with or was installed as part of Interchange, or a standard Perl distribution, or that was provided by the payment gateway or a party authorized by that gateway.
- On the same level, watch what you do with the various ID numbers and passwords given to you by the payment gateway for accessing their service with, too.
- Internet gateways - these are companies that operate a special server on the public Internet. You connect to them over a secure internet connection with an ID and sometimes a password, submit the transaction details, and get back an authorization code. Examples: Authorize.Net, Linkpoint, and VeriSign Payflow Pro.
- Credit card terminals - software you run on your own server that calls up a conventional credit card clearinghouse over a phone line with a modem installed on the server. Examples: CCVS, MCVE.
- The credit card number and other transaction info must be encrypted before being sent over the public Internet. That means all of them will use SSL.
- All of them need to work across a company firewall if necessary, so most will tunnel their protocol through standard HTTP POSTs and responses.
Don't Start From Scratch
To show how this is done I'll step through the development of a payment gateway module for Concord's EFSNet service. We'll need the documentation for their API (PDF), which we can find on their developer web site. To begin, we'll pick a good starting point for our project by copying an existing module and renaming it. The Authorize.Net gateway works on the same principles (POSTing forms over HTTPS), so we'll use that.cwenham@strewth:/usr/local/interchange/lib/Vend/Payment$ cp AuthorizeNet.pm EFSNet.pmThen, to make sure the module will even compile and execute properly when called, we go in and change all references from
Vend::Payment::AuthorizeNet to Vend::Payment::EFSNet:
From:
package Vend::Payment::AuthorizeNet;At the top of the module, to:
package Vend::Payment::EFSNet;Also scroll all the way to the bottom of the module and change the second reference there, too. Next, we need to change the name of the main method from
authorizenet to efsnet. Just underneath the documentation and the BEGIN section that loads the SSL libraries, you'll find this:
package Vend::Payment;
use strict;
sub authorizenet {
....
Change the sub authorizenet { to sub efsnet {. Now you have a module that calls itself efsnet, but still speaks the AuthorizeNet protocol. Interchange wants to call a Perl function with the same name that MV_PAYMENT_MODE is set to, which is why we change the name of the main function.
The other behavior of note is that all of the Interchange payment gateways are chimeras; they deliberately dope Vend::Payment with their own DNA. If you're familiar with Object Oriented Programming, you might think this is inheritance, but it isn't. The payment modules aren't actually subclassing Vend::Payment, they're injecting new code into it and letting Interchange invoke it like a new body organ, which is why there's a mysterious package Vend::Payment; line just before the main function.
Connecting to the right service
Referring to Concord's section on where to locate their transaction servers, we see that there are two servers online that we can post transactions to: the test server at testefsnet.concordebiz.com and a production server at efsnet.concordebiz.com. Since we're not ready to post real transactions yet, we'll use the test server for now. Look for a couple of lines like this: $opt->{host} ||= 'secure.authorize.net';
$opt->{script} ||= '/gateway/transact.dll';
These set up the server and script to post the transaction to, using the Perlish way of assigning defaults (so they can be overridden by MV_PAYMENT_HOST and MV_PAYMENT_SCRIPT). Change these to:
$opt->{host} ||= 'testefsnet.concordebiz.com';
$opt->{script} ||= '/efsnet.dll';
Translating the transaction names
There's about half a dozen basic transaction types to be supported, and Interchange has its own lingo to refer to them.AUTH_ONLY, for example, refers to a transaction where you simply verify that the card number is authentic, and place a hold on funds in the cardholder's account. This doesn't actually transfer the funds into your merchant account. Normally when you order a product online, the merchant is only supposed to authorize the card and put a hold on the funds, then he'll settle the transaction hours or days later when the product is shipped. Alternatively, you could issue a sale transaction, which authorizes and settles the payment in a single operation, and should only be done if you're providing the product or service at the time of the sale. Interchange supports both types, and so should our module.
So the next major block of code to change is the type_map which translates Interchange's words into the ones used by the payment gateway. AUTH_ONLY, translated into EFSNet-speak, for example, is CreditCardAuthorize. Here's the complete table, as expressed in code:
my %type_map = ( AUTH_ONLY => 'CreditCardAuthorize', CAPTURE_ONLY => 'CreditCardCapture', CREDIT => 'CreditCardCredit', PRIOR_AUTH_CAPTURE => 'CreditCardSettle', VOID => 'VoidTransaction', auth => 'CreditCardAuthorize', authorize => 'CreditCardAuthorize', mauthcapture => 'CreditCardCharge', mauthonly => 'CreditCardAuthorize', return => 'CreditCardRefund', settle_prior => 'CreditCardSettle', sale => 'CreditCardCharge', settle => 'CreditCardSettle', void => 'VoidTransaction', );There's quite a few synonyms in there, but there's no harm in making sure they're all paired up, just in case your module gets used in a legacy setting.
Filling in the form
Skipping past some of the giblets, you'll see another long and uniform block of code that sets up the form with all the transaction data in it. Here's where the customer's name, address, credit card number, expiration date, and other information such as your EFSNet Store ID and Key will get put. We'll use the protocol spec to find out what needs to go in here. The rest is just a bit of keyboard time to get something like this:my %query = (
Method => $transtype,
StoreKey => $secret,
StoreID => $user,
ApplicationID => "Interchange EFSNet Module v0.0.1",
BillingName => "$actual->{b_fname} $actual->{b_lname}",
BillingAddress => $actual->{b_address},
BillingCity => $actual->{b_city},
BillingState => $actual->{b_state},
BillingZip => $actual->{b_zip},
BillingCountry => $actual->{b_country},
BillingEmail => $actual->{email},
BillingPhone => $actual->{phone_day},
ShippingName => "$actual->{fname} $actual->{lname}",
ShippingAddress => $actual->{address},
ShippingCity => $actual->{city},
ShippingState => $actual->{state},
ShippingZip => $actual->{zip},
ShippingCountry => $actual->{country},
TransactionAmount => $amount,
AccountNumber => $actual->{mv_credit_card_number},
ExpirationMonth => $actual->{mv_credit_card_exp_month},
ExpirationYear => $actual->{mv_credit_card_exp_year},
CardVerificationValue => $actual->{cvv2},
ReferenceNumber => $order_id,
);
If you compare the above with what you see in AuthorizeNet.pm, you'll notice I've removed some of the fields that are exclusive to AuthorizeNet, such as x_ADC_URL and x_ADC_Delim_Data. EFSNet also wants the customer's first and last name in one field instead of separated into two.
The code that follows is just a bit of song-n-dance to screen the data for any illegal characters, then push everything together into a URL encoded query and submit it to EFSNet with Vend::Payment's post_data function. We'll leave that in place because it's already what we want. Our next major area of concern is reading what gets sent back.
The return trip
At the time I write this, the AuthorizeNet module is still showing its age with the following comment:# Minivend names are on the left, Authorize.Net on the rightMinivend was the original name for Interchange, back before Akopia acquired the product and rebranded it. Bits of trivia aside, what comes right after this comment is another translation table to convert EFSNet-speak back into Interchange-speak. Interchange is interested in things like whether the transaction was a success and what the authorization number is, but it wants them stored under names it recognizes. Simple enough to lay out the map like this:
my %result_map = ( qw/ pop.status ResponseCode pop.error-message ResultMessage order-id TransactionID pop.order-id TransactionID pop.auth-code ApprovalNumber pop.avs_code AVSResponseCode pop.cvv2_resp_code CVVResponseCode / );And almost at the finish line, we need to change the way the result is actually parsed. Authorize.Net returns all of its results unlabeled, in fixed order, with each field separated by a unit separator character (ASCII character 37). EFSNet returns results in "label=result" pairs, separated by ampersands. In the AuthorizeNet.pm code there was another big array called
@result to store the names of the fields, but they aren't necessary with EFSNet, so delete the whole thing, including the line that reads = split (/\037/,$page);. Replace it instead with this:
my %result;
my @results = split /\&/,$page;
foreach (@results) {
my ($key,$val) = split '=', $_;
$result{$key} = $val;
}
(Perl smart-alecs who know how to do this in one line can bite my ass.)
All that's left now is to change the way the response codes are interpreted for success or failure. Instead of:
if ($result{x_response_code} == 1) {
$result{MStatus} = 'success';
$result{'order-id'} ||= $opt->{order_id};
}
We change it to:
if ($result{ResponseCode} == 0) {
$result{MStatus} = 'success';
$result{'order-id'} ||= $opt->{order_id};
}
We also need to change the names on some of the other response codes checked, such as the AVS codes, so the final pieces look like this:
if ($result{AVSResponseCode} eq 'N') {
my $msg = $opt->{message_avs} ||
q;
$result{MErrMsg} = errmsg($msg, $result{ResultMessage});
}
else {
my $msg = $opt->{message_declined} ||
"EFSNet error: %s. Please call in your order or try again.";
$result{MErrMsg} = errmsg($msg, $result{ResultMessage});
}
Cleanup
Finally, we review the code and fix some bits and pieces.- EFSNet doesn't ask for a
refererfield, so remove the following lines:my $referer = $opt->{referer} || charge_param('referer');$opt->{extra_headers} = { Referer => $referer };
- EFSNet also doesn't need the expiration date mashed into a single field, so remove these lines:
my $exp = sprintf '%02d%02d', $actual->{mv_credit_card_exp_month}, $actual->{mv_credit_card_exp_year}; - Remove four lines under the comment "
## Authorizenet does things a bit different, ensure we are OK" that modify the expiration date month and year. - EFSNet requires that the expiration month be two digits long, so add this code in place of the four you just removed:
$actual->{mv_credit_card_exp_month} = sprintf('%02d', $actual->{mv_credit_card_exp_month}); - EFSNet also requires that the Reference Number (your order ID) is no more than 12 characters, so we'll use a mechanism built into Interchange to modify the order ID on-the-fly, passing it a chunk of code that will trim it to the last 12 characters. Put this just before the call to
gen_order_id:%order_id_check = ( efsnet => sub { my $val = shift; $val = substr($val, -12); return $val; }, ); my $order_id = gen_order_id($opt); - Some of the transaction types require certain fields to be passed, so add this code just underneath your big
%querysection that fills the form:if ($transtype =~ /(CreditCardSettle|CreditCardRefund)/) { $query{OriginalTransactionID} = $actual->{auth_code}; $query{OriginalTransactionAmount} = $opt->{original_amount} || $amount; } if ($transtype eq 'VoidTransaction') { $query{TransactionID} = $actual->{auth_code}; } if ($transtype eq 'CreditCardCapture') { $query{AuthorizationNumber} = $actual->{auth_code}; }
Testing
To test it you have to install it. If you have a test server or a test copy of Interchange or even just a test catalog, then put it there first so it doesn't affect production sites. You need to add this line in your interchange.cfg:Require module Vend::Payment::EFSNetThen re-start your Interchange daemon. Two things will happen as it restarts:
- The
BEGINblock at the top of your module will execute right away and determine which SSL libraries to use, displaying the message "Vend::Payment::EFSNet payment module initialized, using LWP and Crypt::SSLeay", or similar. - The subroutine
efsnet()will get inserted into the method space of Vend::Payment.
pages directory of any catalog you want to use for testing. This page simply borrows some of the code from a standard Interchange checkout page, and invokes the [charge] tag with all the right arguments.
Whichever method you use, be sure to change MV_PAYMENT_HOST to the test server. In EFSNet's case, this is testefsnet.concordebiz.com. You should also create a test account for yourself if the payment processor supports them. If you're using my cc_testrig.html, edit it and change the host, id, and secret parameters of the [charge] tag to fit your circumstances.
Debugging
It's likely that you'll need to see what's going on inside the new module to correct any bugs that remain. In the Authorize.Net module you copied from, you should have left several::logDebug lines intact. Now is the time to uncomment them. They still won't have any effect until you also edit the interchange.cfg file and turn on Debugging. Find the line #Variable DEBUG 1 near the top and remove the # to un-comment it. Re-start Interchange, and the debugging output of your module will be sent to /tmp/icdebug. Watch this file as you enter some more test transactions, and you'll see things like the input arguments, the queries they get translated into, and the raw results coming back from the payment gateway.
Other places to look for error reports are the catalog's error.log and Interchange's error.log.
Problems with Perl Signals and PreFork mode
Because this module and many others use code that posts an HTTPS transaction over the internet and waits for the results, it's vulnerable to a problem with the way Perl handles signals, particularly when Interchange is running in PreFork mode (where Interchange forks off multiple instances of itself to handle greater traffic). The problem usually manifests itself as a failure to get results back from the payment gateway, even though it's able to POST the transaction successfully. Without results, Interchange thinks the transaction failed. You can see this in the/tmp/icdebug file as a lack of results from the gateway. Be aware that the problem occurs intermittently, so sometimes you see it, and sometimes you don't.
The following are some suggested ways to fix the problem, should you experience it, in decreasing order of preference. Either one of these should work, but you shouldn't need to implement all three.
- Set
PERL_SIGNALS=unsafein your environment variables before starting Interchange. Example from the command line:export PERL_SIGNALS=unsafe interchange -r
- Downgrade to Perl 5.6.1.
- Change
MaxServersto0ininterchange.cfg.
Certification
Some payment processing companies are naturally wary about new code accessing their system and will require you to go through a certification stage before they'll let you talk to their production servers. In the case of EFSNet and most other processors, this simply means obtaining a list of test scenarios (see "EFSNet Certification Request Form" at the bottom), running them on their test server, and sending them a list of transaction ID numbers for each test scenario. Once their certification department has looked up those test transactions on their servers and verified that you sent and handled each query properly, they'll mail or email you a certification letter which authorizes you to use your new code on their system. This is where having a test-rig that lets you input each scenario quickly is very handy, especially when testing certain transaction types that need a previous transaction ID (such asvoid and return). Trying to recreate each scenario with the shopping-basket takes longer, and is trickier when many certification scenarios want you to charge a specific dollar amount.
For the sense of completeness, I decided to get my EFSNet module authorized for all of the credit card methods supported by the processor, even methods not normally used by Interchange such as CreditCardCredit (apply a credit to a card without a previous transaction ID), and partial refunds. You may only want to certify your code for the methods you'll actually be using, which will save you time.
To support partial refunds, I added an optional original_amount parameter to my module. Interchange might sport an interface for issuing partial refunds from the Admin UI in the future, at which point it may be necessary to change the name (or add an alias) of the parameter to whatever Interchange has settled on.
(Most merchants use the "Virtual Terminal" provided by their payment gateway to issue refunds, rather than try to do it through Interchange's Admin UI.)
Deploying the new module
Once tested and certified, your new module is ready to be used for real. This is when you'd go to your production catalog and modify theMV_PAYMENT_MODE variable to match the name of your method (efsnet in this case, after sub efsnet { in our code), clear MV_PAYMENT_HOST--if set--and change the host line in EFSNet.pm to the production servers. Finally, put your real Store ID and Store Key in MV_PAYMENT_ID and MV_PAYMENT_SECRET.
If you're also going to be distributing this module to others (perhaps donating it to the Interchange community), you should also go through the documentation section of the module and at the least change all the references from Authorize.Net to EFSNet. In mine, I changed references to IDs and passwords to Store IDs and Store Keys, plus a few notes on the partial refunds support and certification process.
Resources
The EFSNet.pm module, and other materials discussed in this article are available below:- EFSNet.pm (Also distributed with Interchange 5.1.1 and higher)
- cc_testrig.html (Gzipped, 2K)
lib/Vend/Payment directory.