Creating payment forms using hosted fields

AffiniPay hosted fields protect your payment page by tokenizing payment information. They provide SAQ-A PCI compliance, the highest level of PCI compliance. Hosted fields are <iframe> elements sourced from AffiniPay’s servers that contain a single corresponding <input> element. These iframes replace the input elements on your form that contain payment information.

Hosted fields should be integrated into your site by a developer. This integration requires web developer skills as well as the ability to pass information to a backend server and then to AffiniPay. If you have questions, contact AffiniPay Support.

To create a payment page with hosted fields:

  1. Add a reference to the AffiniPay hosted fields JavaScript library
  2. Create a payment form
  3. Create a hosted fields configuration object
  4. Style the hosted fields
    • Style the content inside hosted fields
    • Enable surcharge
    • Disable or enable fields
    • Focus a field
    • Clear a field
  5. Create a user-defined callback
  6. Initialize hosted fields
  7. Get a payment token

Hosted fields versioning

With version 1.4.0, hosted fields now supports enabling surcharge, disabling and enabling fields, focusing on a field, and clearing a field.

The AffiniPay hosted fields JavaScript library (fieldGen.js) is versioned. The versioning URL is:

https://cdn.affinipay.com/hostedfields/<version>/fieldGen_<version>.js

For example, the URL for version 1.4.0 is:

https://cdn.affinipay.com/hostedfields/1.4.0/fieldGen_1.4.0.js

For SRI and integrity attribute purposes, here are the SHA384 digests:

1.4.0 Release

integrity="sha384-2LuR5n01lipNZjmgOGn8snFZbtajEtxHUI057u3NHA/Q7rJkVeP0qmLknnZ2YsZ4" crossorigin="anonymous"

1: Add a reference to the AffiniPay hosted fields JavaScript library

On your payment page, add a reference to the AffiniPay hosted fields library.

<script src="https://cdn.affinipay.com/hostedfields/1.4.0/fieldGen_1.4.0.js"></script>

The AffiniPay hosted fields library binds the following functions to the window.AffiniPay.HostedFields namespace:

2: Create a payment form

Create an HTML form and replace the <input> elements that contain payment information with <div> elements. On each <div>, set an id that will be used when configuring the hosted field. Your payment page must additionally include all required fields and other payment page requirements.

Here’s an example:

<div>
  <label for="my_credit_card_field_id">Credit Card</label>
  <div id="my_credit_card_field_id"></div>
</div>
<div>
  <label for="exp_month">Exp Month</label>
  <input id="exp_month" type="text" name="exp_month">
</div>
<div>
  <label for="exp_year">Exp Year</label>
  <input id="exp_year" type="text" name="exp_year">
</div>
<div>
  <label for="my_cvv_field_id">CVV</label>
  <div id="my_cvv_field_id"></div>
</div>
<div>
  <label for="postal_code">Zip Code</label>
  <input id="postal_code" type="text" name="postal_code">
</div>
<input type="submit" value="Submit" />

3: Create a hosted fields configuration object

The initializeFields function must be called with a valid config object and a user-defined callback function (in the Create a user-defined callback step). The config object is the only way to configure hosted fields. The config object contains keys that apply to all hosted field as well as individual fieldConfig objects that apply to individual hosted fields.

A valid config object includes:

The window.AffiniPay.HostedFields.initializeFields function returns an object with the following methods. Be sure to save this object.

Here’s an example:

const creditCardFieldConfig = {
  selector: "#my_credit_card_field_id",
  input: {
    type: "credit_card_number"
  }
}

const cvvFieldConfig = {
  selector: "#my_cvv_field_id",
  input: {
    type: "cvv"
  }
}

const hostedFieldsConfiguration = {
  publicKey: 'm_1234567890',
  fields: [
    creditCardFieldConfig,
    cvvFieldConfig,
  ]
}

const hostedFieldsCallback = function (state) {
  console.log(JSON.stringify(state, null, 2))
}

const hostedFields = window.AffiniPay.HostedFields.initializeFields(hostedFieldsConfiguration, hostedFieldsCallback)

