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 🙏

© 2026 – Pkg Stats / Ryan Hefner

paysafe-form

v0.4.0

Published

React lifecycle wrapper for Paysafe.js.

Readme

paysafe-form

React lifecycle wrapper for Paysafe.js. The component owns the Paysafe setup lifecycle and tokenization calls for cards, Google Pay, and Apple Pay. Your app owns the setup options, tokenization options, and the JSX containers rendered for each payment method.

Install

npm install paysafe-form

React and React DOM are peer dependencies.

Paysafe script

This package expects the Paysafe browser SDK to already be loaded on window.paysafe before the form sets up. If the SDK is missing, setup throws Paysafe script has not loaded.

Basic Usage

import type { FC } from 'react';
import { useCallback, useId } from 'react';
import type { CardOptions, PaymentType, SetupOptions, TokenizeOptions } from 'paysafe-form';
import { isSetupError, isTokenizeError, PaysafeForm, useFormContext } from 'paysafe-form';

interface Props {
  environment: 'LIVE' | 'TEST';
  merchantRefNum: string;
  currencyCode: string;
  amount: number;
  customerDetails: TokenizeOptions['customerDetails'];
}

export const CheckoutForm: FC<Props> = ({ environment, merchantRefNum, currencyCode, amount, customerDetails }) => {
  const id = useId().replaceAll(':', '');

  const getSetupOptions = useCallback((setupKey: string): SetupOptions => ({
    environment,
    currencyCode,
    fields: {
      cardNumber: {
        selector: `#card-number-${id}-${setupKey}`,
        placeholder: 'Card Number',
      },
      cvv: {
        selector: `#cvv-${id}-${setupKey}`,
        placeholder: 'CVV',
      },
      expiryDate: {
        selector: `#expiry-date-${id}-${setupKey}`,
        placeholder: 'Exp. Date',
      },
    },
  }), [ environment, currencyCode, id ]);

  const getCardTokenizeOptions = useCallback((): CardOptions => ({
    amount,
    merchantRefNum,
    paymentType: 'CARD',
    transactionType: 'PAYMENT',
    customerDetails,
  }), [ amount, merchantRefNum, customerDetails ]);

  const handleSetupError = (err: unknown) => {
    if (isSetupError(err)) {
      console.log(err.displayMessage);
    } else {
      console.log(err);
    }
  }

  const handleTokenize = useCallback((token: string, paymentType: PaymentType) => {
    console.log(token, paymentType);
  }, []);


  const handleTokenizeError = useCallback((err: unknown, paymentType: PaymentType) => {
    if (isTokenizeError(err)) {
      console.error(paymentType, err.displayMessage)  
    } else {
      console.error(paymentType, err)
    }
  }, []);

  return (
    <PaysafeForm
      apiKey={import.meta.env.VITE_PAYSAFE_API_KEY}
      cardContainer={<CardContainer id={id} />}
      getSetupOptions={getSetupOptions}
      getCardTokenizeOptions={getCardTokenizeOptions}
      onTokenize={handleTokenize}
      onSetupError={handleSetupError}
      onTokenizeError={handleTokenizeError}
    />
  );
}

const CardContainer: FC<{id: string}> = ({ id }) => {
  const { initialized, setupKey } = useFormContext();

  return (
    <>
      <div id={`card-number-${id}-${setupKey}`} />
      <div id={`cvv-${id}-${setupKey}`} />
      <div id={`expiry-date-${id}-${setupKey}`} />
      <button type="submit" disabled={!initialized.card}>
        Pay by card
      </button>
    </>
  );
}

Payment Containers

Provide the JSX for each enabled payment method through its corresponding container prop:

| Payment method | Container prop | Tokenization trigger | | --- | --- | --- | | Card | cardContainer | The card form is submitted | | Google Pay | googlePayContainer | A click bubbles from the container | | Apple Pay | applePayContainer | A click bubbles from the container |

Each container is optional and rendered separately. cardContainer is rendered inside a <form> owned by PaysafeForm, so it should not render another form. It should include a submit control when card submission is required.

The Google Pay and Apple Pay wrappers remain hidden until their corresponding payment method is initialized. Paysafe renders its payment button into the element selected by getSetupOptions.

Each container subtree is keyed by the current setup key and remounts when a new Paysafe setup begins.

Form Context

Components supplied through the container props can call useFormContext().

The context contains:

  • setupKey: string - Current internal setup key. Use it to build field container ids that match getSetupOptions.
  • initialized.card: boolean - Whether card fields initialized successfully.
  • initialized.googlePay: boolean - Whether Google Pay initialized successfully.
  • initialized.applePay: boolean - Whether Apple Pay initialized successfully.
  • instance: PaysafeInstance | null - Current Paysafe instance, available after initialization.

The hook must be called by a descendant of PaysafeForm, not by the component that renders PaysafeForm itself.

Setup Options and Markup

  • getSetupOptions: (setupKey: string) => SetupOptions | Promise<SetupOptions> - Returns, or resolves to, the options passed to paysafe.fields.setup for the current setup key.

Your rendered field container ids must match the selectors returned or resolved from getSetupOptions for the same setupKey.

