A React Hook & Container to help with payment card input fields.
- React Payment Inputs
- Demos
- Requirements
- Installation
- Usage
data = usePaymentInputs(options)
<PaymentInputsWrapper>
props- Using a third-party UI library
- Form library examples
- Customising the in-built style wrapper
- Custom card images
- License
Ensure you are running on a hooks-compatible version of React (v16.8 & above).
npm install react-payment-inputs --save
or install with Yarn if you prefer:
yarn add react-payment-inputs
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".
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.
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.
Note:
<PaymentInputsWrapper>
requires styled-components to be installed as a dependency.
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>
);
}
returns an object (
data
)
Object({ cardNumberValidator, cvcValidator, errorMessages, expiryValidator, onBlur, onChange, onError, onTouch })
function({cardNumber, cardType, errorMessages})
Set custom card number validator function
const cardNumberValidator = ({ cardNumber, cardType, errorMessages }) => {
if (cardType.displayName === 'Visa' || cardType.displayName === 'Mastercard') {
return;
}
return 'Card must be Visa or Mastercard';
}
export default function MyComponent() {
const { ... } = usePaymentInputs({
cardNumberValidator
});
}
function({cvc, cardType, errorMessages})
Set custom cvc validator function
Object
Set custom error messages for the inputs.
const ERROR_MESSAGES = {
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'
}
export default function MyComponent() {
const { ... } = usePaymentInputs({
errorMessages: ERROR_MESSAGES
});
}
function({expiryDate, errorMessages})
Set custom expiry date validator function
function(event)
Function to handle the blur event on the inputs. It is invoked when any of the inputs blur.
function(event)
Function to handle the change event on the inputs. It is invoked when any of the inputs change.
function(error, erroredInputs)
Function to invoke when any of the inputs error.
function(touchedInput, touchedInputs)
Function to invoke when any of the inputs are touched.
function(overrideProps)
| returnsObject<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.
<input {...getCardNumberProps({ onBlur: handleBlur, onChange: handleChange })} />
function(overrideProps)
| returnsObject<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.
<input {...getExpiryDateProps({ onBlur: handleBlur, onChange: handleChange })} />
function(overrideProps)
| returnsObject<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.
<input {...getCVCProps({ onBlur: handleBlur, onChange: handleChange })} />
function(overrideProps)
| returnsObject<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.
<input {...getZIPProps({ onBlur: handleBlur, onChange: handleChange })} />
function({ images })
| returnsObject<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.
import images from 'react-payment-inputs/images';
<svg {...getCardImageProps({ images })} />
Object
Returns information about the current card type, including: name, lengths and formats.
const { meta } = usePaymentInputs();
<span>Current card: {meta.cardType.displayName}</span>
string
Returns the current global error between all rendered inputs.
const { meta } = usePaymentInputs();
console.log(meta.error); // "Card number is invalid"
boolean
Returns the current global touched state between all rendered inputs.
Object
Returns the error message of each rendered input.
const { meta } = usePaymentInputs();
console.log(meta.erroredInputs);
/*
{
cardNumber: undefined,
expiryDate: 'Enter an expiry date',
cvc: 'Enter a CVC'
}
*/
Object
Returns the touch state of each rendered input.
const { meta } = usePaymentInputs();
console.log(meta.touchedInputs);
/*
{
cardNumber: true,
expiryDate: true,
cvc: false
}
*/
string
Returns the current focused input.
const { meta } = usePaymentInputs();
console.log(meta.focused); // "cardNumber"
Object
Returns the props to apply to <PaymentInputsWrapper>
.
Object
Custom styling to pass through to the wrapper. Either a styled-component's css
or an Object can be passed.
{
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
}
}
Object
Custom props to pass to the error text component.
Object
Custom props to pass to the input wrapper component.
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>
.
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>
);
}
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>
);
}
React Payment Inputs has support for any type of React form library. Below are examples using Formik & React Final Form.
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>
);
}
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>
);
}
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>
);
}
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>
);
}