// Save the return value of initializeFields for later use
// hostedFields.getState()
// hostedFields.getPaymentToken(formData)

4: Style the hosted fields

Although it is not required, styling is recommended.

Within the css key for a config or fieldConfig object, you can:

Here’s an example of hosted field styling:

const hostedFieldsConfiguration = {
  publicKey: "m_1234567890",
  input: {
    css: {
      'font-family': 'serif',
      'font-size': '22px',
      'color': '#0BEEF0', // fieldConfig css will overwrite this value
      ':focus': { color: 'orange' },
      ':invalid': { background: 'antiquewhite' },
      ':valid': { color: 'blanchedalmond' }
    }
  },
  fields: [
    {
      selector: "#my_credit_card_field_id",
      input: {
        type: "credit_card_number",
        css: {
          "border": "1px solid rgb(204, 204, 204)",
          "color": "#000",
          "font-size": "30px",
          "font-weight": "200",
          "width": "100%",
          "::placeholder": { color: 'green' }
        }
      }
    },
    {
      selector: "#my_cvv_field_id",
      input: {
        type: "cvv",
        css: {
          "border": "1px solid rgb(204, 204, 204)",
          "border-style": 'inset',
          "color": "blue",
          "font-size": "11px",
          "font-weight": "400",
          "padding": "8px",
          ':invalid': { color: 'purple' },
          "width": "100%"
        }
      }
    }
  ]
}

Enable surcharge

To enable surcharge, add surchargeEnabled: true to the top level of the hosted fields configuration object passed to window.AffiniPay.HostedFields.initializeFields.

Here is an example:

const hostedFieldsConfiguration = {
  publicKey: "m_1234567890",
  surchargeEnabled: true,
  input: {
    …
  }
}
window.AffiniPay.HostedFields.initializeFields(hostedFieldsConfiguration, callback)

After surcharge is enabled, hosted fields will provide a new surchargeable field in its state.

Disable or enable fields

To disable and enable inputs use the following methods, passing the value from the field selector property in your hostedFieldsConfiguration object.

const hostedFields = window.AffiniPay.HostedFields.initializeFields(hostedFieldsConfiguration, hostedFieldsCallback)
hostedFields.disableIframeInput("#my_credit_card_field_id")
hostedFields.enableIframeInput("#my_credit_card_field_id")

To disable/enable all iframes, call hostedFields.disableIframeInput() or hostedFields.enableIframeInput() without any arguments.

Focus a field

To focus an input use the following method, passing the value from the field selector property in your hostedFieldsConfiguration object.

const hostedFields = window.AffiniPay.HostedFields.initializeFields(hostedFieldsConfiguration, hostedFieldsCallback)
hostedFields.focusIframeInput("#my_credit_card_field_id")

Clear a field

To clear an input use the following method, passing the value from the field selector property in your hostedFieldsConfiguration object.

const hostedFields = window.AffiniPay.HostedFields.initializeFields(hostedFieldsConfiguration, hostedFieldsCallback)
hostedFields.clearIframeInput("#my_credit_card_field_id")

To clear all iframes, call hostedFields.clearIframeInput() without any arguments.

5: Create a user-defined callback

The second parameter to window.AffiniPay.HostedFields.initializeFields is a user-defined callback function. The callback function is called with a single argument state, which is a JavaScript object containing the tokenization state for all hosted fields. The bulk of your JavaScript logic that interacts with hosted fields is likely to be contained within this callback function.

Here’s an example:

const hostedFieldsCallBack = function(state) {
  console.log(state)
}

The callback function will be called when the following input events occur:

The callback will also be called in intervals and when trying to recover from network failure.

The state argument passed to your callback function has three keys.

Here’s an example of the state argument passed to the callback function:

