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

@whop/elements-react-native

v0.4.0

Published

Whop Elements — native React Native components for collecting payments.

Downloads

680

Readme

@whop/elements-react-native

Native React Native components for collecting payments with Whop.

The elements are real React Native views, and card fields are PCI-isolated by Basis Theory's React Native SDK. The pieces only the platform can do, the Apple Pay and Google Pay sheets, their branded buttons, and the system browser a 3DS step opens in, are Swift and Kotlin behind React Native Codegen.

import {
  WhopElements, Payments, PaymentElement, AddressElement, EmailElement, BrandingElement,
} from '@whop/elements-react-native';

<WhopElements environment="production">
  <Payments ref={payments} accountId="biz_…" plan="plan_…">
    <EmailElement />
    <AddressElement />
    <PaymentElement onChange={(e) => setReady(e.complete)} />
    <BrandingElement />
  </Payments>
</WhopElements>;

const { confirmationToken } = await payments.current.createConfirmationToken();

Confirm the token server-side, then hand any pending step to payments.current.handleNextAction({ clientSecret }).

Requirements

| | | | --- | --- | | React Native | >= 0.87 on Swift Package Manager, >= 0.86 on CocoaPods. New Architecture on | | iOS | 15.1+ | | Android | minSdk 24, compileSdk 37 | | Peers | react and react-native only — no native peer dependencies |

The package ships two iOS manifests and the toolchain picks one. React Native's SPM autolinker prefers ios/Package.swift and only falls back to a podspec, so an app on Swift Package Manager is unaffected by the podspec's existence. The floor is higher there because Package.swift links ReactNativeHeaders and ReactNativeDependenciesHeaders, two header-only XCFrameworks that first shipped in React Native 0.87.

WhopElementsReactNative.podspec exists because every CocoaPods-based toolchain discovers native modules by scanning a package for a podspec, and skips a package without one silently rather than failing. That includes Expo, whose iOS autolinking is CocoaPods-based at every released SDK. Without the podspec an Expo app installs this package, builds cleanly, and gets no native module at all.

Elements

| Component | What it collects | | --- | --- | | <PaymentElement /> | The offered payment methods and the selected one's required fields | | <AddressElement /> | A postal address, in the selected country's own format | | <CardElement /> | Card number, expiry, and security code, prearranged | | <CardFields> + <CardNumberElement /> <CardExpiryElement /> <CardCvcElement /> | The same three fields, placed individually | | <EmailElement /> | The buyer's email | | <TaxIdElement /> | A business tax registration | | <BrandingElement /> | Whop's merchant-of-record notice | | <ExpressCheckoutElement /> | One Apple Pay or Google Pay press that pays a whole checkout, under <Checkout> |

<BrandingElement /> is required beside a payment surface: Whop is the merchant of record on these sales, so createConfirmationToken refuses with BRANDING_NOT_MOUNTED without it.

<AddressElement /> autocompletes by default from the platform geocoder, MKLocalSearchCompleter on iOS and android.location.Geocoder on Android, so it needs no API key, no account, no location permission and no extra dependency. Android resolves a complete address rather than predicting from a fragment, so expect fewer and coarser suggestions there. A geocoder with no matches shows nothing and the buyer types the address by hand, which is where they started: autocomplete never blocks a checkout. Pass autocomplete={false} to turn it off.

<Payments>

| Prop | | | --- | --- | | accountId | The Whop company the sale belongs to | | plan | The plan being sold, which resolves the currency, amount and offered methods. Pass currency and amount instead for an ad-hoc charge | | mode | payment (default) charges; setup saves a payment method without charging: pass currency, never plan or amount; methods that cannot be saved are not offered and amount limits do not apply; confirm as usual and create the setup intent from your server | | returnUrl | Where the issuer returns after a redirect or 3DS step. Must be https and hosted by you, because the API refuses anything else, so a custom app scheme will not work. You do not register a deep link: handleNextAction polls the payment to rest and closes the browser itself. A setup's sint_… secret is handled the same way against the setup intent | | googlePayMerchantName | The name shown in the Google Pay sheet, defaulting to Whop. Neither wallet needs anything else from you: Apple Pay uses the merchant registered on the Whop account | | order | The method tile order, with Stripe's paymentMethodOrder semantics |

