Your own payment forms

While the ProcessOut payment modal is a great way to start, it isn’t that customizable. ProcessOut.js lets you entirely control the payment flow on your own pages and removes any ProcessOut branding- effectively making ProcessOut invisible to your end-user. ProcessOut.js is available and free for every ProcessOut user.


Common payment flow

A common payment flow is composed of the following steps:

  • The customer enters its credit card information on the merchant’s website
  • ProcessOut.js creates a secure token from the card and returns it to the merchant’s script
  • The merchant sends the token back to its backend and captures the payment

Behind the scenes, ProcessOut will encrypt the credit card information, send it to the ProcessOut PCI-compliant vault to tokenize it, and return the created token to the merchant. This effectively makes the merchant escape from the PCI DSS nightmare by being compliant with the PCI Self-Assessment Questionary.

By using ProcessOut’s hosted fields, you’re elligible to PCI DSS A Self-Assessment Questionary ↗.

PCI DSS SAQ-A is the easiest PCI DSS compliancy assessment, and requires the least amount of work on your side to be compliant with the industry’s standards regarding online card payments. ProcessOut uses iframes to serve payment inputs for your customers, leaving the security to be handled by ProcessOut.

<div class="container">
  <form action="" method="POST" id="card-form">
    <div class="block-group">
            <input type="text" placeholder="John Smith" class="b75 block" id="cardholdername">
      <input type="text" placeholder="10018" class="b25 block" id="cardholderzip">
        </div>

    <div class="block-group">
      <div class="block" data-processout-input="cc-number" 
      data-processout-placeholder="4242 4242 4242 4242"></div>
      <div class="block" data-processout-input="cc-exp" 
        data-processout-placeholder="MM / YY"></div>
      <div class="block" data-processout-input="cc-cvc" 
        data-processout-placeholder="CVC"></div>
    </div>

    <div>
      <input type="submit" id="paymentBtn">
    </div>
    <div id="errors"></div>
    <div id="success"></div>
  </form>
</div>
/*! PocketGrid 1.1.0
* Copyright 2013 Arnaud Leray
* MIT License
*/
* {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

/* Clearfix */
.block-group {
  *zoom: 1;
}
.block-group:before, .block-group:after {
  display: table;
  content: "";
  line-height: 0;
}
.block-group:after {
  clear: both;
}

.block-group {
  /* ul/li compatibility */
  list-style-type: none;
  padding: 0;
  margin: 0;
}

/* Nested grid */
.block-group > .block-group {
  clear: none;
  float: left;
  margin: 0 !important;
}

/* Default block */
.block {
  float: left;
  width: 100%;
}
.b75 {
  width: 74%;
}
.b25 {
  width: 25%;
  margin-left: 1%;
}

html, body {
  background: #ECEFF1;
  font-size: 16px;
}

.container {
  width: 100%;
  max-width: 400px;
  background: white;
  margin: 3em auto;
  padding: 1em;
  border-radius: 4px;
  box-shadow: 0 5px 7px rgba(50, 50, 93, 0.04), 0 1px 3px rgba(0, 0, 0, 0.03);
}

[data-processout-input], input {
  border: 1px solid #ECEFF1;
  border-radius: 4px;
  box-shadow: 0 5px 7px rgba(50, 50, 93, 0.04), 0 1px 3px rgba(0, 0, 0, 0.03);
  padding: 0.5em;
  width: 100%;
  margin-bottom: 1em;
  font-size: 14px;
  min-height: 2em;
}
::-webkit-input-placeholder { /* WebKit, Blink, Edge */
  color:    #ECEFF1;
}
:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
  color:    #ECEFF1;
  opacity:  1;
}
::-moz-placeholder { /* Mozilla Firefox 19+ */
  color:    #ECEFF1;
  opacity:  1;
}
:-ms-input-placeholder { /* Internet Explorer 10-11 */
  color:    #ECEFF1;
}
[data-processout-input]:nth-child(1) {
  width: 48%;
  display: inline-block;
}
[data-processout-input]:nth-child(2) {
  width: 25%;
  display: inline-block;
  margin-left: 1%;
}
[data-processout-input]:nth-child(3) {
  width: 25%;
  display: inline-block;
  margin-left: 1%;
}

