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.

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
  5. Create a user-defined callback
  6. Initialize hosted fields
  7. Get a payment token

Hosted fields versioning

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.0.1 is:

https://cdn.affinipay.com/hostedfields/1.0.1/fieldGen_1.0.1.js

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

1.0.0 Release

integrity="sha384-rw98TpeT1hZMElF/dWRgUa2njBdgDddqTgn7NpOt+i5Yt0fDdgAtUs4K5gooVmkw"

1.0.1 Release

integrity="sha384-3moF/q/WMU1/nVjvtAtOKtcWklA1/swIfC5HnH3F5JFEDDC5Zjos5kUPZE14Ywwq"

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.0.1/fieldGen_1.0.1.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%"
        }
      }
    }
  ]
}

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.

<html>
  <head>
    <title>Hosted Fields Payment Page</title>
    <script src="https://cdn.affinipay.com/hostedfields/1.0.1/fieldGen_1.0.1.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())
        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

Note: 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.

<html>
<head>
    <title>Hosted Fields Payment Page</title>
    <script src="https://cdn.affinipay.com/hostedfields/1.0.1/fieldGen_1.0.1.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()) {
      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()) {
      eCheckHostedFields.getPaymentToken({
        'given_name': given_name,
        'surname': surname,
        'business_name': business_name,
        'account_holder_type': business_radio.checked ? 'business' : 'individual',
        'account_type': savings_radio.checked ? 'savings' : 'checking'
      }).then(function (result) {
        console.log('got here');
        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>

Additional examples

AffiniPay has live examples to help you get started. These are only meant to show styling; they are not turnkey solutions.

Next step

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