/* 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(); } }