React Payment Inputs
A zero-dependency React Hook & Container to help with payment card input fields.
Requirements
Ensure you are running on a hooks-compatible version of React (v16.8 & above).
Installation
npm install react-payment-inputs styled-components --save
or install with Yarn if you prefer:
yarn add react-payment-inputs styled-components
React Payment Inputs requires styled-components to be installed as a dependency.
Usage
By default (as seen above), React Payment Inputs does not come with built-in styling meaning that you can easily adapt React Payment Inputs to your own design system.
However, if you would like to use the built-in styles as seen in the animation above, read "Using the built-in styled wrapper".
With hooks
If you'd like to use the hooks version of React Payment Inputs, you can import usePaymentInputs
into your component.
import React from 'react';
import { usePaymentInputs } from 'react-payment-inputs';
export default function PaymentInputs() {
const { meta, getCardNumberProps, getExpiryDateProps, getCVCProps } = usePaymentInputs();
return (
<div>
<input {...getCardNumberProps({ onChange: handleChangeCardNumber })} value={cardNumber} />
<input {...getExpiryDateProps({ onChange: handleChangeExpiryDate })} value={expiryDate} />
<input {...getCVCProps({ onChange: handleChangeCVC })} value={cvc} />
{meta.isTouched && meta.error && <span>Error: {meta.error}</span>}
</div>
);
}
By spreading the prop getter functions (e.g. {...getCardNumberProps()}
) on the inputs as shown above, React Payment Inputs will automatically handle the formatting, focus & validation logic for you.
IMPORTANT: You must place your event handlers (e.g. onChange
, onBlur
, etc) inside the prop getter function (e.g. getCardNumberProps()
) so the default event handlers in React Payment Inputs don't get overridden.
With render props
If you'd like to use the render props version of React Payment Inputs, you can import PaymentInputsContainer
into your component.
The props of <PaymentInputsContainer>
are the same as the hook options and the render props are the same as the hook data.
import React from 'react';
import { PaymentInputsContainer } from 'react-payment-inputs';
export default function PaymentInputs() {
return (
<PaymentInputsContainer>
{({ meta, getCardNumberProps, getExpiryDateProps, getCVCProps }) => (
<div>
<input {...getCardNumberProps({ onChange: handleChangeCardNumber })} value={cardNumber} />
<input {...getExpiryDateProps({ onChange: handleChangeExpiryDate })} value={expiryDate} />
<input {...getCVCProps({ onChange: handleChangeCVC })} value={cvc} />
{meta.isTouched && meta.error && <span>Error: {meta.error}</span>}
</div>
)}
</PaymentInputsContainer>
);
}
IMPORTANT: You must place your event handlers (e.g. onChange
, onBlur
, etc) inside the prop getter function (e.g. getCardNumberProps()
) so the default event handlers in React Payment Inputs don't get overridden.
Using the built-in styled wrapper
By default, React Payment Inputs does not have built-in styling for it's inputs. However, React Payment Inputs comes with a styled wrapper which combines the card number, expiry & CVC fields seen below:
import React from 'react';
import { PaymentInputsWrapper, usePaymentInputs } from 'react-payment-inputs';
import images from 'react-payment-inputs/images';
export default function PaymentInputs() {
const {
wrapperProps,
getCardImageProps,
getCardNumberProps,
getExpiryDateProps,
getCVCProps
} = usePaymentInputs();
return (
<PaymentInputsWrapper {...wrapperProps}>
<svg {...getCardImageProps({ images })} />
<input {...getCardNumberProps()} />
<input {...getExpiryDateProps()} />
<input {...getCVCProps()} />
</PaymentInputsWrapper>
);
}
More examples
data = usePaymentInputs(options)
returns an object (data
)
options
Object({ errorMessages, onBlur, onChange, onError, onTouch })
options.errorMessages
Object
Set custom error messages for the inputs.
Example
const { ... } = usePaymentInputs({
errorMessages: {
emptyCardNumber: 'El número de la tarjeta es inválido',
invalidCardNumber: 'El número de la tarjeta es inválido',
emptyExpiryDate: 'La fecha de expiración es inválida',
monthOutOfRange: 'El mes de expiración debe estar entre 01 y 12',
yearOutOfRange: 'El año de expiración no puede estar en el pasado',
dateOutOfRange: 'La fecha de expiración no puede estar en el pasado',
invalidExpiryDate: 'La fecha de expiración es inválida',
emptyCVC: 'El código de seguridad es inválido',
invalidCVC: 'El código de seguridad es inválido'
}
})
options.onBlur
function(event)
Function to handle the blur event on the inputs. It is invoked when any of the inputs blur.
options.onChange
function(event)
Function to handle the change event on the inputs. It is invoked when any of the inputs change.
options.onError
function(error, erroredInputs)
Function to invoke when any of the inputs error.
options.onTouch
function(touchedInput, touchedInputs)
Function to invoke when any of the inputs are touched.
data
getCardNumberProps
function(overrideProps)
| returns Object<props>
Returns the props to apply to the card number input.
IMPORTANT: You must place your event handlers (e.g. onChange
, onBlur
, etc) inside the getCardNumberProps()
so the default event handlers in React Payment Inputs don't get overridden.
Example snippet
<input {...getCardNumberProps({ onBlur: handleBlur, onChange: handleChange })} />
getExpiryDateProps
function(overrideProps)
| returns Object<props>
Returns the props to apply to the expiry date input.
IMPORTANT: You must place your event handlers (e.g. onChange
, onBlur
, etc) inside the getExpiryDateProps()
so the default event handlers in React Payment Inputs don't get overridden.
Example snippet
<input {...getExpiryDateProps({ onBlur: handleBlur, onChange: handleChange })} />
getCVCProps
function(overrideProps)
| returns Object<props>
Returns the props to apply to the CVC input.
IMPORTANT: You must place your event handlers (e.g. onChange
, onBlur
, etc) inside the getCVCProps()
so the default event handlers in React Payment Inputs don't get overridden.
Example snippet
<input {...getCVCProps({ onBlur: handleBlur, onChange: handleChange })} />
getZIPProps
function(overrideProps)
| returns Object<props>
Returns the props to apply to the ZIP input.
IMPORTANT: You must place your event handlers (e.g. onChange
, onBlur
, etc) inside the getZIPProps()
so the default event handlers in React Payment Inputs don't get overridden.
Example snippet
<input {...getZIPProps({ onBlur: handleBlur, onChange: handleChange })} />
getCardImageProps
function({ images })
| returns Object<props>
Returns the props to apply to the card image SVG.
This function only supports SVG elements currently. If you have a need for another format, please raise an issue.
You can also supply custom card images using the images
attribute. The example below uses the default card images from React Payment Inputs.
Example snippet
import images from 'react-payment-inputs/images';
<svg {...getCardImageProps({ images })} />
meta.cardType
Object
Returns information about the current card type, including: name, lengths and formats.
Example snippet
const { meta } = usePaymentInputs();
<span>Current card: {meta.cardType.displayName}</span>
meta.error
string
Returns the current global error between all rendered inputs.
Example snippet
const { meta } = usePaymentInputs();
console.log(meta.error);
meta.isTouched
boolean
Returns the current global touched state between all rendered inputs.
meta.erroredInputs
Object
Returns the error message of each rendered input.
Example snippet
const { meta } = usePaymentInputs();
console.log(meta.erroredInputs);
meta.touchedInputs
Object
Returns the touch state of each rendered input.
Example snippet
const { meta } = usePaymentInputs();
console.log(meta.touchedInputs);
meta.focused
string
Returns the current focused input.
const { meta } = usePaymentInputs();
console.log(meta.focused);
wrapperProps
Object
Returns the props to apply to <PaymentInputsWrapper>
.
<PaymentInputsWrapper>
props
styles
Object
Custom styling to pass through to the wrapper. Either a styled-component's css
or an Object can be passed.
Schema
{
fieldWrapper: {
base: css | Object,
errored: css | Object
},
inputWrapper: {
base: css | Object,
errored: css | Object,
focused: css | Object
},
input: {
base: css | Object,
errored: css | Object,
cardNumber: css | Object,
expiryDate: css | Object,
cvc: css | Object
},
errorText: {
base: css | Object
}
}
errorTextProps
Object
Custom props to pass to the error text component.
inputWrapperProps
Object
Custom props to pass to the input wrapper component.
Using a third-party UI library
React Payment Inputs allows you to integrate into pretty much any React UI library. Below are a couple of examples of how you can fit React Payment Inputs into a UI library using usePaymentInputs
. You can also do the same with <PaymentInputsContainer>
.
Fannypack
import React from 'react';
import { FieldSet, InputField } from 'fannypack';
import { usePaymentInputs } from 'react-payment-inputs';
import images from 'react-payment-inputs/images';
export default function PaymentInputs() {
const {
meta,
getCardNumberProps,
getExpiryDateProps,
getCVCProps
} = usePaymentInputs();
const { erroredInputs, touchedInputs } = meta;
return (
<FieldSet isHorizontal>
<InputField
// Here is where React Payment Inputs injects itself into the input element.
{...getCardNumberProps()}
placeholder="0000 0000 0000 0000"
label="Card number"
inputRef={getCardNumberProps().ref}
// You can retrieve error state by making use of the error & touched attributes in `meta`.
state={erroredInputs.cardNumber && touchedInputs.cardNumber ? 'danger' : undefined}
validationText={touchedInputs.cardNumber && erroredInputs.cardNumber}
maxWidth="15rem"
/>
<InputField
{...getExpiryDateProps()}
label="Expiry date"
inputRef={getExpiryDateProps().ref}
state={erroredInputs.expiryDate && touchedInputs.expiryDate ? 'danger' : undefined}
validationText={touchedInputs.expiryDate && erroredInputs.expiryDate}
maxWidth="8rem"
/>
<InputField
{...getCVCProps()}
placeholder="123"
label="CVC"
inputRef={getCVCProps().ref}
state={erroredInputs.cvc && touchedInputs.cvc ? 'danger' : undefined}
validationText={touchedInputs.cvc && erroredInputs.cvc}
maxWidth="5rem"
/>
</FieldSet>
);
}
Bootstrap
import React from 'react';
import { FieldSet, InputField } from 'fannypack';
import { usePaymentInputs } from 'react-payment-inputs';
import images from 'react-payment-inputs/images';
export default function PaymentInputs() {
const {
meta,
getCardNumberProps,
getExpiryDateProps,
getCVCProps
} = usePaymentInputs();
const { erroredInputs, touchedInputs } = meta;
return (
<Form>
<Form.Row>
<Form.Group as={Col} style={{ maxWidth: '15rem' }}>
<Form.Label>Card number</Form.Label>
<Form.Control
// Here is where React Payment Inputs injects itself into the input element.
{...getCardNumberProps()}
// You can retrieve error state by making use of the error & touched attributes in `meta`.
isInvalid={touchedInputs.cardNumber && erroredInputs.cardNumber}
placeholder="0000 0000 0000 0000"
/>
<Form.Control.Feedback type="invalid">{erroredInputs.cardNumber}</Form.Control.Feedback>
</Form.Group>
<Form.Group as={Col} style={{ maxWidth: '10rem' }}>
<Form.Label>Expiry date</Form.Label>
<Form.Control
{...getExpiryDateProps()}
isInvalid={touchedInputs.expiryDate && erroredInputs.expiryDate}
/>
<Form.Control.Feedback type="invalid">{erroredInputs.expiryDate}</Form.Control.Feedback>
</Form.Group>
<Form.Group as={Col} style={{ maxWidth: '7rem' }}>
<Form.Label>CVC</Form.Label>
<Form.Control
{...getCVCProps()}
isInvalid={touchedInputs.cvc && erroredInputs.cvc}
placeholder="123"
/>
<Form.Control.Feedback type="invalid">{erroredInputs.cvc}</Form.Control.Feedback>
</Form.Group>
</Form.Row>
</Form>
);
}
Form library examples
React Payment Inputs has support for any type of React form library. Below are examples using Formik & React Final Form.
Formik
import { Formik, Field } from 'formik';
import { PaymentInputsWrapper, usePaymentInputs } from 'react-payment-inputs';
function PaymentForm() {
const {
meta,
getCardImageProps,
getCardNumberProps,
getExpiryDateProps,
getCVCProps,
wrapperProps
} = usePaymentInputs();
return (
<Formik
initialValues={{
cardNumber: '',
expiryDate: '',
cvc: ''
}}
onSubmit={data => console.log(data)}
validate={() => {
let errors = {};
if (meta.erroredInputs.cardNumber) {
errors.cardNumber = meta.erroredInputs.cardNumber;
}
if (meta.erroredInputs.expiryDate) {
errors.expiryDate = meta.erroredInputs.expiryDate;
}
if (meta.erroredInputs.cvc) {
errors.cvc = meta.erroredInputs.cvc;
}
return errors;
}}
>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div>
<PaymentInputsWrapper {...wrapperProps}>
<svg {...getCardImageProps({ images })} />
<Field name="cardNumber">
{({ field }) => (
<input {...getCardNumberProps({ onBlur: field.onBlur, onChange: field.onChange })} />
)}
</Field>
<Field name="expiryDate">
{({ field }) => (
<input {...getExpiryDateProps({ onBlur: field.onBlur, onChange: field.onChange })} />
)}
</Field>
<Field name="cvc">
{({ field }) => <input {...getCVCProps({ onBlur: field.onBlur, onChange: field.onChange })} />}
</Field>
</PaymentInputsWrapper>
</div>
<Button marginTop="major-2" type="submit">
Submit
</Button>
</form>
)}
</Formik>
);
}
See this example in Storybook
React Final Form
import { Form, Field } from 'react-final-form';
import { PaymentInputsWrapper, usePaymentInputs } from 'react-payment-inputs';
function PaymentForm() {
const {
meta,
getCardImageProps,
getCardNumberProps,
getExpiryDateProps,
getCVCProps,
wrapperProps
} = usePaymentInputs();
return (
<Form
onSubmit={data => console.log(data)}
validate={() => {
let errors = {};
if (meta.erroredInputs.cardNumber) {
errors.cardNumber = meta.erroredInputs.cardNumber;
}
if (meta.erroredInputs.expiryDate) {
errors.expiryDate = meta.erroredInputs.expiryDate;
}
if (meta.erroredInputs.cvc) {
errors.cvc = meta.erroredInputs.cvc;
}
return errors;
}}
>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div>
<PaymentInputsWrapper {...wrapperProps}>
<svg {...getCardImageProps({ images })} />
<Field name="cardNumber">
{({ input }) => (
<input {...getCardNumberProps({ onBlur: input.onBlur, onChange: input.onChange })} />
)}
</Field>
<Field name="expiryDate">
{({ input }) => (
<input {...getExpiryDateProps({ onBlur: input.onBlur, onChange: input.onChange })} />
)}
</Field>
<Field name="cvc">
{({ input }) => <input {...getCVCProps({ onBlur: input.onBlur, onChange: input.onChange })} />}
</Field>
</PaymentInputsWrapper>
</div>
<Button marginTop="major-2" type="submit">
Submit
</Button>
</form>
)}
</Form>
);
}
See this example in Storybook
Customising the in-built style wrapper
React Payment Input's default style wrapper can be customized by supplying a styles
prop.
import { css } from 'styled-components';
import { usePaymentInputs, PaymentInputsWrapper } from 'react-payment-inputs';
function PaymentForm() {
const {
getCardNumberProps,
getExpiryDateProps,
getCVCProps,
wrapperProps
} = usePaymentInputs();
return (
<PaymentInputsWrapper
{...wrapperProps}
styles={{
fieldWrapper: {
base: css`
margin-bottom: 1rem;
`
},
inputWrapper: {
base: css`
border-color: green;
`,
errored: css`
border-color: maroon;
`,
focused: css`
border-color: unset;
box-shadow: unset;
outline: 2px solid blue;
outline-offset: 2px;
`
},
input: {
base: css`
color: green;
`,
errored: css`
color: maroon;
`,
cardNumber: css`
width: 15rem;
`,
expiryDate: css`
width: 10rem;
`,
cvc: css`
width: 5rem;
`
},
errorText: {
base: css`
color: maroon;
`
}
}}
>
<input {...getCardNumberProps()} />
<input {...getExpiryDateProps()} />
<input {...getCVCProps()} />
</PaymentInputsWrapper>
);
}
See the example on Storybook
Custom card images
The card images can be customized by passing the images
attribute to getCardImageProps({ images })
. The images
object must consist of SVG paths.
import { css } from 'styled-components';
import { usePaymentInputs, PaymentInputsWrapper } from 'react-payment-inputs';
const images = {
mastercard: (
<g fill="none" fillRule="evenodd">
<rect fill="#252525" height="16" rx="2" width="24" />
<circle cx="9" cy="8" fill="#eb001b" r="5" />
<circle cx="15" cy="8" fill="#f79e1b" r="5" />
<path
d="m12 3.99963381c1.2144467.91220633 2 2.36454836 2 4.00036619s-.7855533 3.0881599-2 4.0003662c-1.2144467-.9122063-2-2.36454837-2-4.0003662s.7855533-3.08815986 2-4.00036619z"
fill="#ff5f00"
/>
</g>
)
}
function PaymentForm() {
const {
getCardNumberProps,
getExpiryDateProps,
getCVCProps,
getCardImageProps,
wrapperProps
} = usePaymentInputs();
return (
<PaymentInputsWrapper {...wrapperProps}>
<svg {...getCardImageProps({ images })} />
<input {...getCardNumberProps()} />
<input {...getExpiryDateProps()} />
<input {...getCVCProps()} />
</PaymentInputsWrapper>
);
}
License
MIT © Medipass Solutions Pty. Ltd.