{
  "isReady": false,
  "target": {
    "selector": "#my_credit_card_field_id",
    "token": "TZiDwi9kJdhQRBVuL41F7LSspCDP1j7h",
    "type": "credit_card_number",
    "card": "visa",
    "luhn": true,
    "error": "",
    "focus": true
  },
  "fields": [
    {
      "selector": "#my_credit_card_field_id",
      "token": "TZiDwi9kJdhQRBVuL41F7LSspCDP1j7h",
      "type": "credit_card_number",
      "card": "visa",
      "luhn": true,
      "error": "",
      "focus": true
    },
    {
      "selector": "#my_cvv_field_id",
      "token": "M9Wte44S2p5smwqmsjFTJNuOTQ4OgSoH",
      "type": "cvv",
      "error": "",
      "focus": false
    }
  ]
}

6: Initialize hosted fields

To initialize hosted fields, call window.AffiniPay.HostedFields.initializeFields(config, callback).

const hostedFields = window.AffiniPay.HostedFields.initializeFields(config, callback)

Tokenization happens automatically and is triggered by events. The token will be visible in the state object passed to your callback function. Do not use or interact with these tokens directly; they appear on the state object in your callback function for visual feedback only.

Tokenization occurs on keydown and paste events if the input passes validation. Additionally tokenization occurs at intervals to ensure tokens have not expired. In the event of a network failure, a tokenization retry will be done with an exponential backoff.

7: Get a payment token

When the state object passed to your callback function has isReady set to true, all fields have tokenized successfully and you can call getPaymentToken(formData). You must include the required fields in your formData argument when calling getPaymentToken. The getPaymentToken(formData) function trades the hosted field tokens and other required parameters for a payment token from AffiniPay backend services.

When you create a charge, you will pass this payment token to your own backend services, which will make the charge.

Here’s an example:

form.onsubmit = function(event) {
  event.preventDefault()
  console.log(hostedFields.getState())
  if(!hostedFields.getState()) {
    //send error
    return
  }
  hostedFields.getPaymentToken({ "postal_code": postal_code, "exp_year": exp_year, "exp_month": exp_month })
  .then(function(result) {
    console.log(result.id)
    // If getPaymentToken returns successfully you may pass your payment token to your backend service.
  }).catch(function(err) {
    console.log(err)
  })
}

Your payment page must also handle the payment response returned from your web server, displaying any errors or updating the page with a receipt.

Exception and schema details

For detailed information about exceptions and schemas, see the Hosted fields reference.

Example page: credit card

Here’s an example of a payment form that includes hosted fields for credit card payments and the information required to support them.

Use this example as a guideline as you develop your own payment form. It is not intended to be a turnkey solution.

<html>
  <head>
    <title>Hosted Fields Payment Page</title>
    <script src="https://cdn.affinipay.com/hostedfields/1.4.0/fieldGen_1.4.0.js"></script>
    <style type="text/css">
      form {
        width: 500px;
        margin: 0 auto;
      }
      form input, form iframe {
        width: 100%;
        margin: 5px;
      }

      form iframe {
        border: none;
        height: 20px;
      }
    </style>
  </head>
  <body>
    <form id="form">

      <div>
        <label for="my_credit_card_field_id">Credit Card</label>
        <div id="my_credit_card_field_id"></div>
      </div>

      <div>
        <label for="exp_month">Exp Month</label>
        <input id="exp_month" type="text" name="exp_month">
      </div>

      <div>
        <label for="exp_year">Exp Year</label>
        <input id="exp_year" type="text" name="exp_year">
      </div>

      <div>
        <label for="my_cvv_field_id">CVV</label>
        <div id="my_cvv_field_id"></div>
      </div>

      <div>
        <label for="postal_code">Zip Code</label>
        <input id="postal_code" type="text" name="postal_code">
      </div>

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

    <script type="text/javascript">

      const style = {
        border: "1px solid rgb(204, 204, 204)",
        'border-style': 'inset',
        color: "#000",
        "font-size": "11px",
        "font-weight": "400",
        padding: "8px",
        width: "100%"
      };
      const hostedFieldsConfiguration = {
        publicKey: "m_1234567890",
        fields: [
          {
            selector: "#my_credit_card_field_id",
            input: {
              type: "credit_card_number",
              css: style
            }
          },
          {
            selector: "#my_cvv_field_id",
            input: {
              type: "cvv",
              css: style
            }
          }
        ]
      };

      const hostedFieldsCallBack = function(state) {
        console.log(state);
      };

      const hostedFields = window.AffiniPay.HostedFields.initializeFields(
        hostedFieldsConfiguration,
        hostedFieldsCallBack
      );

      form.onsubmit = function(event) {
        event.preventDefault();
        console.log(hostedFields.getState())

        const postal_code = document.getElementById('postal_code').value
        const exp_year = document.getElementById('exp_year').value
        const exp_month = document.getElementById('exp_month').value

        if(!hostedFields.getState()){
          //send error
          return;
        }
        hostedFields.getPaymentToken({ "postal_code": postal_code, "exp_year": exp_year, "exp_month": exp_month })
          .then(function(result){
            console.log(result.id)
            // If getPaymentToken returns successfully you may pass your payment token to your backend service.
          }).catch(function(err){
            console.log(err);
        });
      };
    </script>
  </body>
