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:
- Add a reference to the AffiniPay hosted fields JavaScript library
- Create a payment form
- Create a hosted fields configuration object
- Style the hosted fields
- Style the content inside hosted fields
- Enable surcharge
- Disable or enable fields
- Focus a field
- Clear a field
- Create a user-defined callback
- Initialize hosted fields
- 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.4.9 is:
https://cdn.affinipay.com/hostedfields/1.4.9-prod/fieldGen_1.4.9.js
For SRI and integrity
attribute purposes, here are the SHA384 digests:
1.4.9 Release
integrity="sha384-ntyY/Qasy+kUeGveCOHkcVXfh6yDTXH8QEA2NJlyP4cxAEYixDRBDpEJyzIajYOO" 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.9-prod/fieldGen_1.4.9.js"></script>
The AffiniPay hosted fields library binds the following functions to the window.AffiniPay.HostedFields
namespace:
window.AffiniPay.HostedFields.initializeFields(config, callback)
to instantiate hosted fields. This is the only function you are required to call to use AffiniPay hosted fields.window.AffiniPay.HostedFields.parseException(hostedFieldException)
to parse exception objects thrown by the hosted fields library.window.AffiniPay.HostedFields.exceptionMap(errorCode)
to look up an exception type of an error code thrown by the hosted fields library.
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.
- For credit card payments, card number and CVV <input> elements must be replaced with hosted fields <div> elements.
- For eCheck payments, account number and routing number <input> elements must be replaced with hosted fields <div> elements.
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 merchant’s public key, which is required to tokenize payment details on behalf of the merchant. The merchant’s public key is safe to expose in web pages (as opposed to secret keys, which must be safeguarded).
An optional
input
key that contains styling that applies to all hosted fields.- Two
fieldConfig
objects that contain individual hosted field configurations. ThefieldConfig
objects are passed to theconfig
object as an array under thefields
key.- A credit card payment page must have one
fieldConfig
of typecredit_card_number
and one of typecvv
. - An eCheck payment page must have one
fieldConfig
of typebank_account_number
one of typerouting_number
.
In each
fieldConfig
object, set theselector
key to theid
of the corresponding <div> in the payment form. - A credit card payment page must have one
The window.AffiniPay.HostedFields.initializeFields
function returns an object with the following methods. Be sure to save this object.
- The
getState()
method returns the current tokenization state of hosted fields. - The
getPaymentToken(formData)
method gets a payment token by passing your hosted field tokens and form data to your AffiniPay backend services. This is required for completing a charge. - The
updateSavedPaymentMethod(formData)
uses the the ID of a previously saved payment method from setSavedPaymentMethod (either Card or Bank) and a form data JSON object of fields to be updated and returns a one-time token to be used for updating a saved payment method. - The
updateSavedPaymentMethod(formData, paymentMethodId)
takes the ID of a saved payment method (either Card or Bank) and a form data JSON object of fields to be updated and returns a one-time token to be used for updating a saved payment method. This call doesn’t require a previous call to setSavedPaymentMethod. - The
setSavedPaymentMethod(paymentMethod)
sets a saved payment method. This function will disable hosted fields and attempt to populate your payment form with data from the saved payment method. It can be used with updateSavedPaymentMethod to avoid passing in a paymentMethodId to updateSavedPaymentMethod. - The
clearSavedPaymentMethod()
clears a currently saved payment method. This will enable hosted fields and clear input elements.
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.
- To style your page and the <iframe> elements (including their size), use your stylesheet.
- For the content inside the hosted fields—the <input> element contained within the <iframe>, provide styling information within the
config
andfieldConfig
objects. Styling within the hosted fields is defined (from highest to lowest precedence) by:fieldConfig
styling,config
styling, hosted fields library defaults, and browser defaults.
Within the css
key for a config
or fieldConfig
object, you can:
- Pass any valid CSS as a JSON object.
- Style input states (such as :focus, :valid, and :invalid) by passing in a nested style object.
- Style placeholder (::placeholder) text by passing in a nested style object.
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.
surchargeable: true
indicates that the card can be surcharged.surchargeable: false
indicates that the card cannot be surcharged.surchargeable: "unknown"
indicates that the surcharge decision making has not yet happened. The hosted fields library requires at least 12 digits in the card number input to trigger the surcharge decision making process.
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:
- valid keydown
- paste
- focus
- blur
The callback will also be called in intervals and when trying to recover from network failure.
- interval
- retry
The state argument passed to your callback function has three keys.
isReady
is a flag that will betrue
if all hosted fields have tokenized successfully. After all fields are tokenized, you can callgetPaymentToken(formData)
.target
is the the state of the hosted field that generated the most recent event. This is useful for CSS changes. The target is the only way the parent page knows a user has navigated into and out of a hosted field.fields
is an array of all hosted fields states.
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.9-prod/fieldGen_1.4.9.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.9-prod/fieldGen_1.4.9.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',
input: {
type: 'bank_account_number',
placeholder: '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.