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

expo-paystack

v0.1.0

Published

The easiest way to integrate Paystack payments into your Expo app. Supports card, bank transfer, USSD, mobile money, and more. Built for Expo and the New Architecture.

Readme

  ███████╗██╗  ██╗██████╗  ██████╗
  ██╔════╝╚██╗██╔╝██╔══██╗██╔═══██╗   ██████╗  █████╗ ██╗   ██╗
  █████╗   ╚███╔╝ ██████╔╝██║   ██║   ██╔══██╗██╔══██╗╚██╗ ██╔╝
  ██╔══╝   ██╔██╗ ██╔═══╝ ██║   ██║   ██████╔╝███████║ ╚████╔╝
  ███████╗██╔╝ ██╗██║     ╚██████╔╝   ██╔═══╝ ██╔══██║  ╚██╔╝
  ╚══════╝╚═╝  ╚═╝╚═╝      ╚═════╝    ██║     ██║  ██║   ██║
                                      ╚═╝     ╚═╝  ╚═╝   ╚═╝
              e x p o - p a y s t a c k

The easiest way to accept Paystack payments in your Expo app.

npm version license platform - ios platform - android types


expo-paystack lets you accept card, bank transfer, USSD, QR, mobile money, and more through Paystack — Africa's leading payment infrastructure — in your Expo app, with a clean hook-based API, first-class TypeScript, and verified support for the New Architecture.

const { initializePayment } = usePaystack();

await initializePayment({
  email: '[email protected]',
  amount: 500000, // ₦5,000.00 in kobo
  onSuccess: (tx) => console.log('Paid!', tx.reference),
  onCancel: () => console.log('Cancelled'),
});

Why expo-paystack?

There are already Paystack packages for React Native — so why another one?

Because every existing option is a React Native package that happens to support Expo. expo-paystack is an Expo package — built, tested, and optimized for the Expo SDK and the New Architecture from the ground up.

The existing packages are React Native packages that support Expo. expo-paystack is an Expo package — built, tested, and optimized for the Expo SDK and the New Architecture.

| | react-native-paystack-webview (market leader) | Native SDK wrappers (react-native-paystack) | expo-paystack | |---|:---:|:---:|:---:| | Works in Expo managed workflow | ⚠️ With caveats | ❌ | ✅ | | New Architecture (SDK 54+, RN 0.81+) | ❌ Open iOS white‑screen bug¹ | ❌ | ✅ | | Named & built for Expo | ❌ | ❌ | ✅ | | Uses the Expo toolchain (expo-crypto) | ❌ | ❌ | ✅ | | Strict TypeScript, zero any | ❌ | ❌ | ✅ | | Currency helpers | ❌ | ❌ | ✅ | | Clean initializePayment() API | ❌ popup.checkout() | ❌ | ✅ | | access_code flow as a first‑class API | ❌ Buried in docs | ❌ | ✅ |

¹ The market leader has an open issue where iOS TestFlight builds on Expo SDK 53+ render a blank screen once the New Architecture became the default. Because the New Architecture is permanent from RN 0.82 onward, this matters for every future Expo app.

This comparison is meant to be factual, not disparaging — those projects paved the way and remain useful. expo-paystack simply takes a different, Expo-first approach.


Installation

npx expo install expo-paystack react-native-webview

react-native-webview is a peer dependency. It is autolinked, so it does not need a config-plugin entry — just make sure the New Architecture is enabled in your app.json (or app.config.js):

{
  "expo": {
    "newArchEnabled": true
  }
}

Note: Do not add "react-native-webview" to your plugins array. It does not export an Expo config plugin, and listing it there will make expo run:ios / run:android fail with a PluginError.

Then create a development build (the WebView requires native code, so it does not run in Expo Go on its own — use npx expo run:ios / run:android or an EAS build):

npx expo prebuild
npx expo run:ios   # or: npx expo run:android

expo-crypto is installed automatically as a dependency and is used to generate cryptographically random transaction references.


Quick Start

import { PaystackProvider, usePaystack } from 'expo-paystack';
import { Button } from 'react-native';