input[type="submit"] {
  margin: 0;
  box-shadow: 0 5px 7px rgba(50, 50, 93, 0.04), 0 1px 3px rgba(0, 0, 0, 0.03);
  background: #3F51B5;
  color: white;
  border-radius: 4px;
  border: 1px solid #303F9F;
}

#errors, #success {
  margin-top: 1em;
  text-align: center;
  font-size: 0.9em;
  color: #D84315;
}
#success {
  color: #4CAF50;
}
document.addEventListener("DOMContentLoaded", function() {
  var processout = new ProcessOut.ProcessOut("test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x");
  var formElement = document.getElementById("card-form");
  processout.setupForm(formElement, {
    style: {
      fontSize: "14px",
      "::placeholder": {
        color: "#ECEFF1",
      },
    }
  }, function(form) {
    form.getNumberField().on("focus", function(e) {
      document.getElementById("errors").innerHTML = "";
    });
    form.getExpiryField().on("focus", function(e) {
      document.getElementById("errors").innerHTML = "";
    });

    form.addEventListener("submit", function(e) {
      e.preventDefault();
      document.getElementById("paymentBtn").disabled = true;

      // Let's tokenize the card
      processout.tokenize(form, {
        name: document.getElementById("cardholdername").value,
        contact: {
          zip:  document.getElementById("cardholderzip").value
        }
      }, function(token) {
        document.getElementById("success").innerHTML = "Success! Your created card token is "+token;
        document.getElementById("paymentBtn").disabled = false;
      }, function(err) {
        document.getElementById("errors").innerHTML = err.message;
        document.getElementById("paymentBtn").disabled = false;
      });

      return false;
    });
  }, function(err) {
    console.log(err);
  });
});

The above example illustrates how one can customize ProcessOut.js in order to match its own website style. In this example, the inputs for the cardholder name and ZIP code are actual inputs, whereas the ones for the cards are iFrames: ProcessOut.js makes it easy to make common and card inputs styles perfectly match, while keeping sensitive data secure.

Setting up

Let’s set up a payment form. Please note that the credit card fields aren’t actual input boxes, but are only DOM containers. To ensure PCI-compliancy, ProcessOut hosts the credit card fields on its own servers.

<form action="/your-capture-endpoint" method="POST" id="payment-form">
  <div data-processout-input="cc-number"
        data-processout-placeholder="4242 4242 4242 4242"></div>
  <div data-processout-input="cc-exp-month"
        data-processout-placeholder="Expiration month"></div>
  <div data-processout-input="cc-exp-year"
        data-processout-placeholder="Expiration year"></div>

  <!-- It is also possible to use a unified field for the expiration date: -->
  <div data-processout-input="cc-exp"
        data-processout-placeholder="Expiration date (mm / yy)"></div>


  <div data-processout-input="cc-cvc"
        data-processout-placeholder="CVC"></div>

  <input type="submit" class="submit" value="Submit Payment">
</form>

For the payment form to be interactable, you will need to load ProcessOut.js. It is important to always load it from our CDN like shown below. Please note that ProcessOut.js has no dependency, meaning it can work without jQuery.

<script src="https://js.processout.com/processout.js"></script>

Now that ProcessOut.js is loaded, we can start using it. Let’s first instanciate a new ProcessOut instance in a different script tag as soon as the DOM is fully loaded, and setup our form:

document.addEventListener("DOMContentLoaded", function() {
  var client = new ProcessOut.ProcessOut(
    "proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x");
  var formElement = document.getElementById("payment-form");
  client.setupForm(formElement, processoutReadyHandler, function(err) {
    console.log("Woops, couldn't setup the form: "+err);
  });
});

Note: Remember to replace the project ID in the example with your own. When testing, prepend your project ID with test- like so: test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x.

We also still have to add the handler, which we’ll do just now:

Bind the form and tokenize the card

