‘Your Page’ with JavaScript
What it is
This technology allows you to place HTML pages on your site that are completely under your design and revision control, while remaining out of scope for PCI. These pages can be out of scope because they are displayed on the consumer’s browser and the consumer enters their own data on the consumer’s own PC or mobile device.
When the consumer submits the form, data from the pages is transmitted twice, first to SLIM CD to collect the credit card data and then to your server to receive the tokenized version of the data.
The tokenized version of the data contains none of the customer’s private information, so your webserver is allowed to see and store it, without having to be PCI compliant. Your servers never receive the actual credit card data, as it is transmitted from the consumer’s PC to SLIM CD. This “double-posting” from a single “form submit” is accomplished with JavaScript.
A complete sample code example is provided below.
How it works
HTML forms transmit data to servers for each <input> tag on the form, but only if that <input> tag contains a “name=” identifier. If the <input> tag does not have a name, the data contained inside that tag cannot be coded as a “name=value” pair on an HTTP GET/POST, so it is ignored by the browser.
JavaScript provides a way to access the data in an <input> tag by giving each <input> field an “id=” identifier string. The <input> field does not need to have a “name=” tag, but simply an “id=” tag for JavaScript to be able to access the value entered into it.
JavaScript also allows interception of the <form> tag’s submission. This is done by using the “OnSubmit=” attribute of the <form> tag which points to a JavaScript routine which is run when the form is submitted. The interception occurs before any name value pairs are posted to your webserver, so the JavaScript can perform it’s work of determining whether the form should be submitted to your webserver, as specified in the “action=” attribute of the <form> tag.
The goal is to remove your server (the target specified in the “action=” attribute) from receiving the cardholder data. To do this, we must:
- Place “id=” attributes on each <input> field for which we need to extract the PCI-sensitive data.Avoid using “name=” values on any fields containing data considered to be sensitive by PCI security. These include card number, CVV2, magnetic swipe or track data, PIN debit pin data, etc.
1234567891011<div><label>Card Number</label><!--set autocomplete so cardnumbers are not stored in the browser---><input type="text" maxlength="20" autocomplete="off" id="cardnumber" /></div><div><label>CVC</label><input type="text" maxlength="4" autocomplete="off" id="cvv2" /></div>
You are allowed to store expiration date unencrypted if you do not store the card number, but you are NEVER allowed to store CVV2 values.
- Use an “id” attribute on the form itself. This will allow us to access the <form> tag and resubmit the data once we get a response back from SLIMCD.COM. The Asynchronous Considerations topic below discusses this in more detail.
12345<form method="post"id="sampleform"onSubmit="return SubmitHandler(this);"
- The “OnSubmit” of the form calls a JavaScript function that will intercept the form’s submission so that we can divert some of the values to SLIM CD in trade for tokenized versions of that data, which can be safely posted back to your server in named, hidden fields.
12345<script type="text/javascript">function SubmitHandler(form) {// CHECK THIS USER INPUT FOR VALIDITY
Asynchronous Considerations
When the form is submitted, the JavaScript OnSubmit handler will step in to intercept the flow of events. The transmission of name value pairs to your server (as specified in the “action=”) will only occur if the OnSubmit returns “true”. Before that occurs, the OnSubmit handler sends the data to SLIM CD for credit card authorization and then wait to get a response.
The waiting step can take a few moments, and since the typical user won’t tolerate an unresponsive browser, the best way to handle the waiting is to set up an Asynchronous call back method. This occcurs prior to sending the data to SLIM CD and then cancels the user-initiated submission. Your website can then return to performing other tasks, such as letting the user know that credit card authorization is underway. When an answer becomes available, your callback function will be called to handle the results of the credit card authorization. If the credit card transaction has been successful, the callback function will submit your form, passing the now-tokenized authorization information on to your webserver.
To accomplish this feat, we’ll rely on how JavaScript and browsers work together. The OnSubmit function must return either true or false. In our case, we will ALWAYS want to return FALSE. This stops the <form> tag from submitting to your server. We need to stop the flow because we don’t yet know if the credit card authorization has been successful. We only want to submit back to your webserver when we are certain that a payment has been made. The callback routine will run with the response from SLIM CD and this callback can then be responsible for re-submitting the form.
1 2 3 4 |
// RETURN FALSE SO THE FORM NEVER IS NEVER SUBMITTED DIRECTLY BY THE SUBMIT BUTTON CLICK return false; |
This is a point worth repeating. The JavaScript function called on OnSubmit will always return false, stopping the form from submission. The callback will be called once the connection to SLIMCD.COM returns a result. The callback will then be responsible for re-submitting the form. One initial concern might be that submitting the form might cause the OnSubmit to be triggered again and cause an infinite loop. Thankfully, submitting the form within JavaScript does not trigger the OnSubmit, it just sends the data to the target server.
The JavaScript callback will need to store the data returned from SLIMCD.COM by placing it your form’s hidden fields, and to clear out any card data values as a safety mechanism to insure these data are not sent to the “action=” target of the form.
1 2 3 4 5 6 |
<!-- Here are a few things returned from slimcd.com --> <input type="hidden" name="gateid" id="gateid" value=""/> <input type="hidden" name="last4" id="last4" value=""/> <input type="hidden" name="cardtype" id="cardtype" value=""> |
The callback will then access the <form> object to submit the updated input/hidden field contents to your server. The easiest way to accomplish this is to place an “id=” attribute on the hidden form fields and on the form itself. The results from SLIMCD.COM can be stored in the hidden fields and the form can be submitted using the “id=” value of the <form> tag.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
if (reply.response == "Sucesss") { // CLEAR THE CARD NUMBER AND CVV2 FIELDS TO STAY OUT OF SCOPE document.getElementById('cardnumber').value = ''; document.getElementById('cvv2').value = ''; // SAVE THE RETURNED NON-SENSITIVE CARD INFORMATION document.getElementById('last4').value = reply.datablock.last4; document.getElementById('cardtype').value = reply.datablock.cardtype; //POST YOUR FROM TO YOUR SERVER document.getElementById('sampleform').submit(); |
Implementation Choices
The submission to the SLIM CD servers can be done in two ways:
- Use JQuery and enable cross-domain resources (CORS). SLIM CD exposes web service entry points that accept either name/value pairs or JSON as input and return JSON. These entry points can either return pure JSON or can return JavaScript that triggers a specified callback with the JSON as an argument to the callback.
- Use the SLIM CD library to make the call to SLIMCD.COM. The SLIM CD library provides support for Cross-Domain resources by dynamically modifying the <head> tag of the browser’s DOM and adding a <script>. The SLIM CD library calls the same JSON entry points that you can call directly using JQuery, but for JavaScript purists, the SLIM CD library wraps the implementation, eliminating dependencies.
1234567891011121314151617181920212223242526272829303132333435SlimCD.Transact.ProcessTransaction ( {"username" : "R6UT8C6M","transtype" : "LOAD","amount" : "0.00","cardnumber" : document.getElementById('cardnumber').value,"expmonth" : document.getElementById('expmonth').value,"expyear" : document.getElementById('expyear').value,"cvv2" : document.getElementById('cvv2').value,"first_name" : document.getElementById('first_name').value,"last_name" : document.getElementById('last_name').value,"email" : document.getElementById('email').value},fucntion (reply) {document.getElementById('btnSubmit').disabled = false;if(reply.datablock != undefined && reply.datablock.gateid != undefined)document.getElementById('gateid').value = reply.datablock.gateid;if(reply.response == "sucess") {document.getElementById('cardnumber').value = '';document.getElementById('cvv2').value = '';document.getElementById('last4').value = reply.datablock.last4;document.getElementById('cardtype').value = reply.datablock.cardtype;document.getElementById('sampleform').submit();}else {alert('There was a problem processing the payment:\r\n' + reply.description);document.getElementById('errormessages').innerHTML = reply. description;}});
SLIM CD provides sample code for both approaches. See the RESOURCES section of the website. Each Entry Point contains JSON and JavaScript tabs that provide sample implementations.
Putting it all together
- To take what we’ve learned and produce a full working sample, here’s what we need to do. For this example, we’ll use the SLIM CD library:
- Obtain “Public” API ACCESS CREDENTIALS from the merchant. Slim CD API calls need either a userid/password or an API Access Credential. You don’t want to place the password inside an HTML file where everyone can View Source, so API Access Credentials are the solution. These are created by the merchant for their login using the SLIM CD website (see the sections on UserNames and on API Access Credentials for more information).
- Create a new HTML form
- If you are using the SLIMCD JavaScript library, Include “slimcd.js”. (You would not need this if you were using JQuery to call the SLIM CD JSON web services directly)
- Add a <form> tag with “id=” and an “onsubmit=” attributes
- Add <input> fields for cardnumber, cvv2, etc with appropriate “id=” and (extremely important) without the “name=” attributes.
- Add <input type=”hidden”> fields both id and name attributes. These will store the “gateid” and other values returned from SLIMCD.COM
- Create the JavaScript callback function for the OnSubmit
- Disable the submit button so the user cannot click twice
- Have the OnSubmit function retrieve the cardholder data fields and format the call to SLIM CD’s library function or JSON web service
- Have the above call define a “callback” function that will
- Determine if the call was successful or failed
- Enable the submit button so the user can try again if the call to SLIMCD.COM failed, or so the form can be submitted via JavaScript if the call to SLIMCD.COM succeeded.
- Upon success:
- Retrieve values from the SLIM CD reply block (returned as JSON)
- Set the values into the hidden form fields
- Clear out the payment form fields (not required, but recommended)
- Submit the form again using the form’s “id=” value
- Upon fail/error
- Display a message box or update the HTML of an area on the page to show the reason for the failure.
- Return FALSE so that the OnSubmit stops the browser from submitting the form until the callback runs.
- Your server will receive the HTTP POST containing the form <input> fields that have names. This will include any hidden <input> fields whose value has been updated by the JavaScript. Note that you can have <input> fields that are both sent to SLIM CD and are sent to your web server (like ADDRESS, ZIP, etc). Your server will not receive any values for fields with no names. If it does receive these values, the JavaScript will have blanked out any values of concern (like Card Number or CVV2).
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
<!DOCTYPE html> <html prefix="og: http://ogp.me/ns#" lang="en"> <head> <title>SLIM CD YourPage Example </title> <script type="text/javascript" src="http://stats.slimcd.com/soft/json/slimcd.js"></script> <script type="text/javascript"> function SubmitHandler(form) { // CHECK THE USER INPUT FOR VALIDITY if (SlimCD.Validate.isValidCardNumber(document.getElementById('cardnumber').value) === false) alert('Invalid cardnumber, please retry'); else if (SlimCD.Validate.isValidExpDate(document.getElementById('expmonth').value, document.getElementById('expyear').value) === false) alert('Invalid expiration date, please retry'); else { // disable the SUBMIT button so the user can't click it twice document.getElementById('btnSubmit').disabled = true; // SUBMIT THE USER INPUT TO SLIMCD USING A LOAD TRANSACTION TYPE SlimCD.Transact.ProcessTransaction( { "username": "R6UT8C6M" , "transtype": "LOAD" , "amount": "0.00" , "cardnumber": document.getElementById('cardnumber').value , "expmonth": document.getElementById('expmonth').value , "expyear": document.getElementById('expyear').value , "cvv2": document.getElementById('cvv2').value , "first_name": document.getElementById('first_name').value , "last_name": document.getElementById('last_name').value , "email": document.getElementById('email').value }, function (reply) { // Enable the submit button again now. document.getElementById('btnSubmit').disabled = false; // Approved or Declined will have a GateID. // SAVE THE UNIQUE RETURNED GATEID AS THE TRANSACTION TOKEN // IN A HIDDEN FIELD SO THAT YOUR SERVER CAN USE IT // TO FINISH THE TRANSACTION ON THE SERVER SIDE if (reply.datablock != undefined && reply.datablock.gateid != undefined) document.getElementById('gateid').value = reply.datablock.gateid; if (reply.response == "Success") { // CLEAR THE CARD NUMBER AND CVV2 FIELDS TO STAY OUT OF SCOPE document.getElementById('cardnumber').value = ''; document.getElementById('cvv2').value = ''; // SAVE THE RETURNED NON-SENSATIVE CARD INFORMATION document.getElementById('last4').value = reply.datablock.last4; document.getElementById('cardtype').value = reply.datablock.cardtype; // POST YOUR FORM TO YOUR SERVER document.getElementById('sampleform').submit(); } else { // ANNOUNCE THE PROBLEM alert('There was a problem processing the payment:\r\n' + reply.description); document.getElementById('errormessages').innerHTML = reply.description; } } ); } // RETURN FALSE SO THE FORM NEVER IS NEVER SUBMITTED DIRECTLY BY THE SUBMIT BUTTON CLICK return false; } </script> </head> <body> <h1>SLIM CD Javascript/Json Examples - ProcessTransaction</h1> <form method="post" id="sampleform" onsubmit="return SubmitHandler(this);" action="http://test.slimcd.com/soft/debugging_echo.asp"> <!--this will be your server's target page --> <!-- Here are a few things returned from slimcd.com --> <input type="hidden" name="gateid" id="gateid" value=""/> <input type="hidden" name="last4" id="last4" value=""/> <input type="hidden" name="cardtype" id="cardtype" value=""/> <div> <label for="name">Your First Name</label> <input type="text" name="first_name" id="first_name"/> </div> <div> <label for="name">Your Last Name</label> <input type="text" name="last_name" id="last_name"/> </div> <div> <label for="email">E-mail Address</label> <input type="text" name="email" id="email"/> </div> <div> <label>Card Number</label> <!-- set autocomplete so cardnumbers are not stored in the browser --> <input type="text" maxlength="20" autocomplete="off" id="cardnumber" /> </div> <div> <label>CVC</label> <input type="text" maxlength="4" autocomplete="off" id="cvv2" /> </div> <div> <label>Expiration</label> <div> <!-- Because we don't get CardNumber, these fields can have the "name" attribute so the to expmonth/expyear get sent to the form's action --> <select id="expmonth" name="expmonth"> <option value="01">01</option> <option value="02">02</option> <option value="03">03</option> <option value="04">04</option> <option value="05">05</option> <option value="06">06</option> <option value="07">07</option> <option value="08">08</option> <option value="09">09</option> <option value="10">10</option> <option value="11">11</option> <option value="12">12</option> </select> <span> / </span> <select id="expyear" name="expyear"> <option value="2013">2013</option> <option value="2014">2014</option> <option value="2015">2015</option> <option value="2016">2016</option> <option value="2017">2017</option> <option value="2018">2018</option> <option value="2019" selected>2019</option> </select> </div> </div> <button type="submit" id="btnSubmit">Submit</button> <!--The OnSubmit JavaScript above displays processing errors here instead of using alert() --> <div><span id="errormessages"></span></div> </form> <!--If you have the field below on your page, SLIM CD's JavaScript will fill it in with the data from the Silent Post so you can debug your variables. REMOVE the below tag before going live --> <input id="slimcd_debugging_url" size=80> </body> </html> |
More references:
Code Project: PCI-Compliant-Credit-Card-Handling-on-Windows-Azure
GitHub: Open Source sample for LOAD and QUEUEDATA (https://github.com/littleRm/slimcd)