Whether synchronous or asynchronous, getSetupOptions should retain the same identity while its captured setup inputs remain unchanged. Changing its identity starts a new Paysafe setup.

Correct:

const getSetupOptions = (setupKey: string) => ({
  environment: 'TEST',
  currencyCode: 'USD',
  fields: {
    cardNumber: { selector: `#paysafe-${setupKey}-cardNumber` },
    cvv: { selector: `#paysafe-${setupKey}-cvv` },
    expiryDate: { selector: `#paysafe-${setupKey}-expiryDate` },
  },
});

const PaymentContainers: FC = () => {
  const { setupKey } = useFormContext();

  return (
    <>
      <div id={`paysafe-${setupKey}-cardNumber`} />
      <div id={`paysafe-${setupKey}-cvv`} />
      <div id={`paysafe-${setupKey}-expiryDate`} />
      <button type="submit">Pay Now</button>
    </>
  );
}

The static-id version can break when React remounts or reruns effects and the Paysafe SDK is still finishing older asynchronous setup work. The setup key gives each Paysafe setup fresh field ids as well as fresh keyed container subtrees.

Tokenization Options

Each payment method has its own optional tokenization-options getter:

  • getCardTokenizeOptions: () => CardOptions
  • getGooglePayTokenizeOptions: () => GooglePayOptions
  • getApplePayTokenizeOptions: () => ApplePayOptions

The appropriate getter is called when that payment method is triggered. Return the complete TokenizeOptions object you want passed to paysafeInstance.tokenize(...).

Use this function to create per-submit values such as merchantRefNum when needed:

const getCardTokenizeOptions = useCallback((): CardOptions => ({
  amount,
  merchantRefNum: generateRefNum(),
  paymentType: 'CARD',
  transactionType: 'PAYMENT',
  customerDetails,
}), [ merchantRefNum, amount, customerDetails ]);

If your backend requires one stable merchant reference per order, create it at the order level and return that value from the relevant getters.

After Apple Pay tokenization, PaysafeForm calls instance.complete('success') before onTokenize, or instance.complete('fail') before onTokenizeError.

Stable Function Props

getSetupOptions is a setup input. If its reference changes, PaysafeForm creates a new Paysafe setup.

Use React's normal dependency model: make getSetupOptions change identity when any setup value it captures changes. You can do that with React Compiler, useCallback, or by defining stable functions outside render.

The payment-specific tokenization getters are used only when their payment method is triggered. Changing their references does not create a new Paysafe setup. Their references should still follow normal React rules so tokenization uses current values.

Changing onTokenize, onTokenizeError, or onSetupError does not create a new Paysafe setup.

Props

Required

  • apiKey: string - Paysafe API key.
  • getSetupOptions: (setupKey: string) => SetupOptions - Returns the options passed to paysafe.fields.setup for the current setup key.
  • onTokenize: (token: string, paymentType: PaymentType) => void - Called after successful tokenization. paymentType identifies the payment-method handler that initiated the request.

Optional

  • cardContainer?: ReactNode - JSX rendered inside the card form.
  • googlePayContainer?: ReactNode - JSX containing the Google Pay element selected during setup.
  • applePayContainer?: ReactNode - JSX containing the Apple Pay element selected during setup.
  • getCardTokenizeOptions?: () => TokenizeOptions - Returns the options passed unchanged to tokenize for a card form submission.
  • getGooglePayTokenizeOptions?: () => TokenizeOptions - Returns the options passed unchanged to tokenize for a Google Pay click.
  • getApplePayTokenizeOptions?: () => TokenizeOptions - Returns the options passed unchanged to tokenize for an Apple Pay click.
  • onSetupError?: (err: unknown) => void - Called when setup or show() rejects, or when a payment method reports a setup error.
  • onTokenizeError?: (err: unknown, paymentType: PaymentType) => void - Called when tokenization rejects.

Callback return values are ignored. If a callback is asynchronous, PaysafeForm does not wait for it to settle.

Error Handling

Error callbacks receive unknown. Use the exported guards before accessing properties from the documented Paysafe error shapes.

<PaysafeForm
  // ...
  onTokenizeError={(err, paymentType) => {
    if (isTokenizeError(err)) {
      console.error(paymentType, err.displayMessage);
      return;
    }

    console.error(paymentType, err);
  }}
/>

Card Field Events

Set up Paysafe field event handlers inside a container using the current instance from useFormContext().

Subscribe in an Effect that depends on instance. When a new Paysafe setup begins, the container remounts and receives the new instance.

const CardContainer: FC = () => {
  const { instance, initialized } = useFormContext();
  const [ allValid, setAllValid ] = useState(false);

  useEffect(() => {
    if (!instance || !initialized.card) {
      return;
    }

    let active = true;

    instance
      .fields('CardNumber Cvv ExpiryDate')
      .on('Valid Invalid', function (_, event) {
        // guard against stale callbacks
        if (!active) {
          return;
        }

        this.classList.toggle('is-valid', event.type === 'Valid');
        this.classList.toggle('is-invalid', event.type === 'Invalid');

        // Update the container's validation state here.
      });

    return () => {
      active = false;
    };
  }, [instance, initialized.card]);

  return (
    <button type="submit" disabled={!initialized.card || !allValid}>
      Pay by card
    </button>
  );
};