How to customize and style the 2Pay.js payment form
Overview
The 2Pay.js payment form can be customized and styled by providing an object as the second argument when calling the create method from component service in the TwoPayClient JavaScript SDK.
Before moving on to the payment form customization, let's take a look at how the elements work and how they are rendered by the browser.
To implement the documentation below, you need to have a 2Pay.js client already set up and a payment form already working inside your web page. If not, read this article to see the basic implementation of 2Pay.js.
Merchant’s Web Page and the 2Pay.js Form
As you probably noticed, the payment form renders inside an iFrame so the merchant’s page CSS will have to be written separately from the payment form. For example, you have a Full name field which is on the merchant’s page and the credit card, CVV and expiration date fields that are in the iFrame. However, the entire form will have to look and behave the same and the only way to customize the iFrame is by using the style configuration.
Payment Form Elements and Behavior
Rendering the payment form with the default styling (no styles applied) will consist of the following structure.
Generic elements
You can use generic elements as high-level HTML elements.
<body>
<div>
<form class="form">
<div class="row">
<div class="col">
<!-- Credit card number field content goes here -->
</div>
</div>
<div class="row">
<div class="col">
<!-- Expiration date field content goes here -->
</div>
<div class="col">
<!-- CVV field content goes here -->
</div>
</div>
</form>
</div>
</body>
The Payment Form <body> Element
The payment form is rendered inside an iFrame and it needs to have an HTML, a <body> and a <head> tag.
CSS rules such as font, color, and background can be applied to the <body> by providing styles in the style object, as shown in the example below.
let component = jsPaymentClient.components.create('card', {
margin : 0,
fontFamily : 'Helvetica, sans-serif',
fontSize : '1rem',
fontWeight : '400',
lineHeight : '1.5',
color : '#212529',
textAlign : 'left',
backgroundColor : '#ffffff'
});
HTML Elements <div class=”row”> and <div class=”col”>
The purpose of these two elements is to offer a way to divide the payment form content. For example, using rows and columns by applying CSS rules for them. Below is an example of how the payment form can display with the credit card number field on a row and expiration date field and CVV field on another row with two columns.
var component = twoPayClient.components.create('card', {
'.row': {
display : 'flex',
flexWrap: 'wrap'
},
'.col': {
flexBasis: '0',
flexGrow : '1',
maxWidth : '100%',
padding : '0',
position : 'relative',
width : '100%'
},
});
Card number field
The card number field will be rendered together with several other HTML elements that are represented and explained below.
<body>
<div>
<form class="form">
<div class="row">
<div class="col">
<!-- Credit card number field START -->
<div class="field-container card-number">
<div class="field-wrapper form-group is-error is-empty is-required">
<label for="card-number" class="label control-label">Card number</label>
<div class="input-wrapper">
<input type="text" autocomplete="cc-number" id="card-number" name="card-number" class="field form-control">
<i class="card-icon"></i>
<i class="lock-icon"></i>
<!-- default state of the error or valid icon -->
<i class="" style="display: none;"></i>
</div>
</div>
<span class="validation-message">Enter a valid card number.</span>
</div>
<!-- Credit card number field END -->
...
The field container <div class=”field-container card-number”>
It holds all the card number field elements and it can customizable, as shown in the example below.
var component = twoPayClient.components.create('card', {
'.field-container': {
paddingBottom: '14px'
}
});
The field wrapper <div class=”field-wrapper”>
It contains the label, input, and icons and offers support for different states (validation or transition):
- default → Contains the base class names “field-wrapper form-group is-error is-empty is-required“. Usually, you will apply only some spacing to this element:
var component = twoPayClient.components.create('card', {
'.field-wrapper': {
paddingRight: '25px'
}
});
- focus → Offers support for when the input is focused by adding the class name “is-focused” to this element.
- error → Offers support for when there is a validation error and adds the class name “is-error“ to this element.
- valid → Offers support for when the field validation passes and adds the class name ”is-valid” to this element.
The field’s label <label class="label control-label">
Styling the field’s label:
var component = twoPayClient.components.create('card', {
label: {
display : 'inline-block',
marginBottom: '9px',
color : '#313131',
fontSize : '14px',
fontWeight : '300',
lineHeight : '17px'
}
});
Styling the field’s label on focus:
var component = twoPayClient.components.create('card', {
'.is-focused label': {
color: '#83ddeb'
}
});
Styling the field’s label on error:
var component = twoPayClient.components.create('card', {
'.is-error label': {
color: '#D9534F'
}
});
Styling the field’s label when valid:
var component = twoPayClient.components.create('card', {
'.is-valid label': {
color: '#1BB43F'
}
});
The input wrapper <div class="input-wrapper">
Offers support for absolute positioning of the icons:
var component = twoPayClient.components.create('card', {
'.input-wrapper': {
position: 'relative'
}
});
The input <input type=”text” class=”field form-control”>
This is the actual input field where the user types in the data.
Styling the input:
var component = twoPayClient.components.create('card', {
'input': {
fontSize: '18px'
}
});
Styling the input on focus:
var component = twoPayClient.components.create('card', {
'input:focus': {
border : '1px solid #5D5D5D',
backgroundColor: '#FFFDF2'
}
});
Styling the input on error:
var component = twoPayClient.components.create('card', {
'.is-error input': {
border: '1px solid #D9534F'
}
});
Styling the input when valid:
var component = twoPayClient.components.create('card', {
'.is-valid input': {
border: '1px solid #1BB43F'
}
});
The card icon <i class="card-icon"></i> or <i class="card-type-icon"></i> (card number specific element)
This element represents the card icon which appears by default in the card number field. The “card-icon” class name changes into “card-type-icon“ after the credit card auto-detection returns a card type.
This element’s style can also be customized:
var component = twoPayClient.components.create('card', {
'.card-icon': {
left: '10px'
},
'.card-type-icon': {
right: '30px'
}
});
The lock icon <i class="lock-icon"></i> (available for card number and CVV fields)
This element can be customized the same way as the card icon or it can be hid.
var component = twoPayClient.components.create('card', {
'.lock-icon': {
display: 'none'
}
});
The lock icon <i class="error-icon"></i> or <i class="valid-icon"></i>
This element represents the error of valid icons. When an error is present, the element will have the class name “error-icon“, or if the field is valid, the element will have the “valid-icon“ class name. There is no class name on default state.
The example below shows how to style the error and valid icons:
var component = twoPayClient.components.create('card', {
'.valid-icon': {
right: '-25px'
},
'.error-icon': {
right: '-25px'
}
});
The validation message element <span class="validation-message">
This element displays only whether an error is present for the current field.
Here's how you can customize the validation message element:
var component = twoPayClient.components.create('card', {
'.validation-message': {
color : '#D9534F',
fontSize : '10px',
fontStyle : 'italic',
marginTop : '6px',
marginBottom: '-5px',
display : 'block',
lineHeight : '1'
}
});
The above styling can be applied to all payment form fields with some exceptions:
- card-icon, card-type-icon – are only available for the card number field;
- lock-icon – is only available for the card number and CVV fields;
- you cannot pass any URL references in the style object. The following example will NOT work:
// Non working example
var component = twoPayClient.components.create('card', {
'.valid-icon': {
backgroundImage: 'url("https://my-web-site.com/some-valid-image.png")'
}
});
The Style Object
Default style object
The default style object used, if no other style object has been provided, is the one below:
{
margin: 0,
fontFamily: 'Helvetica, sans-serif',
fontSize: '1rem',
fontWeight: '400',
lineHeight: '1.5',
color: '#212529',
textAlign: 'left',
backgroundColor: '#ffffff',
'*': {
'boxSizing': 'border-box'
},
'.no-gutters': {
marginRight: 0,
marginLeft: 0
},
'.row': {
display: 'flex',
flexWrap: 'wrap'
},
'.col': {
flexBasis: '0',
flexGrow: '1',
maxWidth: '100%',
padding: '0',
position: 'relative',
width: '100%'
},
'div': {
display: 'block'
},
'.field-container': {
paddingBottom: '14px'
},
'.field-wrapper': {
paddingRight: '25px'
},
'.input-wrapper': {
position: 'relative'
},
label: {
display: 'inline-block',
marginBottom: '9px',
color: '#313131',
fontSize: '14px',
fontWeight: '300',
lineHeight: '17px'
},
'input': {
overflow: 'visible',
margin: 0,
fontFamily: 'inherit',
display: 'block',
width: '100%',
height: '42px',
padding: '10px 12px',
fontSize: '18px',
fontWeight: '400',
lineHeight: '22px',
color: '#313131',
backgroundColor: '#fff',
backgroundClip: 'padding-box',
border: '1px solid #CBCBCB',
borderRadius: '3px',
transition: 'border-color .15s ease-in-out,box-shadow .15s ease-in-out',
outline: 0
},
'input:focus': {
border: '1px solid #5D5D5D',
backgroundColor: '#FFFDF2'
},
'.is-error input': {
border: '1px solid #D9534F'
},
'.is-error input:focus': {
backgroundColor: '#D9534F0B'
},
'.is-valid input': {
border: '1px solid #1BB43F'
},
'.is-valid input:focus': {
backgroundColor: '#1BB43F0B'
},
'.validation-message': {
color: '#D9534F',
fontSize: '10px',
fontStyle: 'italic',
marginTop: '6px',
marginBottom: '-5px',
display: 'block',
lineHeight: '1'
},
'.card-expiration-date': {
paddingRight: '.5rem'
},
'.is-empty input': {
color: '#EBEBEB'
},
'.lock-icon': {
top: 'calc(50% - 7px)',
right: '10px'
},
'.valid-icon': {
top: 'calc(50% - 8px)',
right: '-25px'
},
'.error-icon': {
top: 'calc(50% - 8px)',
right: '-25px'
},
'.card-icon': {
top: 'calc(50% - 10px)',
left: '10px',
display: 'none'
},
'.is-empty .card-icon': {
display: 'block'
},
'.is-focused .card-icon': {
display: 'none'
},
'.card-type-icon': {
right: '30px',
display: 'block'
},
'.card-type-icon.visa': {
top: 'calc(50% - 14px)'
},
'.card-type-icon.mastercard': {
top: 'calc(50% - 14.5px)'
},
'.card-type-icon.amex': {
top: 'calc(50% - 14px)'
},
'.card-type-icon.discover': {
top: 'calc(50% - 14px)'
},
'.card-type-icon.jcb': {
top: 'calc(50% - 14px)'
},
'.card-type-icon.dankort': {
top: 'calc(50% - 14px)'
},
'.card-type-icon.cartebleue': {
top: 'calc(50% - 14px)'
},
'.card-type-icon.diners': {
top: 'calc(50% - 14px)'
},
'.card-type-icon.elo': {
top: 'calc(50% - 14px)'
}
}
Allowed CSS Properties
[
// orphan properties
'bottom',
'clear',
'clip',
'color',
'cursor',
'direction',
'display',
'float',
'height',
'left',
'letterSpacing',
'opacity',
'position',
'right',
'top',
'verticalAlign',
'visibility',
'width',
'zIndex',
// align
'alignContent',
'alignItems',
'alignSelf',
// background
'backgroundBlendMode',
'backgroundClip',
'backgroundColor',
'backgroundPosition',
'backgroundRepeat',
'backgroundSize',
'background',
// border
'borderBottomColor',
'borderBottomLeftRadius',
'borderBottomRightRadius',
'borderBottomStyle',
'borderBottomWidth',
'borderBottom',
'borderCollapse',
'borderColor',
'borderLeftColor',
'borderLeftStyle',
'borderLeftWidth',
'borderLeft',
'borderRadius',
'borderRightColor',
'borderRightStyle',
'borderRightWidth',
'borderRight',
'borderSpacing',
'borderStyle',
'borderTopColor',
'borderTopLeftRadius',
'borderTopRightRadius',
'borderTopStyle',
'borderTopWidth',
'borderTop',
'borderWidth',
'border',
// box
'boxDecorationBreak',
'boxShadow',
'boxSizing',
// break
'breakAfter',
'breakBefore',
'breakInside',
// flex
'flex',
'flexBasis',
'flexDirection',
'fontFeatureSettings',
'flexFlow',
'flexGrow',
'flexShrink',
'flexWrap',
// font
'fontFamily',
'fontKerning',
'fontLanguageOverride',
'fontSizeAdjust',
'fontSize',
'fontStretch',
'fontStyle',
'fontSynthesis',
'fontVariant',
'fontVariantAlternates',
'fontVariantCaps',
'fontVariantEastAsian',
'fontVariantLigatures',
'fontVariantNumeric',
'fontVariantPosition',
'fontWeight',
'font',
// justify
'justifyContent',
'justifyItems',
'justifySelf',
// line
'lineBreak',
'lineHeight',
// list
'listStylePosition',
'listStyleType',
'listStyle',
// margin
'marginBottom',
'marginLeft',
'marginRight',
'marginTop',
'margin',
// max
'maxHeight',
'maxWidth',
// min
'minHeight',
'minWidth',
// outline
'outlineColor',
'outlineOffset',
'outlineStyle',
'outlineWidth',
'outline',
// overflow
'overflow',
'overflowWrap',
'overflowX',
'overflowY',
// padding
'paddingBottom',
'paddingLeft',
'paddingRight',
'paddingTop',
'padding',
//text
'textAlign',
'textAlignLast',
'textDecoration',
'textDecorationColor',
'textDecorationLine',
'textDecorationSkip',
'textDecorationStyle',
'textIndent',
'textOverflow',
'textShadow',
'textTransform',
'textUnderlinePosition',
// transform
'transform',
'transformOrigin',
'transformStyle',
'transition',
'transitionDelay',
'transitionDuration',
'transitionProperty',
'transitionTimingFunction',
// word
'wordBreak',
'wordSpacing',
'wordWrap'
]
Allowed CSS Pseudo-classes
[
':hover',
':focus',
':active',
':disabled',
'::placeholder',
'::after',
'::before'
]