<Checkout> and <ExpressCheckoutElement />

<WhopElements environment="production">
  <Checkout plan="plan_…" onComplete={({ sessionId, receiptId }) => navigation.navigate('Thanks')}>
    <ExpressCheckoutElement />
  </Checkout>
</WhopElements>

<Checkout> opens a Whop checkout session when it mounts and keeps the session's credential to itself. <ExpressCheckoutElement /> renders the device's wallet button: Apple Pay on iOS, Google Pay on Android. One press is the whole purchase. The sheet collects what the session still needs (the email, a phone number, a shipping address), the element confirms the session, runs any 3DS step in the system browser, and shows a native code step for sellers who verify phone numbers. There is no confirmation token to send to your server.

| Prop | | | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | plan, items, checkoutConfiguration, quantity | What is being bought, with the web element's rules: items is the whole cart, plan beside checkoutConfiguration only sets quantity | | promoCode, affiliateCode, attribution, metadata | Recorded on the session. A promo code that cannot apply opens the checkout at full price | | returnUrl | Where an off-site payment step returns. https, hosted by you, as on <Payments> | | onComplete | ({ sessionId, receiptId }), once, when the purchase stands. Your app is never navigated, so this is where you move the buyer on. Fulfill from webhooks | | onError, onLoadingChange, fallback, style | As on <Payments> |

Every option is create-time. Changing one after mount reports CHECKOUT_OPTIONS_FROZEN and keeps the original order: give <Checkout> a new key to sell something else.

The element renders nothing where the device has no wallet, the checkout's payment method configuration disables it, or the plan cannot be bought right now. A checkout a sheet cannot finish (a setup, a waitlist join, a transfer, a question only a form can ask) shows an explanatory face and reports EXPRESS_CHECKOUT_UNAVAILABLE. On iOS the sheet reprices as the buyer changes card and offers a promo code field when the seller allows codes. Google's sheet has neither on Android. wallets filters what may render, and there is no layout prop because a device only ever offers one wallet.

Appearance

One object, applied live — mounted elements restyle in place.

<WhopElements
  appearance={{
    theme: { appearance: 'dark', accentColor: 'blue' },
    tokens: { radius: 12 },
    parts: { 'whop-CardNumberField': { borderWidth: 2 } },
  }}
>

theme is identical to the web elements'. The two deltas are deliberate, because React Native has no CSS:

  • tokens replaces variables — a closed, typed map of numbers, not "8px" strings.
  • parts replaces classes — the same whop-* part names, with React Native style objects instead of CSS declarations. Flat per-part overrides: no cascade, no pseudo-classes.

Passing variables or classes throws by name rather than being silently ignored.

An unset theme.appearance follows the device's colour scheme.

Example app

cd example
pnpm install
pnpm run setup     # generates ios/ and android/, converts iOS to SwiftPM
pnpm run ios       # or: pnpm run android

Set your accountId and plan in example/src/config.ts first. It runs against production by default — switch environment to sandbox there to point at the sandbox API.

ios/ and android/ are generated by pnpm run setup from the official React Native app template rather than committed: they are several hundred files that belong to a React Native version, and regenerating is how the example follows a version bump instead of drifting from it. The script also converts the project to Swift Package Manager without ever invoking pod — it strips CocoaPods out of the .pbxproj directly, because react-native spm add --deintegrate shells out to it.

One deliberate leftover: ios/Podfile stays as a pod-free stub. The React Native CLI locates the iOS project by finding a Podfile, and react-native spm reads that location, so deleting it makes project.ios resolve to null and the SPM setup fail. The example has four screens: a realistic Checkout, Express (a real one-press purchase), a Gallery of each element with live prop controls, and Theming applied to a live form.

