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

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

<script src="https://cdn.affinipay.com/hostedfields/release/fieldGen.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.data.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.

Updating a saved payment method

Updating a saved payment method allows you to update information about a saved Card or a saved Bank, for instance, if a card has been renewed and the expiration date has changed, or a bank account holder’s name has a typo.

Updating a saved payment method without calling setSavedPaymentMethod

The merchant’s saved payment method is now updated.

// This saved payment method will come from your backend service
  const savedPaymentMethod = {
    "id": "QMC1gjkWQUu_qYKRyMSnRQ",
    "created": "2019-04-15T15:35:57.437Z",
    "modified": "2019-04-15T15:35:57.437Z",
    "address1": "123 memory lane",
    "city": "Austin",
    "state": "Texas",
    "postal_code": "78717",
    "type": "card",
    "number": "************4242",
    "fingerprint": "GunPelYVthifNV63LEw1",
    "card_type": "VISA",
    "exp_month": 10,
    "exp_year": 2030
  }

  // The hostedFields object is the return object from window.AffiniPay.HostedFields.initializeFields
  // Be sure to add a 'type' key that is card for updating saved cards, or 'bank' for updating saved banks
  hostedFields.updateSavedPaymentMethod({ exp_year: 2025, exp_month: 3, type: 'card' }, 'QMC1gjkWQUu_qYKRyMSnRQ')
  .then(function(updateToken){
    // Pass this token to your backend service, your backend service will be responsible for saving the charge
    console.log(updateToken)
  })

See Basic Example for an update saved payment method implementation

Updating a saved payment method calling setSavedPaymentMethod

The merchant’s saved payment method is now updated.

// This saved payment method will come from your backend service
  const savedPaymentMethod = {
    "id": "QMC1gjkWQUu_qYKRyMSnRQ",
    "created": "2019-04-15T15:35:57.437Z",
    "modified": "2019-04-15T15:35:57.437Z",
    "address1": "123 memory lane",
    "city": "Austin",
    "state": "Texas",
    "postal_code": "78717",
    "type": "card",
    "number": "************4242",
    "fingerprint": "GunPelYVthifNV63LEw1",
    "card_type": "VISA",
    "exp_month": 10,
    "exp_year": 2030
  }

  // The hostedFields object is the return object from window.AffiniPay.HostedFields.initializeFields
  hostedFields.setSavedPaymentMethod(savedPaymentMethod)

  // The hostedFields object is the return object from window.AffiniPay.HostedFields.initializeFields
  // Be sure to add a 'type' key that is card for updating saved cards, or 'bank' for updating saved banks
  hostedFields.updateSavedPaymentMethod({ exp_year: 2021, exp_month: 5, 'type': card })
  .then(function(updateToken){
    // Pass this token to your backend service, your backend service will be responsible for saving the charge
    console.log(updateToken)
  })

See Basic Example for an update saved payment method implementation

Exception and schema details

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

Example page

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

<html>
  <head>
    <title>Hosted Fields Payment Page</title>
    <script src="https://cdn.affinipay.com/hostedfields/release/fieldGen.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.data.id)
            // If getPaymentToken returns successfully you may pass your payment token to your backend service.
          }).catch(function(err){
            console.log(err);
        });
      };
    </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.