</html>

Example page: credit card and eCheck payments

If your payment page allows both credit card and eCheck payments, it must have separate config objects, initializeFields calls, and getPaymentToken calls for each.

Here’s an example of a payment form that includes hosted fields for credit card and eCheck payments and the information required to support them.

Use this example as a guideline as you develop your own payment form. It is not intended to be a turnkey solution.

<html>
<head>
    <title>Hosted Fields Payment Page</title>
    <script src="https://cdn.affinipay.com/hostedfields/1.4.0/fieldGen_1.4.0.js"></script>
    <style type="text/css">
        form {
            margin: 0 auto;
        }

        form input, form iframe {
            width: 100%;
            margin: 5px;
        }

        form iframe {
            border: none;
            height: 20px;
        }
    </style>
</head>
<body>
<form id="form" name="form">
    <table>
        <tr>
            <td>
                <table width="100%">
                    <tr>
                        <td>
                            <input type="radio" id="card_radio" name="payment_type_radio" value="card">
                        </td>
                        <td>
                            <label for="card_radio">Credit Card</label>
                        </td>
                        <td width="20px"></td>
                        <td>
                            <input type="radio" id="bank_radio" name="payment_type_radio" value="bank" >
                        </td>
                        <td>
                            <label for="bank_radio">eCheck</label>
                        </td>
                    </tr>
                </table>
            </td>
        </tr>
        <tr>
            <td>
                <div>
                    <label for="my_credit_card_field_id">Credit Card</label>
                    <div id="my_credit_card_field_id"></div>
                </div>

                <div>
                    <label for="exp_month">Exp Month</label>
                    <input id="exp_month" type="text" name="exp_month">
                </div>

                <div>
                    <label for="exp_year">Exp Year</label>
                    <input id="exp_year" type="text" name="exp_year">
                </div>

                <div>
                    <label for="my_cvv_field_id">CVV</label>
                    <div id="my_cvv_field_id"></div>
                </div>

                <div>
                    <label for="postal_code">Zip Code</label>
                    <input id="postal_code" type="text" name="postal_code">
                </div>
            </td>
            <td width="20px">

            </td>
            <td>
                <table width="100%">
                    <tr>
                        <td>
                            <input type="radio" id="business_radio" name="account_holder_type_radio" value="business">
                        </td>
                        <td>
                            <label for="business_radio">Business</label>
                        </td>
                        <td>
                            <input type="radio" id="personal_radio" name="account_holder_type_radio" value="personal">
                        </td>
                        <td>
                            <label for="personal_radio">Personal</label>
                        </td>
                    </tr>
                </table>
                <div>
                    <label for="my_routing_number_id">Routing Number</label>
                    <div id="my_routing_number_id"></div>
                </div>
                <div>
                    <label for="my_bank_account_number_id">Account Number</label>
                    <div id="my_bank_account_number_id"></div>
                </div>
                <table width="100%">
                    <tr>
                        <td>
                            <input type="radio" name='account_type_radio' id="checking_radio" value="checking">
                        </td>
                        <td>
                            <label for="checking_radio">Checking</label>
                        </td>
                        <td>
                            <input type="radio" name='account_type_radio' id="savings_radio" value="savings">
                        </td>
                        <td>
                            <label for="savings_radio">Savings</label>
                        </td>
                    </tr>
                </table>
                <div>
                    <label for="given_name">First Name</label>
                    <input id="given_name" type="text" name="given_name">
                </div>
                <div>
                    <label for="surname">Last Name</label>
                    <input id="surname" type="text" name="surname">
                </div>
                <div>
                    <label for="business_name">Business Name</label>
                    <input id="business_name" type="text" name="business_name">
                </div>

            </td>
        </tr>
    </table>
    <table>
        <tr>
            <td>
                <input type="submit" value="Submit"/>
            </td>
        </tr>
    </table>