The example is its own pnpm install root. React Native's settings.gradle does includeBuild("../node_modules/@react-native/gradle-plugin"), which pnpm's isolated linker does not expose, so example/.npmrc sets node-linker=hoisted — scoped there so the rest of the monorepo stays isolated.

Native layer

Three units, all generated from src/specs/ by React Native Codegen:

| Unit | Kind | iOS | Android | | --- | --- | --- | --- | | WhopAuthSession | Turbo Module | ASWebAuthenticationSession | Chrome Custom Tabs | | WhopPlatformPay | Turbo Module | PKPaymentAuthorizationController | PaymentsClient | | WhopPayButton | Fabric Component | PKPaymentButton | PayButton |

A redirect or 3DS step opens in the system browser, out of process — not a WebView the app can read or script. handleNextAction then polls the payment to rest, because the API accepts only https or loopback return URLs and a custom-scheme deep link back into the app is refused.

React Native cannot express a Fabric component or a Turbo Module in pure Swift: both need an Objective-C++ class the C++ renderer can see. Nobody writes it here. scripts/codegen-shims.mjs emits it from the same specs Codegen reads, into the gitignored ios/generated/, during prepare. Every line a human writes on iOS is Swift and on Android is Kotlin.

The SDK has no native peer dependencies, deliberately: under Swift Package Manager, one peer that ships no Package.swift makes the whole app unbuildable, and most of the React Native ecosystem does not ship one yet. That is why the branding wordmark is drawn with plain views rather than react-native-svg.

Not in this release

Absent entirely rather than half-present, so nothing renders that cannot work: saved payment methods, the email code sign-in, installment tiers, ledger-balance payment, and the post-confirm instruction panes.

Development

pnpm install
pnpm build          # emits ios/generated, then module + typescript targets
pnpm test
pnpm check-types
pnpm theme          # regenerate src/theme/scales.generated.ts from frosted-ui's stylesheet
pnpm mirror         # which files here mirror the web elements changed on this branch
pnpm check-swift    # typecheck the Swift against the iOS SDK, no app build

Nothing in ios/Sources imports React, so the whole Swift implementation typechecks standalone — a wrong PassKit or AuthenticationServices symbol surfaces in seconds rather than one at a time inside an Xcode build.

pnpm mirror answers the question a web author cannot otherwise ask, which files here implement the same behaviour as the web elements they just changed. scripts/mirror.mjs derives the ported files from their own headers and keeps an explicit table for the element components; pnpm mirror:validate fails if either side of a pair has moved. Add a row when you add an element.

src/core/ is ported verbatim from frontend/apps/web/src/elements/. Each file names its source in a header, and the address engine's own test suite is ported with it, so the port is proved against the same assertions the web elements pass.

CI and npm releases

.github/workflows/frontend-elements-react-native.yml runs the unit tests, typecheck, lint, mirror validation, native shim generation, and package build. It packs the SDK and checks that the JavaScript, declarations, native sources, generated shims, and autolinking config ship. These checks do not build or run an iOS or Android app; native changes still need device verification.

To release, bump this package's version in a PR, add its entry to CHANGELOG.md, and merge it into main. After the checks pass, the workflow publishes the verified tarball. Stable versions go to latest; prereleases use alpha, beta, canary, or rc (for example, 0.2.0-beta.1 goes to beta). Already published versions skip publishing, and older unpublished versions cannot move a channel backwards. The version is not bumped automatically. Merging CI setup with the existing 0.1.0 version therefore does not publish another release.

Publishing uses npm OIDC, following the web Elements workflow. Configure the trusted publisher in the npm settings for @whop/elements-react-native:

  • Provider: GitHub Actions
  • Organization: whopio
  • Repository: whop-monorepo
  • Workflow filename: frontend-elements-react-native.yml
  • Environment: leave blank

No npm token is needed. The publish step runs from the downloaded artifact directory outside frontend so the workspace's install-time npm configuration cannot override OIDC authentication. Provenance is not requested because the source repository is private. If publishing fails, rerun the failed job, or dispatch the workflow on main to retry after checks. Dispatching it on another branch only runs checks.