ExactJS Payment Forms

Create custom, secure payment forms using ExactJS components

The ExactJS library allows you to create your own payment forms hosted on your servers and drop in UI components for entering PCI data. Since the PCI-related UI components continue to be served from our site, and the data entered into them is never submitted to your servers, you do not have to worry about data security and the burden of maintaining PCI compliance.

ExactJS gives you the freedom to design and manage payment forms without having to redirect the customer away from your checkout page and without PCI data passing through or being stored in your network.

Getting Started

If you have not already done so, please read the API Access Set-up guide and ensure you have generated a secret API key, which you will use to communicate with our APIs from your backend server.

Creating the Order

Before rendering your payment form for the customer, you need to send the details of the order to our servers. From your server, send a POST request to our Orders with the details of your customer's order.

You will need to use your secret API key to authenticate this request.

POST /orders
{
  amount: 1123,
  capture: true,
  terminal: {
    gateway_id: "your_gateway_id"
  }
}

The above example is the bare minimum required to create an order. Please see the Orders API reference for the various other options available when creating an Order.

This will return an order ID and an access token which you will need to include on your payment form.

You can also use the ID with the Orders API to update the order as many times as you need until the customer has paid. Once the order has been paid, further updates will be rejected.

Access Tokens

The access token you received when you created the Order will allow your customers to access our APIs from their browser for a limited time period. It is valid for 15 minutes and, once it has expired, further API requests will be rejected.

You can optionally re-generate an access token if you want to allow your customer more time to complete their payment.

Building Your Form

When building your payment form, you first need to add our ExactJS library.

For the sandbox environment, you should load the file from the api.exactpaysandbox.com domain.
For production, load it from the api.exactpay.com domain.

<head>
  <script src="https://api.exactpaysandbox.com/js/v1/exact.js"></script>
</head>

You then need to initialize the library with the access token - do not use your secret API key!!

const exact = ExactJS("the_access_token");

Create your payment form, including an element to which we will attach our credit card UI. Here's a simple example:

<form id="myForm" action="your_form_url" method="post">
  <div>
    <label for="email-address"">Email address</label>
    <input type="email" id="email-address" name="email-address" autocomplete="email">
  </div>

  <div id="cardElement">
    <!-- our credit card UI component will be attached here -->
  </div>

  <div>
    <label for="address">Address</label>
    <input type="text" id="address" name="address" autocomplete="street-address">
  </div>

  <div>
    <label for="apartment">Apartment, suite, etc.</label>
    <input type="text" id="apartment" name="apartment">
  </div>

  <div>
    <label for="city">City</label>
    <input type="text" id="city" name="city">
  </div>

  <div>
    <label for="province">Province</label>
    <input type="text" id="province" name="province">
  </div>

  <div>
    <label for="postal-code">Postal code</label>
    <input type="text" id="postal-code" name="postal-code" autocomplete="postal-code">
  </div>

  <!-- INSERT RESPONSE ITEMS HERE -->

  <div>
    <input type="submit" name="commit" value="Pay Now" data-disable-with="Pay Now">
  </div>
</form>

Tell ExactJS to add the credit card UI to your form.

const components = exact.components();
components.addCard('cardElement');

Finally, you need to intercept the form submission and tell ExactJS what to do with the payment details supplied by the customer.

Option 1: Pay from the Browser

The first option is to configure your payment form so that your customers pay for the order directly from their browser. The ID of the completed payment will be returned to you. This ID can then be used with our Payments API to look up further details of the payment for example, or to capture an authorization.

For this option, you need to add an element to your form to accept the payment ID returned by ExactJS, so insert the following snippet into your form in place of the <!-- INSERT RESPONSE ITEMS HERE --> comment in the sample form above.

<input type="hidden" name="payment_id" id="payment_id">

Now add a listener to tell ExactJS to pay for the order when the customer submits the form. You will need to pass in the ID of the order you created earlier through our Orders API.

document.getElementById("myForm").addEventListener('submit', (event) => {
  event.preventDefault();

  const form = event.target.closest("form");
  exact.payOrder("your_order_id")
    .then(payment_id => {
        // add the payment id to your form
      document.getElementById('payment_id').value = payment_id
        // submit your form to your backend
      form.submit()
    })
    .catch(err => console.error(err));
});

You should save the payment ID on your server as it will allow you to use the Payments API to get more details about the payment, or to perform follow-up transactions such as captures, refunds or voids.

Option 2: Tokenize from the Browser

The second option is to configure your payment form so that your customer's payment details will be tokenized from the browser. You will then need to pay for the Order from your backend server, using the token details returned by ExactJS.

You can also save the returned token details to initiate payments in the future, without incurring any PCI compliance requirements.

For this option, you need to add a few elements to your form to accept the token details returned by ExactJS, so insert the following snippet into your form in place of the <!-- INSERT RESPONSE ITEMS HERE --> comment in the sample form above.

<input type="hidden" name="token" id="token">
<input type="hidden" name="token_type" id="token_type">
<input type="hidden" name="token_last4" id="token_last4">
<input type="hidden" name="token_brand" id="token_brand">

Now add a listener to tell ExactJS to tokenize their payment details when the customer submits the form. You will need to pass in the ID of the order you created earlier through our Orders API.

document.getElementById("myForm").addEventListener('submit', (event) => {
  event.preventDefault();

  const form = event.target.closest("form");
  exact.tokenize("your_order_id")
    .then(tokenDetails => {
        // add the token details to your form
            document.getElementById('token').value = tokenDetails.token;
            document.getElementById('token_type').value = tokenDetails.token_type;
            document.getElementById('token_last4').value = tokenDetails.last4;
            document.getElementById('token_brand').value = tokenDetails.card_brand;
        // submit your form to your backend
      form.submit()
    })
    .catch(err => console.error(err));
});

The final step for this option is for you to pay for the order from your backend server, using the token you have just received. As before, you will use your secret API key to authenticate this API request.

POST /orders/the_order_id/pay
{
  payment_method: {
    token: {
      token: "the_token",
        token_type: "the_token_type"
    }
  }
}

Displaying Errors

If something goes wrong during the payment process, whether it's due to the customer entering invalid information, or something going wrong with the payment or tokenization submission, the payOrder or tokenize methods will fail with an array of error strings. You are responsible for displaying these errors to your customer, placed and styled in whichever way suits your design.

Here's a simple example, which assumes your payment form has a <div id="errors"> element somewhere on the page.

const errorElem = document.getElementById("errors");

const showErrors = (errs) => {
  Array.from(errs).forEach(err => {
    const pElem = document.createElement("p");
    pElem.textContent = err;
    errorElem.append(pElem);
  });
  errorElem.classList.remove("hidden");
};

const clearErrors = () => {
  errorElem.classList.add("hidden");
  Array.from(errorElem.children).forEach(elem => {
    if(elem.tagName === "P") {
      errorElem.removeChild(elem);
    }
  });
};

document.addEventListener('DOMContentLoaded', function() {
  const exact = ExactJS("the_access_token");
  const components = exact.components();
  components.addCard('cardElement');

  document.forms.myForm.addEventListener('submit', function(event) {
    event.preventDefault();
    clearErrors();

    const form = event.target.closest("form");
    exact.payOrder("the_order_id")
      .then(payment_id => {
        // add the payment ID to the form and submit to your server
        document.getElementById('payment_id').value = payment_id;
        form.submit();
      })
      .catch(err => showErrors(err));
  });
});