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

@juspay-tech/react-native-hyperswitch-payment-methods

v1.0.4

Published

Provider-agnostic React Native card collection widgets (VGS, Skyflow, Basis Theory, Evervault + request-builder) behind one widget interface.

Readme

@juspay-tech/react-native-hyperswitch-payment-methods

Provider-agnostic React Native card collection. Render one set of card fields and let the backend decide which vault/tokenization provider is used — your app code never branches on the provider. The names, options, events and results are the ones hyperswitch-web's separate card fields and @juspay-tech/react-native-hyperswitch-vault use, so one integration reads the same across all three.

<CardNumberField />
<CardExpiryField />
<CardCVCField />
<CardholderNameField />

Supported vaults: Hyperswitch, plus VGS, Skyflow, Basis Theory, Evervault.

Installation

npm install @juspay-tech/react-native-hyperswitch-payment-methods

Then install only the provider SDK(s) you actually use (they are optional peer dependencies, so you only pay for — and natively link — what you configure):

| vaultType | Peer dependency to install | | -------------- | --------------------------------------------------- | | hyperswitch | @juspay-tech/react-native-hyperswitch-vault | | vgs | @vgs/collect-react-native | | skyflow | skyflow-react-native | | basis_theory | @basis-theory/react-native-elements (v3+) | | evervault | @evervault/react-native (+ react-native-webview)|

If a vaultType is configured without its SDK installed, the form surfaces an actionable "install X" error via onError.

Usage

Wrap the checkout in <HyperPaymentMethodSession>. It carries the payment session, the vault configuration and the appearance, so a form below it is just <CardForm> and its fields — the same code for every provider.

Give it either sdkAuthorization or vaultDetails. With sdkAuthorization alone the session reads the vault off the payment-method session for you; TypeScript requires one of the two.

import { useRef } from 'react';
import {
  Hyperswitch,
  HyperPaymentMethodSession,
  CardForm,
  CardNumberField,
  CardExpiryField,
  CardCVCField,
  CardholderNameField,
  type CardFormHandle,
} from '@juspay-tech/react-native-hyperswitch-payment-methods';

/* Created once, outside the component. */
const hyper = Hyperswitch.init({
  publishableKey: 'pk_snd_…',
  profileId: 'pro_…',      // optional; falls back to the one in sdkAuthorization
  environment: 'SANDBOX',  // 'PROD' (default) | 'SANDBOX' | 'INTEG'
});

function Checkout({ sdkAuthorization, appearance }) {
  const vaultRef = useRef<CardFormHandle>(null);

  const pay = async () => {
    const result = await vaultRef.current?.tokenize();
    if (result?.status === 'success') {
      // result.data.tokens -> the provider's tokens
    } else if (result?.error) {
      showError(result.error.message); // result.error.code names the cause
    }
  };

  return (
    <HyperPaymentMethodSession
      hyper={hyper}
      options={{ sdkAuthorization, appearance }}
    >
      <CardForm ref={vaultRef}>
        <CardNumberField />
        <CardExpiryField />
        <CardCVCField />
        <CardholderNameField />
      </CardForm>
    </HyperPaymentMethodSession>
  );
}

The session's props

| Prop | | | --- | --- | | hyper | Required. What Hyperswitch.init(...) returns, or a plain HyperswitchConfiguration — a promise or an object. The merchant's identity and endpoints live here, not in options, the same split react-hyper-js uses. The fields do not wait for it; only the lookup does. | | options | An object, or a promise of one, as the web wrapper accepts. | | options.sdkAuthorization | The payment session your backend minted, as the checkout SDK spells it. Enough on its own: the vault is looked up from it. | | options.vaultDetails | Which vault to drive — {vaultType, vaultData}, the web SDK's shape. Supply it and no lookup happens, even alongside sdkAuthorization. | | options.locale | The web SDK's locale ('fr', 'de', …), forwarded to the Hyperswitch vault fields for placeholders, labels and validation messages. Omitted, 'auto' or an unsupported tag renders English; there is no device-locale detection. | | options.appearance | The vault's theme variables and labels, plus this package's RN slots. See Appearance. | | onError | Called if the hyper promise or the options promise rejects, or the lookup fails. |

Hyperswitch.init

The instance factory, so this package stands on its own — nothing else is needed to use it. HyperswitchConfiguration is the checkout SDK's, field for field, so one config object configures either package:

