Bonaventure OgetoBy Bonaventure Ogeto|

Accept Payments with Paystack in React Native

React Native apps cannot call the Paystack API with a secret key. Your backend initializes the transaction and returns the authorization_url. Open this URL with react-native-paystack-webview or a custom react-native-webview. Detect the callback URL redirect, extract the reference, and verify the transaction on your backend.

Install react-native-paystack-webview

npm install react-native-paystack-webview react-native-webview
# or
yarn add react-native-paystack-webview react-native-webview

# iOS
cd ios && pod install

For Expo managed workflow, use Expo WebBrowser instead:

npx expo install expo-web-browser

Backend: Initialize Transaction

// Node.js backend
app.post('/api/pay/initialize', async (req, res) => {
  var { email, amount } = req.body;
  var reference = 'rn_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6);

  var response = await fetch('https://api.paystack.co/transaction/initialize', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      email,
      amount: Math.round(amount * 100), // kobo
      reference,
      callback_url: 'https://yourapp.com/payment/callback',
    }),
  });

  var data = await response.json();
  if (!data.status) return res.status(400).json({ error: data.message });

  res.json({
    authorization_url: data.data.authorization_url,
    access_code: data.data.access_code,
    reference: data.data.reference,
  });
});

React Native: Paystack WebView Checkout

// PaymentScreen.tsx
import React, { useState } from 'react';
import { View, ActivityIndicator, Alert } from 'react-native';
import PaystackWebView from 'react-native-paystack-webview';

interface PaymentScreenProps {
  route: {
    params: {
      email: string;
      amount: number; // in display units (e.g. NGN 5000)
    };
  };
  navigation: any;
}

export function PaymentScreen({ route, navigation }: PaymentScreenProps) {
  var { email, amount } = route.params;
  var [loading, setLoading] = useState(true);

  return (
    <View style={{ flex: 1 }}>
      <PaystackWebView
        paystackKey="pk_test_xxxxxxxxxxxx"  // Your PUBLIC key
        amount={amount}
        billingEmail={email}
        activityIndicatorColor="green"
        onCancel={() => {
          navigation.goBack();
        }}
        onSuccess={async (response) => {
          // response contains transactionRef
          var reference = response.transactionRef?.reference || response.transactionRef;

          // Verify on your backend
          try {
            var verifyRes = await fetch(
              'https://your-backend.com/api/pay/verify?reference=' + reference
            );
            var result = await verifyRes.json();

            if (result.verified) {
              navigation.navigate('PaymentSuccess', { reference });
            } else {
              Alert.alert('Payment issue', 'Please contact support');
              navigation.goBack();
            }
          } catch (e) {
            Alert.alert('Error', 'Could not verify payment');
            navigation.goBack();
          }
        }}
        autoStart={true}
      />
    </View>
  );
}

Note: paystackKey takes your public key, not the secret key. react-native-paystack-webview loads Paystack's hosted checkout and handles the WebView internally.

Expo: WebBrowser Alternative

For Expo managed workflow, use expo-web-browser to open the authorization URL:

import * as WebBrowser from 'expo-web-browser';

async function startPayment(email: string, amount: number) {
  // Get authorization URL from your backend
  var response = await fetch('https://your-backend.com/api/pay/initialize', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, amount }),
  });
  var data = await response.json();

  if (!data.authorization_url) {
    Alert.alert('Error', 'Could not initialize payment');
    return;
  }

  // Open in WebBrowser
  var result = await WebBrowser.openAuthSessionAsync(
    data.authorization_url,
    'yourapp://payment/callback'  // Deep link callback
  );

  if (result.type === 'success') {
    var url = new URL(result.url);
    var reference = url.searchParams.get('reference') || url.searchParams.get('trxref');
    if (reference) {
      await verifyPayment(reference);
    }
  }
}

Deep link handling requires configuring the scheme in your app.json and setting up deep link routing.

Learn More

Key Takeaways

  • Never put your Paystack secret key in React Native code. Initialize transactions from your backend.
  • react-native-paystack-webview is the most popular library for Paystack checkout in React Native.
  • Both Expo and bare React Native workflows support the WebView approach.
  • After checkout, the library provides the transaction reference in the onSuccess callback.
  • Always verify the transaction on your backend before granting access or fulfilling orders.
  • Add INTERNET permission on Android and ATS configuration on iOS for WebView to load Paystack pages.

Frequently Asked Questions

Can I use Paystack in Expo without ejecting?
Yes. Use expo-web-browser to open the Paystack authorization URL. This opens the checkout in a system browser or in-app browser. After payment, handle the deep link callback. You do not need react-native-paystack-webview, which requires native modules.
Where do I put the Paystack public key in React Native?
The public key (pk_test_ or pk_live_) can go in your React Native code — it is safe to expose. Store it in your .env file as EXPO_PUBLIC_PAYSTACK_KEY for Expo, or in a config file. Never put the secret key (sk_test_ or sk_live_) in your React Native code.
Does react-native-paystack-webview work on both iOS and Android?
Yes. The library wraps react-native-webview, which supports both platforms. Ensure you install pods for iOS (cd ios && pod install) and have INTERNET permission in AndroidManifest.xml.
What is the difference between access_code and authorization_url for React Native?
Both can be used to launch checkout. The authorization_url is a full URL you can open in a WebView or browser. The access_code is a shorter token used with react-native-paystack-webview's accessCode prop for inline checkout on the hosted Paystack page.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon