npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-payment-inputs

v1.2.0

Published

A zero-dependency React Hook & Container to help with payment card input fields.

Downloads

98,350

Readme

React Payment Inputs

A React Hook & Container to help with payment card input fields.

Demos

Requirements

Ensure you are running on a hooks-compatible version of React (v16.8 & above).

Installation

npm install react-payment-inputs --save

or install with Yarn if you prefer:

yarn add react-payment-inputs

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

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>
  );
}

More examples

data = usePaymentInputs(options)

returns an object (data)

options

Object({ cardNumberValidator, cvcValidator, errorMessages, expiryValidator, onBlur, onChange, onError, onTouch })

options.cardNumberValidator

function({cardNumber, cardType, errorMessages})

Set custom card number validator function

Example
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
  });
}

options.cvcValidator

function({cvc, cardType, errorMessages})

Set custom cvc validator function

options.errorMessages

Object

Set custom error messages for the inputs.

Example
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
  });
}

options.expiryDateValidator

function({expiryDate, errorMessages})

Set custom expiry date validator function

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); // "Card number is invalid"

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);
/*
{
  cardNumber: undefined,
  expiryDate: 'Enter an expiry date',
  cvc: 'Enter a CVC'
}
*/

meta.touchedInputs

Object

Returns the touch state of each rendered input.

Example snippet
const { meta } = usePaymentInputs();

console.log(meta.touchedInputs);
/*
{
  cardNumber: true,
  expiryDate: true,
  cvc: false
}
*/

meta.focused

string

Returns the current focused input.

const { meta } = usePaymentInputs();

console.log(meta.focused); // "cardNumber"

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.