| | | | --- | --- | | publishableKey | Required. | | platformPublishableKey | Carried for parity; unused by this package. | | profileId | Carried for parity with the checkout SDK; unused by this package. | | environment | 'PROD' (default), 'SANDBOX' or 'INTEG' — the checkout SDK's spelling and its default. | | customEndpoints | {commonEndpoint} or {overrideEndpoints: {customBackendEndpoint}}. Wins over environment, and is the only way to reach INTEG. |

Already using @juspay-tech/react-native-hyperswitch? Its Hyperswitch.init(...) promise is accepted directly — it resolves to a {publishableKey}. Pass this package's own instance when you need environment or customEndpoints.

At least one of sdkAuthorization and vaultDetails is required, and the type enforces it.

usePaymentMethodSession() (the web SDK's name) or usePaymentMethodsSession() reads it back from anywhere inside the session: hyper (the resolved instance — hyper.publishableKey and the rest), sdkAuthorization, vaultDetails, appearance, locale, loading and error. The web hook's {session, isPresent} is its iframe handle and has no counterpart here.

A <CardForm vaultDetails={…}> still works on its own, with no session around it, and overrides the session's vault when there is one.

Resolving the vault

Given sdkAuthorization and no vaultDetails, the session reads the vault off the payment-method session:

GET {baseUrl}/v1/payment-method-sessions/{payment_method_session_id}
Authorization: <sdkAuthorization>

The session id is read out of the sdkAuthorization itself, which is base64 of a comma-separated key=value list, so you pass it nowhere.

The vault is read from the response, vault_details first and then external_vault_details:

"vault_details":          { "vault_type": "hyperswitch", "vault_data": { "sdk_authorization": "…" } },
"external_vault_details": { "vgs": { "external_vault_id": "…", "sdk_env": "…" } }

become {vaultType: 'hyperswitch', vaultData: {sdkAuthorization: '…'}} and {vaultType: 'vgs', vaultData: {vaultId: '…', environment: '…'}}. So one integration covers a profile on Hyperswitch's own vault and one on an external vault, with nothing to change in your app when that setting moves.

Hyperswitch and VGS are the wire shapes confirmed against the API; the other providers' entries are camelized and passed through, and the provider adapter's own validation is the backstop. If your provider's fields don't line up, pass vaultDetails explicitly.

While the lookup is in flight the fields render their placeholders and tokenize() answers sdk_not_ready. If it fails, onError fires, usePaymentMethodsSession().error holds the reason, and tokenize() answers unsupported_configuration quoting it.

Default hosts: https://live.hyperswitch.io/api for PROD (the default) and https://app.hyperswitch.io/api for SANDBOX — the route is appended after the /api prefix. INTEG and self-hosted deployments have no default and are reached through customEndpoints.

vaultDetails

| vaultType | vaultData | | -------------- | --------------------------------------------------------------------------- | | hyperswitch | {sdkAuthorization, environment?} | | vgs | {vaultId, environment?, routeId?, cname?} | | skyflow | {vaultId, vaultUrl, table, bearerToken?, columns?, options?} | | basis_theory | {apiKey, baseUrl?} | | evervault | {teamId, appId} |

Tokenizing from outside the tree

If a Pay button can't reach the form ref, give the form an id and tokenize by id:

<CardForm id="checkout">...</CardForm>;

import { HyperswitchPaymentMethods } from '@juspay-tech/react-native-hyperswitch-payment-methods';
await HyperswitchPaymentMethods.tokenize('checkout');

Descendant components can also use the useCardForm() hook.

Saved card — CVC recollect

Mount only the CVC field and give it the stored card's token:

<HyperPaymentMethodSession hyper={hyper} options={{ sdkAuthorization }}>
  <CardForm ref={vaultRef}>
    <CardCVCField
    options={{
      savedCard: {
        paymentMethodToken: entry.payment_method_token,
        paymentMethodData: { card: { cardNetwork: entry.payment_method_data.card.card_network } },
      },
    }}
  />
  </CardForm>
</HyperPaymentMethodSession>;

const result = await vaultRef.current?.tokenize();

tokenize() sends just the CVC through the configured vault and hands the stored card back beside the vault's tokens, so your backend knows what to confirm with:

result.data.tokens;    // the provider's tokens for the CVC
result.data.savedCard; // the entry back, network in the web's spelling

Rules, all answered without a request:

  • A CVC field with savedCard must be the only field in the form. Another field beside it is unsupported_configuration.
  • savedCard on any field other than the CVC is unsupported_configuration.
  • A missing or blank paymentMethodToken is validation_error.

paymentMethodData.card.cardNetwork is passed down to the provider's field as a card-network hint and echoed back on the result in the web's spelling. Every member is optional, so a list-payment-methods entry passes through without reshaping — the same shape @juspay-tech/react-native-hyperswitch-vault takes. It is a hint, not a length rule this package enforces: the digits never leave the provider's secure input, so CVC length validation is whatever the provider does.

The result shape

type TokenizeResult =
  | {
      status: 'success';
      vaultType?: VaultType;
      data?: { tokens?: Record<string, unknown>; raw?: unknown; savedCard?: SavedCard };
      card?: TokenizedCard;
    }
  | { status: 'validation_error' | 'error'; vaultType?: VaultType; error: { code; message; type } };

/* The card the provider reported, in the members `onChange` publishes — so `result.card.last4`
   and `event.payload.last4` read alike. Never a PAN, never a CVC. */
type TokenizedCard = {
  bin?: string; last4?: string; brand?: string; expiryMonth?: string; expiryYear?: string;
};

tokenize() never throws. if (result.error) reads the same way it does with the web SDK; status lets TypeScript narrow.

card carries whatever the provider told the form about the card. A provider whose secure input keeps every digit reports nothing, and then there is no card key at all; Evervault reports the BIN, last four, brand and expiry. An absent member is an absent key, never undefined.

| error.code | type | Meaning | | -------------------------- | ------------------ | -------------------------------------------------------------------- | | validation_error | validation_error | a field is empty or malformed, or savedCard has no paymentMethodToken | | incomplete_field_set | validation_error | no <CardForm id> is mounted for the id given to tokenize(id) | | unsupported_configuration| api_error | no vault configuration in scope or the lookup failed, or savedCard mounted beside other fields | | sdk_not_ready | api_error | the provider's SDK has not finished initialising, or the vault lookup is still in flight | | session_expired | api_error | Hyperswitch vault: the session's expires_at has passed | | session_consumed | api_error | Hyperswitch vault: this session already tokenized a card | | invalid_session | api_error | Hyperswitch vault: no session, or an unreadable one | | unknown_outcome | api_error | the request threw, timed out, or was aborted — reconcile before retrying | | tokenization_failed | api_error | the provider refused, answered unreadably, or failed to initialise |

Two tokenize() calls at once share one request.

Events

Every field takes the web's four events. The change carries no card value:

<CardNumberField
  onReady={(e) => {/* e.elementType === 'cardNumber' */}}
  onFocus={(e) => setActive(e.elementType)}
  onBlur={() => setActive(null)}
  onChange={(s) => setValid(s.valid)}
  // s: {elementType, empty, complete, valid, brand?, error?, touched}
/>

brand is spelt as the web spells it (Visa, Mastercard, AmericanExpress, …). Focus and blur are reported where the provider reports them (VGS, Skyflow); change is wired for VGS, Skyflow and Basis Theory (Evervault reports validity at the card level).

The form emits the web's cardDetailsChange envelope on every change, always on (the web SDK requires a field-level subscriptionEvents opt-in; this package does not):

<CardForm
  onReady={(e) => {/* e.elementType === 'cardForm' */}}
  onChange={(e) => {
    // e.eventName === 'cardDetailsChange'
    setCanPay(e.complete && e.valid);
    e.payload; // {bin, extendedBin, last4, brand, expiryMonth, expiryYear, formattedExpiry, is*Complete, is*Valid}
    e.fields;  // the latest change per mounted field
  }}
>

A keystroke can reach onChange more than once (each field reports, then the provider reports the card details); read the latest event rather than counting calls.

A provider's secure input keeps the digits to itself, so bin, last4 and the expiry parts are null unless the provider reports them (Evervault does); the flags are derived from the fields.

Handles

  • Form ref (CardFormHandle): tokenize(providerData?), status (initializing | ready | tokenizing | error).
  • Field ref (FieldHandle): focus(), blur(), clear() — no-ops where the provider's secure input does not support them.
const field = useRef<FieldHandle>(null);
<CardNumberField ref={field} />;
field.current?.focus();

useCardForm() is the ref-free spelling, for a Pay button that lives inside the form:

function PayButton() {
  const { tokenize, status } = useCardForm();
  return <Button title="Pay" disabled={status !== 'ready'} onPress={() => tokenize()} />;
}

Field options

Every field takes the web SDK's options object, same names and nesting:

<CardNumberField options={{ placeholder: '1234 1234 1234 1234', cardBrandIcon: 'hideGeneric' }} />
<CardExpiryField options={{ placeholder: 'MM / YY' }} />
<CardCVCField options={{ placeholder: '123', cvcIcon: 'hidden' }} />

| options.… | Fields | Values | Default on RN | | --- | --- | --- | --- | | placeholder | all | any string ('' shows none) | the vault's locale text: Card number, MM / YY, CVC (web shows 1234 1234 1234 1234 / 123) | | cvcIcon | CVC | 'default' | 'hidden' | 'default' | | cardBrandIcon | number | 'standard' | 'hidden' | 'animated' | 'hideGeneric' | 'standard'; animated cycles brand marks here, while web currently renders it like standard | | label | all | any string | the vault's locale text | | labelBehavior | all | 'above' | 'floating' | 'never' | the session appearance's labels ('floating') | | errorDisplay | all | 'none' | 'colorOnly' | 'inline' | 'colorOnly' — composed fields print no error text, so render it yourself from onChange's error | | unstyled | all | true | false | false — true drops the vault's chrome (border, label, icons) so you can draw your own | | savedCard | CVC | see Saved card | none |

label, labelBehavior, errorDisplay and unstyled are also accepted as top-level props, as are placeholder (all fields) and cvcIcon (CVC) from 1.0.x; when both forms are given the top-level prop wins (an empty top-level '' wins too). Supported by the Hyperswitch vault; other providers take placeholder and ignore the icons. At runtime an icon value outside the list warns in development and falls back to the default; '', null and undefined mean "not set"; a non-string placeholder is ignored, as on web.

Appearance

options.appearance on the session takes colors, shapes and font under the same names as @juspay-tech/react-native-hyperswitch, with lengths as React Native points, plus labels as 'above' | 'floating' | 'never' (the RN default is floating; web's separate fields never render a label). colors is split into light and dark: the active one follows the device and dark falls back to light. Values of the wrong runtime type are dropped and an unknown labels value warns in development. Members with no field to paint — overlay, loaderBackground, icon, selectedComponent*, theme presets, primaryButton, logo, shapes.shadow — are accepted and ignored, as are the web's rules (CSS selectors) and innerLayout.

<HyperPaymentMethodSession
  hyper={hyper}
  options={{
    sdkAuthorization,
    appearance: {
      colors: {
        light: { primary: '#0570DE', componentText: '#1A1A1A', componentBorder: '#E0E0E0' },
        dark: { primary: '#8AB4F8', componentText: '#FFFFFF', componentBackground: '#121212' },
      },
      shapes: { borderRadius: 8, inputHeight: 48 },
      labels: 'never',
    },
  }}
>

On top of that, a field has two RN-only style slots, which patch the theme rather than replace it:

  • styles.container — the field's box (border, background, radius, height, padding).
  • styles.input — the secure input's text (color, fontSize, fontFamily).

appearance.container, appearance.input and appearance.fields[elementType] set defaults for those slots across every field below. Later wins, so the order is: the session's appearance, then a <CardForm appearance>, then the per-element-type fields entry, then the field's own styles.

styles.input is forwarded to the provider's underlying secure input where supported (VGS textStyle, Basis Theory / Evervault field style); providers that don't support text styling ignore it.

Custom providers

Register your own adapter (also handy in tests):

import { registerAdapter } from '@juspay-tech/react-native-hyperswitch-payment-methods';
const off = registerAdapter(myAdapter); // off() to unregister

An adapter provides vaultType, validateVaultData, a Host, a Field (which receives elementType, styles, placeholder, savedCard, cvcIcon, cardBrandIcon, onChange, onFocus, onBlur) and tokenize(collector, providerData?).

Notes

  • VGS uses @vgs/collect-react-native, currently in beta — pin the version.
  • Basis Theory targets @basis-theory/react-native-elements v3+ (the older SDK is deprecated).
  • Evervault additionally requires react-native-webview.

Contributing

License

MIT


Made with create-react-native-library