function PayButton() {
  const { initializePayment, isLoading } = usePaystack();

  return (
    <Button
      title="Pay ₦5,000"
      disabled={isLoading}
      onPress={() =>
        initializePayment({
          email: '[email protected]',
          amount: 500000, // in kobo
          currency: 'NGN',
          onSuccess: (tx) => console.log('Success:', tx.reference),
          onCancel: () => console.log('Cancelled'),
        })
      }
    />
  );
}

export default function App() {
  return (
    <PaystackProvider publicKey="pk_test_xxxxxxxxxxxxxxxx">
      <PayButton />
    </PaystackProvider>
  );
}

Usage Patterns

1. Provider (required for the hook)

Wrap your app once at the root:

<PaystackProvider publicKey="pk_test_xxxxxxxxxxxxxxxx">
  <App />
</PaystackProvider>

2. usePaystack() hook (recommended)

The hook returns initializePayment and isLoading. Calling initializePayment opens a checkout modal that the provider renders for you — you never have to mount a component yourself.

const { initializePayment, isLoading } = usePaystack();

3. <PaystackCheckout /> component

For a JSX-driven flow, or to use the package without a provider (pass a publicKey prop directly):

const [visible, setVisible] = useState(false);

<PaystackCheckout
  visible={visible}
  publicKey="pk_test_xxxxxxxx"
  email="[email protected]"
  amount={500000}
  currency="NGN"
  onSuccess={(tx) => {
    setVisible(false);
    console.log('Paid!', tx);
  }}
  onCancel={() => setVisible(false)}
/>

Payment Flow

   ┌──────────┐   (optional)   ┌──────────┐        ┌──────────────┐
   │  Your    │ ─ initialize ─▶│  Your    │ ─────▶ │ Paystack API │
   │  App     │  (access_code) │  Backend │        └──────┬───────┘
   └────┬─────┘                └──────────┘               │
        │  initializePayment({ email, amount, ... })      │
        ▼                                                  │
   ┌──────────────────────┐    Paystack Inline JS (v2)     │
   │  expo-paystack        │◀──────────────────────────────┘
   │  WebView checkout     │
   └────┬─────────────────┘
        │ onSuccess(transaction) / onCancel() / onError()
        ▼
   ┌──────────┐   verify reference (server-side)   ┌──────────────┐
   │  Your    │ ─────────────────────────────────▶ │ Paystack API │
   │  Backend │   verifyTransaction(ref, secret)   └──────────────┘
   └──────────┘

The frontend should always treat onSuccess as "the user finished the flow" — confirm the real status by verifying the reference on your backend.


API Reference

<PaystackProvider>

| Prop | Type | Required | Description | |---|---|:---:|---| | publicKey | string | ✅ | Your Paystack public key (pk_test_… / pk_live_…). | | children | React.ReactNode | ✅ | Your app tree. |

usePaystack()

Returns:

| Field | Type | Description | |---|---|---| | initializePayment | (config) => Promise<void> | Opens the checkout. | | isLoading | boolean | true while a payment is being prepared. |

initializePayment(config) / <PaystackCheckout> props

| Field | Type | Required | Description | |---|---|:---:|---| | email | string | ✅ | Customer email. | | amount | number | ✅ | Amount in the smallest currency unit (kobo, pesewa, cents). | | currency | PaystackCurrency | | NGN (default), GHS, ZAR, KES, USD, XOF. | | reference | string | | Auto-generated if omitted. | | channels | PaystackChannel[] | | Restrict the channels shown. | | label | string | | Display label (e.g. "Order #1234"). | | plan | string | | Paystack plan code (subscriptions). | | quantity | number | | Quantity for plan-based charges. | | metadata | Record<string, unknown> | | Custom metadata. | | subaccount | string | | Subaccount code for split payments. | | transactionCharge | number | | Flat fee (smallest unit) for the subaccount. | | bearer | 'account' \| 'subaccount' | | Who bears the transaction charge. | | accessCode | string | | Resume a backend-initialized transaction. | | onSuccess | (tx: PaystackTransaction) => void | ✅ | Success callback. | | onCancel | () => void | ✅ | Cancel callback. | | onError | (err: PaystackError) => void | | Error callback. |

Additional <PaystackCheckout>-only props: visible (required), publicKey (overrides provider), animationType ('slide' | 'fade' | 'none'), activityIndicatorColor, closeOnBackdropPress.

