I was spending some time today integrating LinkPoint into my Credit Card Processing classes into my West Wind Web Store for both the .NET and Fox versions. I’ve integrated a number of providers over the years and working with these Credit Card APIs is always a chore mainly because the documentation for these services is always bad.
LinkPoint has to be among the worst if not the worst I’ve seen.
I used LinkPoint about 10 years ago as my first online Merchant gateway and the configuration and setup and documentation was horrible then and it doesn’t seem like it has improved much since then. The API doesn’t look much different – the same concepts are used except that Linkpoint has add some ‘XML features’ to the interface. Note the quotes.
I find this an interesting business proposition. If you are a provider for an Internet based service, wouldn’t you try to make your product as easy as possible to use for your customers? To attract customers (meaning attracting developers who will have to implement this interface)? To keep support calls down with stupid questions like – ‘what do I need to install platform X?’.
Instead, LinkPoint seems to go out of their way to make their documentation as scattered as possible, not providing you clear documentation on how to install each of the different platform APIs (they support, COM, .NET, C++, PHP, Java and more, but don’t provide clear installation instructions for at least the COM and .NET ones). I downloaded the .NET version of the SDK, but other than the examples, there’s nothing .NET specific in this SDK. Not a word about how to set it up. Yes, I can figure it out, but there are dependencies that you won’t know about until you run your code and get errors!
No – it’s not rocket science and the API itself is not difficult to work with at all, but even so, it took the better part of a day to hook up a generic class front end to their API for both COM (for my VFP framework) and .NET (for the .NET version).
One of the really odd features of this API is the fact that they make you install a third party SSL driver (not for COM, but for .NET and C++). This is not part of an installation but something you must download separately from the Open Source site somewhere. To find these links from the docs you first have to go to the installation page, decipher the meaning of that text to figure out which product needs this and which doesn’t. The COM interface which I did first didn’t need it, but the .NET interface did even though the docs don’t differentiate. The .NET interface failed with DLL errors which I didn’t catch.
A third party SSL interface. Interesting concept. Isn’t SSL built into every operating system or platform? Certainly if you were building a .NET component for distribution you could use the built in .NET SSL/HTTP support to do this and give out a component that doesn’t have any dependencies? Nope, we have to install the assembly, plus a wrapper DLL into the execution directory, then copy the SSL drivers into the System32 directory. That’s intuitive. Of course, no installer so you get to repeat and more importantly remember this exercise when you deploy your app to a live server.
I’m always amazed at companies that still have an API to do this. Why not go the route of direct HTTP access like Authorize.NET or AccessPoint, which eliminates the need for any additional components? Posting data via HTTPS/SSL is built into any development platform and what could be easier than posting individual values, or as would probably be the case with LinkPoint, a hierarchical XML document. Or if you do provide an API provide it optionally to create this XML document (which is what LinkPoint apparently does), but do it in a platform specific way so it’s easy to integrate without (unmentioned) dependencies.
LinkPoint uses XML to handle its message – all the value you set get generated into an XML document that you then feed to a transaction object which basically is the HTTP transfer mechanism. The data comes back from Linkpoint as – pseudo XML! Nope not real XML, but a string of an XML fragment. No root node. Hmmm… how hard would it have been to add the root node and XML header? Most people including myself probably will just use string extraction to retrieve the content out of this string, but if you did want to stick it into an XML document/reader you would have to add the doc root at least. Silly.
The point of this isn’t to rag on LinkPoint, but it seems it’s a prime example of misguided development interfaces. To be sure the LinkPoint API does its job once you have it set up, so functionally there’s no problem. But documentation and making integration easy is a key point to a developer tool. This is often forgotten by providers and especially credit card providers. I’ve worked with at least 10 providers over the last 10 years and other than Authorize.NET I’ve never seen documentation that is even close to acceptable. With Authorize.NET I was up and running with a new implementation and class wrapper in 30 minutes. Not that the Authorize.NET docs were spectacular, but accessing an HTTP POST interface that has no installation requirements gets you on your way a lot quicker than spending an hour just trying to figure out exactly which SDK you need to install and what files you need.
It seems doubly strange when you think that these company’s primary business comes from selling these services. They have tons of staff for tech support, but a technical writer or a developer responsible for creating useful and viewable samples is apparently too much to ask. The samples that come with LinkPoint are workable, but abstracted documentation that shows the concise requirements rather than a huge ASP.NET page, that has 250 lines of text and 20 that relate to the actual CC interface is a waste of space.
The CC interfaces for all these providers are also different for each of the providers. Maybe I should say thankfully so, since some provider interfaces are much easier to implement and use than others. But if a common format was used it would at least be easier to implement in generic solutions like store front software, which is bascically what I'm doing here.
And then there’s documentation in PDF format. PDF has to be the worst format for any technical documentation. PDFs are horrible to read, cross linking works awkwardly and you can’t easily copy text out of the document. Never mind the horrible view options in the Acrobat Reader. Anything is better than a PDF document – HTML, RTF, Word… heck even a Text file would better. Unless you print the PDF document PDF is not the right choice for documentation. And who prints things these days? And LinkPoints samples would look horrible printed since nobody bothered to try to fit the samples into the line lenghts of the document.
.NET Configuration
To give you a heads up here’s what I put into my documentation for the West Wind Web Store for the .NET version:
The ccLinkPoint class interfaces with the LinkPoint API via a managed DLL that interfaces with a couple of unmanaged DLLs (the SSL interfaces). The process of using LinkPoint involves:
· Signing up with LinkPoint and getting a test account
· Downloading and installing the .NET API
· Using the ccLinkPoint class from within .NET
· Enabling the class by setting a #define EnableLinkPoint
Getting and Installing the LinkPoint API
The LinkPoint API provides a variety of components for different languages and development environments. The API basically handles setting up transactions and sending the transactions via HTTPS to the LinkPoint Gateway server. To work with the API you only the appropriate component plus a little code to drive the component which is provided by the wwLinkPoint class.
You can download the LinkPoint API from:
https://www.linkpoint.com/viewcart/
You'll want to use the LinkPoint API .NET (C# or VB) download for Windows. This download includes a couple of DLLs (linkpointTransaction.dll and lpssl.dll) which you should copy in your bin or executable directory so your application can find it.
OpenSSL is a separate, required download
For .NET you will also need to download the OpenSSL DLLs which Linkpoint uses. You can go to http://www.linkpoint.com/support/ and follow the links from there to download and drop libeay.dll and ssleay.dll into your System32 directory.
Using the wwLinkPoint Class
Once the LinkPoint API is installed you'll need the following information from your confirmation email(s):
· The path to the filename to which you copied your Certificate (a .PEM file)
· Your Store Number
· Your Store Password
· The domain name and port number of the live or test server
Enabling the ccLinkPoint class
Because the LinkPoint API has a dependency on the external LinkPoint assemblies the ccLinkPoint class by default disables the class. At the top of the ccLinkPoint class there's a #define EnableLinkPoint directive, which is commented out by default. When commented the body of the class is included and any attempt to instantiate the class fails with an exception.
To enable the class uncomment the #define EnableLinkPoint directive. Then copy the LinkpointTransaction.dll and lpssl.dll into the bin/executable directory of your application and add a reference in your project to LinkPointTransaction.dll.
Although a factory pattern might have isolated this interface more cleanly it would have also required a separate assembly. Since LinkPoint is a specialty install and you will need to copy the LinkPoint DLLs explicitly anyway we opted for this simpler more integrated approach.
Special Properties you need to set
CC = new ccLinkPoint();
CC.MerchantPassword = App.Configuration.CCMerchantId; // "123412314"
CC.CertificatePath = App.Configuration.CCCertificatePath; // "d:\app\MyCert.pem"
// *** HTTP Link can be server name plus port
CC.HTTPLink = "staging.linkpt.net:1229";
// CC.Port = 1229; // or specify port separately
Here’s the processing class. This relies on the parent classes properties, but it should be pretty obvious what these properties mean.
#define EnableLinkPoint // Make sure to add LinkPointTransaction.dll and lpSSL.dll from the LinkPoint SDK
using System;
using System.Globalization;
using Westwind.Tools;
using LinkPointTransaction;
namespace Westwind.WebStore
{
/// <summary>
///
/// </summary>
public class ccLinkPoint : ccProcessing
{
/// <summary>
/// The Port used by the LinkPoint API to communicate with the server.
/// This port will be provided to you by LinkPoint.
/// </summary>
public int Port = 1129;
public ccLinkPoint()
{
this.HttpLink = "staging.linkpt.net:1129"; // override
}
#if EnableLinkPoint
/// <summary>
/// Validates the credit card. Supported transactions include only Sale or Credit.
/// Credits should have a negative sales amount.
/// </summary>
/// <returns></returns>
public override bool ValidateCard()
{
if (!base.ValidateCard())
return false;
this.Error = false;
this.ErrorMessage = "";
// *** Parse the HttpLink for domain + port number
int At = this.HttpLink.IndexOf(":");
if (At > 0)
{
string t = this.HttpLink.Substring(At+1);
this.Port = int.Parse( t );
this.HttpLink = this.HttpLink.Substring(0,At);
}
// create order
LPOrderPart order = LPOrderFactory.createOrderPart("order");
// create a part we will use to build the order
LPOrderPart op = LPOrderFactory.createOrderPart();
// Figure out what type of order we are processing
if (this.OrderAmount < 0 )
{
op.put("ordertype","CREDIT");
this.OrderAmount = this.OrderAmount * -1;
}
else if (this.ProcessType == ccProcessTypes.Credit )
op.put("ordertype","CREDIT");
else if (this.ProcessType == ccProcessTypes.Sale )
op.put("ordertype","SALE");
else if (this.ProcessType == ccProcessTypes.PreAuth )
op.put("ordertype","PREAUTH");
// add 'orderoptions to order
order.addPart("orderoptions", op );
// Build 'merchantinfo'
op.clear();
op.put("configfile",this.MerchantId);
// add 'merchantinfo to order
order.addPart("merchantinfo", op );
// Build 'billing'
// Required for AVS. If not provided,
// transactions will downgrade.
op.clear();
op.put("name",this.Name);
op.put("address1",this.Address);
op.put("city",this.City);
op.put("state",this.State);
op.put("zip",this.Zip);
op.put("country",this.Country); // 2 letter ID
op.put("email",this.Email);
op.put("phone",this.Phone);
int AddrNum = 0;
try
{
string T = this.Address.Substring( 0 , this.Address.IndexOf(" ") ).Trim();
AddrNum = int.Parse(T);
}
catch { ; }
if (AddrNum != 0)
{
op.put("addrnum", AddrNum.ToString( System.Globalization.CultureInfo.InvariantCulture.NumberFormat) );
}
order.addPart("billing", op );
// Build 'creditcard'
op.clear();
op.put("cardnumber",this.CreditCardNumber );
op.put("cardexpmonth",this.CreditCardExpirationMonth);
op.put("cardexpyear",this.CreditCardExpirationYear);
order.addPart("creditcard", op );
// Build 'payment'
op.clear();
op.put("chargetotal",this.OrderAmount.ToString( System.Globalization.CultureInfo.InvariantCulture.NumberFormat ) );
order.addPart("payment", op );
// *** Trasnaction Details
op.clear();
if (this.OrderId != null && this.OrderId != "" )
op.put("oid",this.OrderId );
order.addPart("transactiondetails",op);
if (this.Comment != "" )
{
// *** Notes
op.clear();
op.put("comments",this.Comment );
order.addPart("notes",op);
}
// create transaction object
LinkPointTxn LPTxn = new LinkPointTxn();
// get outgoing XML from the 'order' object
this.RawProcessorRequest = order.toXML();
// Call LPTxn
try
{
this.RawProcessorResult = LPTxn.send(this.CertificatePath,this.HttpLink,this.Port, this.RawProcessorRequest );
}
catch(Exception ex)
{
if (this.RawProcessorResult != "")
{
string Msg = wwUtils.ExtractString(this.RawProcessorResult,"<r_error>","</r_error>") ;
if (Msg == "")
this.SetError(ex.Message);
else
this.SetError(Msg);
}
else
{
this.SetError(ex.Message);
}
this.ValidatedMessage = this.ErrorMessage;
this.ValidatedResult = "FAILED";
return false;
}
this.ValidatedResult = wwUtils.ExtractString(this.RawProcessorResult,"<r_approved>","</r_approved>");
if (this.ValidatedResult == "")
this.ValidatedResult = "FAILED";
this.ValidatedMessage = wwUtils.ExtractString(this.RawProcessorResult,"<r_message>","</r_message>");
if (this.ValidatedResult == "APPROVED")
{
this.SetError(null);
}
else
{
this.SetError( wwUtils.ExtractString(this.RawProcessorResult,"<r_error>","</r_error>") );
this.ValidatedMessage = this.ErrorMessage;
}
this.LogTransaction();
return !this.Error;
}
#else
public override bool ValidateCard()
{
throw new Exception("ccLinkPoint Class is not enabled. Set the EnableLinkPoint #define in the source file.");
}
#endif
}
}
When I get some time I will write up this whole Credit Card Processing hierarchy business into an article. Until then hopefully this linkpoint sample will help someone out.
Other Posts you might also like