As soon as the form is fully loaded, setupForm will call processoutReadyHandler with the created CardForm object. We’ll use this object to handle the tokenization transparently.

Let’s bind the form submission and tokenize the card:

function processoutReadyHandler(form) {
  // The form is now fully loaded!
  formElement.addEventListener("submit", function(e) {
    // Cancel any default action
    e.preventDefault();
    // Blocking the form while performing the tokenization may also
    // prevent from race conditions from happening, such as when
    // the user double-clicks on the button
    document.getElementById("paymentBtn").disabled = true;

    // Let's tokenize the card
    client.tokenize(form, {
      // It's possible to send cardholder information (optional)
      name: document.getElementById("cardholdername"),
      contact: {
        zip: document.getElementById("cardholderzip"),
        // Available contact fields:
        // address1, address2, city, state, country_code, zip
      }
    }, function(token) {
      var field   = document.createElement("input");
      field.type  = "hidden";
      field.name  = "token";
      field.value = token;

      // Enable back the button
      document.getElementById("paymentBtn").disabled = false;

      // We add the token input so that it's sent back to  the server.
      // The only thing left to do is to submit the form
      formElement.appendChild(field);
      formElement.submit();
    }, function(err) {

      // Card validation errors or network issues are returned
      // as well. Find the full list of errors below
      alert(err.message);

      // Enable back the button
      document.getElementById("paymentBtn").disabled = false;
    });

    return false;
  });
}

Validation and error handling

When tokenizing a card or catching errors, ProcessOut returns custom exceptions which can be used to better understand what happened and show friendly messages to your users.

client.tokenize(form, {}, function(token) {}, function(err) {
  switch (err.code) {
  case "card.declined":
    break; // The card was declined, a new one should be submitted
  case "card.expired":
    break; // The card is expired, a new one should be submitted
  case "card.invalid":
    break; // The card is invalid
  case "card.invalid-number":
    break; // The card number is invalid
  case "card.invalid-date", "card.invalid-month", "card.invalid-year":
    break; // The card expiration date is invalid
  case "card.invalid-cvc":
    break; // The card CVC is invalid
  default:
    // Another less common error was thrown
  }

  // It is also possible to display a friendly message to the user:
  alert(err.message);
});

Add some style

Now that card inputs are set up and working on your payment page, you might want to update their style a little bit. To provide maximum security, hosted card fields styles are kept to the minimum. They are made to come unstylised by default, with a transparent background and no margin or padding. This is meant to let you customize their parent elements to your likings. For instance, if you wish your input to have borders and paddings like default inputs do, simply add those attributes to the parent elements of the input fields.

However, ProcessOut.js still lets you customize the fonts and similar CSS attributes of the fields. This can be done by passing options along when calling setupForm.

var style = {
  fontSize: "14px",
  "::placeholder": {
    color: "#ECEFF1"
  }
};

client.setupForm(formElement, {
  style: style
}, processoutReadyHandler, function(err) {
  //
});

The full list of all the CSS attributes that can be used to style the card inputs is the following:

color, fontFamily, fontSize, fontSizeAdjust, fontStretch, fontSmoothing, fontStyle, fontVariant, fontWeight, fontHeight, lineHeight, textShadow, textTransform, textDecoration, transition

Additionnally, you may provide pseudo classes/elements as sub-items in the style, as shown in the above example. The available pseudo elements are:

:hover, :focus, ::placeholder, ::selection

ProcessOut.js also supports custom fonts for enterprise users. If you’d like to use your own font and branding on ProcessOut fields, reach out to us! We’re always glad to help.

Handle input events

It is possible to hook functions to specific events happening on card inputs, such as when the use clicks or focuses on an input.

client.setupForm(formElement, function(form) {
  form.getNumberField().on("click", function(e) {
    console.log(e);
  });
  form.getExpiryField().on("click", function(e) {
    console.log(e);
  });
  form.getCVCField().on("click", function(e) {
    console.log(e);
  });
}, function(err) {
  //
});

What’s next?

We’ve sent the card token back to our server. Let’s use it to capture a payment ↗ or to create a subscription ↗.