Utilities

| Function | Signature | Description | |---|---|---| | formatAmount | (amount, currency) => string | formatAmount(500000, 'NGN')₦5,000.00 | | toSubunit | (amount, currency) => number | toSubunit(5000, 'NGN')500000 | | getCurrencySymbol | (currency) => string | getCurrencySymbol('NGN') | | generateReference | () => Promise<string> | Crypto-random reference. | | verifyTransaction | (reference, secretKey) => Promise<PaystackVerifyResponse> | Server-side only. |


The access_code (backend-first) flow

For maximum security, initialize the transaction on your backend and pass the returned access_code to the app:

// 1. Your backend calls POST https://api.paystack.co/transaction/initialize
//    and returns { access_code, reference }.
const { access_code, reference } = await fetch('/api/checkout').then((r) => r.json());

// 2. The app resumes that exact transaction:
await initializePayment({
  email: '[email protected]',
  amount: 500000,
  accessCode: access_code,
  reference,
  onSuccess: (tx) => {/* verify on backend */},
  onCancel: () => {},
});

Supported Channels

| Channel | Countries (typical) | |---|---| | card | NG, GH, ZA, KE, CI | | bank / bank_transfer | NG | | ussd | NG | | qr | NG | | mobile_money | GH, KE | | eft | ZA | | apple_pay | Supported regions | | capitec_pay | ZA | | payattitude | NG |

Channel availability ultimately depends on your Paystack account, country, and the currency of the transaction.

Supported Currencies

| Code | Currency | Symbol | Smallest unit | |---|---|:---:|---| | NGN | Nigerian Naira | ₦ | kobo (×100) | | GHS | Ghanaian Cedi | ₵ | pesewa (×100) | | ZAR | South African Rand | R | cent (×100) | | KES | Kenyan Shilling | KSh | cent (×100) | | USD | US Dollar | $ | cent (×100) | | XOF | West African CFA Franc | CFA | ×100 |


Security Best Practices

  • Never put your secret key (sk_…) in app code. It grants full access to your Paystack account. The app only ever uses the public key.
  • Verify every payment on your backend with verifyTransaction(reference, secretKey) (or a direct call to the Paystack verify endpoint) before fulfilling an order. Client-side onSuccess is not proof of payment.
  • Prefer the access_code flow: initialize transactions on your backend so the amount and metadata cannot be tampered with on the device.
// Server-side only (e.g. an Expo API Route or your own backend):
import { verifyTransaction } from 'expo-paystack';

const result = await verifyTransaction(reference, process.env.PAYSTACK_SECRET_KEY!);
if (result.data.status === 'success') {
  // fulfill the order
}

TypeScript

Types are bundled — there is no @types/expo-paystack to install. Every public API, including PaystackTransaction, PaystackPaymentConfig, PaystackVerifyResponse, and the currency/channel unions, is fully typed.


FAQ

The WebView shows a blank/loading screen forever. Ensure react-native-webview is installed and that you are running a development build (not plain Expo Go). Do not add it to plugins — it is autolinked and has no config plugin.

"Reference already used / duplicate reference" error. Each transaction needs a unique reference. Omit reference to let the package generate one, or always generate a fresh one per attempt.

Does this work with the New Architecture? Yes. The package uses only Expo SDK packages and react-native-webview — no legacy bridge / NativeModules usage — and is verified against the New Architecture.

Can I use it without PaystackProvider? Yes — use <PaystackCheckout> directly with a publicKey prop. The provider is only required for the usePaystack() hook.

Which Expo SDK versions are supported? Expo SDK 54 and newer (React Native 0.81+, React 19+).


Example App

A complete demo lives in example/ covering the hook flow, component flow, mobile money, and the currency helpers. Add your own pk_test_… key in example/App.tsx and run it with npx expo run:ios.


Contributing

Contributions are welcome! To get started:

git clone https://github.com/codinnovate/expo-paystack.git
cd expo-paystack
npm install
npm run build       # build with react-native-builder-bob
npm run typecheck   # tsc --noEmit
npm run lint        # eslint

Please open an issue to discuss substantial changes before sending a PR, and keep the strict-TypeScript / zero-any bar intact.


Support


License

MIT © Sam Adeyemi