react-payment-inputs

repository·master·Indexed 19 days ago

https://github.com/medipass/react-payment-inputs

A zero-dependency React Hook and Container for managing payment card input fields. It provides logic for formatting, validation, and card type detection via the usePaymentInputs hook and PaymentInputsContainer. The library includes a pre-styled PaymentInputsWrapper (requiring styled-components) and supports integration with third-party UI libraries, Formik, and React Final Form.

Tokens
7.6K
Snippets
16
Records
21
Agent score
63%

What's inside react-payment-inputs

  1. Integrate React Payment Inputs with third-party UI libraries

    master

    You can integrate react-payment-inputs into any UI library (like Bootstrap or Fannypack) by using the usePaymentInputs hook to extract props and metadata.

    To ensure proper integration:

    1. Spread the get...Props() onto your library's input component.
    2. Pass the ref from the props to the library's inputRef prop if available.
    3. Use meta.erroredInputs and meta.touchedInputs to drive the library's error/invalid states.
    // Example: Integrating with a generic InputField
    import { usePaymentInputs } from 'react-payment-inputs';
    
    export default function MyCustomForm() {
      const { meta, getCardNumberProps, getExpiryDateProps, getCVCProps } = usePaymentInputs();
      const { erroredInputs, touchedInputs } = meta;
    
      return (
        <InputField
          {...getCardNumberProps()}
          inputRef={getCardNumberProps().ref}
          state={erroredInputs.cardNumber && touchedInputs.cardNumber ? 'danger' : undefined}
          validationText={touchedInputs.cardNumber && erroredInputs.cardNumber}
        />
        // ... repeat for other fields
      );
    }
  2. Configure `<PaymentInputsWrapper>` styles

    master

    The <PaymentInputsWrapper> component accepts a styles prop to customize the appearance of the wrapper and its internal elements. You can pass either a standard JavaScript Object or a styled-component's css object.

    Styles 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" }
    }
  3. Customize the PaymentInputsWrapper styles

    master

    The PaymentInputsWrapper component accepts a styles prop to allow deep customization of the UI. You can provide CSS (e.g., via styled-components) for various states and elements, including:

    • fieldWrapper: The container for the entire set of inputs.
    • inputWrapper: The container for individual inputs. Supports base, errored, and focused states.
    • input: The actual input elements. Supports base, errored, and specific types like cardNumber, expiryDate, and cvc.
    • errorText: The container for error messages.
    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>
      );
    }
  4. Integrate with Formik

    master

    You can use react-payment-inputs with Formik by mapping the usePaymentInputs props to Formik's Field components. Use meta.erroredInputs to drive Formik's validation logic. When passing props to the input helpers (like getCardNumberProps), ensure you pass the onBlur and onChange handlers from Formik's field object to maintain synchronization.

    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>
      );
    }
  5. Customize card images

    master

    You can override the default card brand images by passing an images object to the getCardImageProps function. The images object should map brand names (e.g., mastercard) to SVG elements (e.g., <g>...</g>).

    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-.9122063 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>
      );
    }
  6. Integrate with React Final Form

    master

    To use react-payment-inputs with React Final Form, map the usePaymentInputs props to the Field component. Use meta.erroredInputs within the validate function of the Form component. Pass the onBlur and onChange handlers from the field's input object into the input prop helpers.

    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>
      );
    }
  7. Use the built-in PaymentInputsWrapper

    master

    For a pre-styled layout that combines card number, expiry, and CVC fields, use <PaymentInputsWrapper>.

    Note: This component requires styled-components to be installed as a dependency in your project.

    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>
      );
    }
  8. Access payment metadata with `meta`

    master

    The meta object returned by usePaymentInputs provides the current state of the payment form, including validation errors, touch state, and card type information.

    meta Properties:

    • cardType: Object. Contains name, lengths, and formats for the detected card type.
    • error: string. The current global error message across all inputs.
    • isTouched: boolean. Global touched state.
    • erroredInputs: Object. A map of error messages per input (e.g., { cardNumber: '...', expiryDate: undefined }).
    • touchedInputs: Object. A map of touch states per input (e.g., { cardNumber: true, cvc: false }).
    • focused: string. The ID/name of the currently focused input (e.g., 'cardNumber').
    const { meta } = usePaymentInputs();
    
    console.log(meta.cardType.displayName); // e.g., "Visa"
    console.log(meta.error);               // e.g., "Card number is invalid"
    console.log(meta.erroredInputs.cvc);    // e.g., "Enter a CVC"
    console.log(meta.focused);             // e.g., "cardNumber"
  9. Use the usePaymentInputs hook

    master

    Import usePaymentInputs to access prop getters and metadata for payment fields.

    Important: When using event handlers like onChange or onBlur, you must pass them inside the prop getter function (e.g., getCardNumberProps({ onChange: ... })) to prevent the library's internal logic from being overridden.

    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>
      );
    }
  10. Get props for payment inputs using `usePaymentInputs`

    master

    The usePaymentInputs hook returns several get...Props functions. These functions return the props required to wire up your HTML <input> elements to the library's logic.

    IMPORTANT: To avoid overriding the library's internal event handlers, you must pass your custom event handlers (like onChange or onBlur) as an argument to these functions via an overrideProps object.

    Prop Functions:

    • getCardNumberProps(overrideProps): Props for the card number input.
    • getExpiryDateProps(overrideProps): Props for the expiry date input.
    • getCVCProps(overrideProps): Props for the CVC input.
    • getZIPProps(overrideProps): Props for the ZIP input.
    • getCardImageProps({ images }): Props for the card image SVG. Currently supports SVG elements. You can pass custom images using the images attribute.
    // Correct way to use custom handlers
    <input {...getCardNumberProps({ onBlur: handleBlur, onChange: handleChange })} />
    
    // Using default images for the card SVG
    import images from 'react-payment-inputs/images';
    <svg {...getCardImageProps({ images })} />
  11. Use the PaymentInputsContainer component

    master

    If you prefer the render props pattern, use PaymentInputsContainer. The component's props are identical to the usePaymentInputs hook options, and the render props provided to the child function are identical to the hook's return 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>
      );
    }