</form>

<script type="text/javascript">

  const style = {
    border: "1px solid rgb(204, 204, 204)",
    'border-style': 'inset',
    color: "#000",
    "font-size": "11px",
    "font-weight": "400",
    padding: "8px",
    width: "100%"
  };
  const creditCardHostedFieldsConfig = {
    publicKey: "m_1234567890",
    fields: [
      {
        selector: "#my_credit_card_field_id",
        input: {
          type: "credit_card_number",
          css: style
        }
      },
      {
        selector: "#my_cvv_field_id",
        input: {
          type: "cvv",
          css: style
        }
      }
    ]
  };

  const eCheckHostedFieldsConfig = {
    publicKey: 'm_1234567890',
    css: style,
    fields: [
      {
        selector: '#my_bank_account_number_id',
        placeholder: 'Bank Account Number',
        input: {
          type: 'bank_account_number',
          css: style
        }
      },
      {
        selector: '#my_routing_number_id',
        input: {
          type: 'routing_number',
          css: style
        }
      }
    ]
  };

  const hostedFieldsCallBack = function (state) {
    console.log(state);
  };

  const creditCardHostedFields = window.AffiniPay.HostedFields.initializeFields(
    creditCardHostedFieldsConfig,
    hostedFieldsCallBack
  );

  const eCheckHostedFields = window.AffiniPay.HostedFields.initializeFields(
    eCheckHostedFieldsConfig,
    hostedFieldsCallBack
  );

  const form = document.getElementById('form');

  form.onsubmit = function (event) {
    event.preventDefault();
    console.log(creditCardHostedFields.getState());
    console.log(eCheckHostedFields.getState());

    if (form['card_radio'].checked && creditCardHostedFields.getState()) {
      const postal_code = document.getElementById('postal_code').value
      const exp_year = document.getElementById('exp_year').value
      const exp_month = document.getElementById('exp_month').value

      creditCardHostedFields.getPaymentToken({"postal_code": postal_code, "exp_year": exp_year, "exp_month": exp_month})
        .then(function (result) {
          console.log(result.id);
          // If getPaymentToken returns successfully you may pass your payment token to your backend service.
        }).catch(function (err) {
        console.log(err);
      });
    } else if (form['bank_radio'].checked && eCheckHostedFields.getState()) {
      const given_name = document.getElementById('given_name').value
      const surname = document.getElementById('surname').value
      const business_name = document.getElementById('business_name').value
      const isBusiness = document.getElementById('business_radio').checked
      const isSavings = document.getElementById('savings_radio').checked

      const extra_fields = {}
      extra_fields.account_holder_type = isBusiness ? 'business' : 'individual'
      extra_fields.account_type = isSavings ? 'savings' : 'checking'

      if (business_radio.checked) {
        extra_fields.name = business_name
      } else {
        extra_fields.given_name = given_name
        extra_fields.surname = surname
      }

      console.log(JSON.stringify(extra_fields))

      eCheckHostedFields.getPaymentToken(extra_fields).then(function (result) {
        console.log(result.id);
        // If getPaymentToken returns successfully you may pass your payment token to your backend service.
      }).catch(function (err) {
        console.log(err);
      });
    } else {
      console.log('error');
    }
  };
</script>
</body>
</html>

Next step

Once you have a payment form, you can create a charge.