/* Monetra CardShield Client example program in C# using XML and HttpWebRequest
*
* Works with .Net Compact Framework v2
*
* Implemented based on the Monetra CardShield Guide (Section 4, other
* sections are not relevant to the CardShield Client implementation) in
* conjunction with the Monetra Client Interface Protocol Specification
*
* Please contact support@monetra.com with any questions
*/
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Threading;
using System.Collections;
using System.Net;
using System.Xml;
using System.ComponentModel;
using System.Windows.Forms;
using System.Security.Cryptography.X509Certificates;
class csctestxml
{
/* Monetra Connectivity Information
* NOTE: This is currently pointing to our Test Server that you may
* use for initial testing if desired. Obviously for production,
* or testing with encrypted card readers, you need to point
* this to your local Monetra server and the username/password
* you configured there. Please take note of the restrictions
* on the username setup as listed in section 4.2 of the
* CardShield Guide (the red note at the bottom of that section).
*/
private const string monetra_host = "testbox.monetra.com";
private const int monetra_port = 8444;
private const string monetra_user = "test_retail:public";
private const string monetra_pass = "publ1ct3st";
/* CardShield Client Connectivity Information
* NOTE: this is the default, it is possible to change, but 99%
* of deployments will probably use this cardshield information
* as-is
*/
private const string csc_host = "localhost";
private const int csc_port = 8123;
/* Windows path */
//private const string csc_path = "C:\\Program Files\\Main Street Softworks\\Monetra CardShield Client\\monetra_cardshield_client.exe";
/* Windows CE Path */
private const string csc_path = "\\Program Files\\Monetra CardShield Client\\monetra_cardshield_client.exe";
/* Unix path */
//private const string csc_path = "/usr/local/monetra/bin/monetra_cardshield_client";
/*! Function to launch the cardshield client from the current process.
* If we don't launch it from the current process, it won't be given
* focus! (at least on Windows this is true, until the first
* manual focus is performed by an end-user) */
static void launch_csc()
{
Process monetra_cardshield_client = new Process();
monetra_cardshield_client.StartInfo.FileName = csc_path;
/* Not supported on CE
* monetra_cardshield_client.StartInfo.CreateNoWindow = true;
*/
monetra_cardshield_client.Start();
/* Make sure the CardShield Client is ready before returning,
* Sleep 1000ms (1s) */
System.Threading.Thread.Sleep(1000);
}
/*! Trust all SSL server certificates */
internal class AcceptAllCertificatePolicy : ICertificatePolicy
{
public AcceptAllCertificatePolicy()
{
}
public bool CheckValidationResult(ServicePoint sPoint,
X509Certificate cert,
WebRequest wRequest, int certProb)
{
// *** Always accept
return true;
}
}
/*! Function to POST and XML message to a Monetra-like entity
* (Monetra or the CardShield Client) via HTTPS. It will return
* the key/value pairs from the XML response
* \param[in] host Host to connect to
* \param[in] port Port to connect to (via SSL/HTTPS)
* \param[in] xml String-form XML to post
* \return True on successful communication, False if communication failed.
* Note: True doesn't mean the transaction itself was successful.
*/
static Hashtable https_post_monetra(string host, int port, string xml)
{
Hashtable response = new Hashtable();
string url = "https://" + host + ":" + port.ToString();
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
string xmlout;
try {
/* POST Request */
/* Disable SSL Server Certificate Checking */
System.Net.ServicePointManager.CertificatePolicy =
new AcceptAllCertificatePolicy();
byte[] bytes;
bytes = System.Text.Encoding.ASCII.GetBytes(xml);
req.Method = "POST";
req.ContentType = "text/xml";
req.ContentLength = bytes.Length;
Stream reqStream = req.GetRequestStream();
reqStream.Write(bytes, 0, bytes.Length);
reqStream.Close();
/* Read Response */
/* Note issues with .Net CF v2 as per below:
* http://blogs.msdn.com/b/andrewarnottms/archive/2007/11/19/why-net-compact-framework-fails-to-call-some-https-web-servers.aspx
* http://support.microsoft.com/kb/970549
* If the Server is OpenSSL, this can be worked around by setting
* SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
*/
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
Stream respStream = resp.GetResponseStream();
StreamReader rdr = new StreamReader(respStream);
xmlout = rdr.ReadToEnd();
rdr.Close();
} catch (System.Net.WebException e) {
response["code"] = "DENY";
response["error_code"] = "CONN_ERROR";
response["verbiage"] = "Connection to " + url + " failed: " +
e.Message;
return response;
}
XmlDocument xmldoc = new XmlDocument();
xmldoc.LoadXml(xmlout);
XmlNodeList trans = xmldoc.DocumentElement.
SelectSingleNode("Resp").ChildNodes;
foreach (XmlNode kv in trans) {
response[kv.Name] = kv.InnerText;
}
return response;
}
/*! Request a ticket from the CardShield Client as documented in
* Section 4 of the Monetra CardShield Guide.
* \param[in] flags Pipe (|) separated list of flags as documented in
* the Monetra CardShield Guide
* \param[in] trantype Transaction Type as documented in the Monetra
* CardShield Guide (CREDIT, DEBIT, EBT, GIFT)
* \param[in] pindevice Applicable only to Debit and EBT, the physically
* connected PIN entry device location. Either USB or
* the serial port address (e.g. Win: COM1, Unix:
* /dev/ttyS0). Specify as null otherwise.
* \param[in] amount Applicable only to Pin-Debit and EBT, needed to send
* the amount to the Pin-entry device. Specify as null
* otherwise.
* \return Hashtable with string responses. The keys are:
* "code", "verbiage", "error_code", "ticket"
* With values equivalent to the Monetra CardShield Guide
* documentation.
*/
static Hashtable csc_ticketrequest(string flags, string trantype,
string pindevice, string amount)
{
string XML;
XML = "" +
"" +
"" + monetra_user + "" +
"" + monetra_pass + "" +
"" + monetra_host + ":" + monetra_port.ToString() +
"" +
"ticketrequest" +
"" + flags + "" +
"" + trantype + "";
if (pindevice != null){
XML = XML + "" + pindevice + "";
}
if (amount != null) {
XML = XML + "" + amount + "";
}
XML = XML + "";
return https_post_monetra(csc_host, csc_port, XML);
}
/*! Tell the CardShield Client to shutdown. Since we start it up,
* we should make sure we turn it off prior to exiting otherwise
* the user will be prompted with an error message stating the
* CardShield Client is already running on the next execution
* of this application!
*/
static void csc_shutdown()
{
string XML;
XML = "" +
"" +
"shutdown" +
"" +
"";
https_post_monetra(csc_host, csc_port, XML);
}
/*! Run a transaction through Monetra using the ticket returned from the
* CardShield Client.
*
* We need to follow the Monetra Client Interface Protocol Spec here for
* the most part as found on:
* http://www.monetra.com/content/developers.html
*
* The exception here is that, as per the addendum CardShield Guide,
* section 4.3.1, under "Passing Ticket Data to Monetra", we replace any
* sensitive information such as trackdata, account, expdate, street, zip,
* cvv2 (or their e_ encrypted counterparts), with the single
* "cardshieldticket" parameter filled in with the "ticket" response
* parameter from the CardShield Client. Since the CardShield Client did
* the collection of this data, we wouldn't have that sensitive data
* available to us anyhow, thus taking the POS out of scope for PA-DSS!
*
* NOTE: This is just an example API, you may want to add additional
* optionalparameters based on your industry (such as tax,
* examount[tip], and so on).
*
* \param[in] action One of the actions available to standard users in the
* Monetra Client Interface Protocol Specification. So
* "sale" or "preauth" might be common ones. Though you
* can also do other requests, like store it in
* Monetra's permanent DSS token system (doc for DSS
* also available in the same place as the Monetra
* Client Interface Protocol Spec)!
* \param[in] amount Dollar amount of request, in string form, with
* decimal place
* \param[in] ticket Ticket returned from the Monetra CardShield Client
* (csc_ticketrequest())
* \param[in] ordernum Optional order number as generated by POS. Pass null
* if not used.
* \return Hashtable of string key/value pairs from Monetra response.
* Please refer to the Monetra Client Interface Protocol
* specification for a list of what these may be based on the action
* being performed. "code" is guaranteed to always be returned.
*/
static Hashtable monetra_transaction(string action, string amount,
string ticket, string ordernum)
{
string XML;
/* Append the parameters for the transaction being run as per
* the Monetra Client Interface Protocol Specification */
XML = "" +
"" +
"" + monetra_user + "" +
"" + monetra_pass + "" +
"" + action + "" +
"" + ticket + "" +
"" + amount + "";
if (ordernum != null) {
XML = XML + "" + ordernum + "";
}
XML = XML + "";
return https_post_monetra(monetra_host, monetra_port, XML);
}
/*! Main entry point to this application to be executed */
static void Main()
{
Hashtable response;
string ticket;
/* Step1: Launch the CardShield Client */
launch_csc();
MessageBox.Show("CardShield Client Launched");
/* Step2: Request Ticket from the CardShield Client using Keyed Entry
* Credit Card */
response = csc_ticketrequest("KEY|AVS|CVV", "CREDIT", null, null);
if (String.Compare((string)response["code"], "AUTH", true) != 0) {
csc_shutdown();
string msg = "TicketRequest failure.\r\n" +
"code = " + (string)response["code"] + "\r\n" +
"error_code = " + (string)response["error_code"] +
"\r\n" +
"verbiage = " + (string)response["verbiage"] + "\r\n";
MessageBox.Show(msg);
return;
}
ticket = (string)response["ticket"];
MessageBox.Show("Received ticket: " + ticket);
/* Step3: Send Transaction to Monetra with Ticket */
response = monetra_transaction("sale", "12.00", ticket, "1234567890");
string resultMsg = "";
if (String.Compare((string)response["code"], "AUTH", true) != 0) {
resultMsg = "Transaction Failed.\r\n";
} else {
resultMsg = "Transaction SUCCESSFUL!\r\n";
}
/* Print out all the response key/value pairs ... */
foreach (DictionaryEntry kv in response) {
resultMsg = resultMsg + (string)kv.Key + " = " + (string)kv.Value +
"\r\n";
}
MessageBox.Show(resultMsg);
/* NOTE: No real reason to exit here ... we could just keep running
* Steps 2 and 3 all day long. No reason to keep disconnecting and
* reconnecting, or starting/stopping the CardShield Client.
*/
/* Step4: Cleanup */
csc_shutdown();
}
}