Hello friends 👋 , Recently, I was working on a React Native project with Expo. In that mobile application, I had to integrate the PayHere payment gateway in an Expo compatible way.
At first, I searched everywhere articles, YouTube videos, and many other resources. But I couldn’t find a proper working solution. Then I started going through the documentation carefully and even used AI tools to guide me. After trying different approaches, I finally found the solution. So today, I’m going to share it with you.
Before starting, you need to create a PayHere account and enable the sandbox environment.
Now you might be wondering what is PayHere?
PayHere is a Sri Lankan based payment gateway. It allows developers to integrate secure online payments into their own applications.
While developing this feature, I used Expo Go on my mobile phone to check all the changes I made to the app. The solution itself is not very complicated it’s actually simple. But there is one very important thing you need to know.
IMPORTANT 🚨
PayHere WILL NOT work in Expo Go!
You must build a development client using Expo Application Services (EAS Build). This is a one-time process that takes around 30 minutes. After that, all your code updates will be instant.
The Challenge : Expo + Native Modules
This is where most developers get stuck.
PayHere provides a React Native SDK, but there’s a catch it is a native module. That means it contains actual Java code (for Android) and Swift code (for iOS), not just JavaScript.
Now here’s the problem.
Expo Go the app we use to test our projects does not include PayHere’s native code. And it cannot include all native modules because there are thousands of them. If it did, the app would be extremely large.
So when you try to use PayHere inside Expo Go, it simply won’t work.
What happens when you try?
import PayHere from '@payhere/payhere-mobilesdk-reactnative';
PayHere.startPayment(paymentObject);
// ❌ Error: Native module cannot be null
You will get this error:
❌ Native module cannot be null
And that’s because Expo Go does not have the PayHere native module inside it.
Install dependencies
# Install dependencies
npm install @payhere/payhere-mobilesdk-reactnative@4.0.14
npx expo install expo-dev-clientThe expo-dev-client package is important because it allows you to:
- Build a custom version of your app with PayHere included
- Keep useful Expo features like Fast Refresh and debugging
- Update JavaScript instantly without rebuilding the app
By installing these first, your project will be ready for the PayHere integration without running into native module issues.
Implementation
// Prepare PayHere payment object
const paymentObject = {
sandbox: PAYMENT_CONFIG.SANDBOX,
merchant_id: PAYMENT_CONFIG.MERCHANT_ID,
notify_url: "", // Optional: Add your backend notification URL
order_id: order123,
items: ITEM_01,
amount: 200,
currency: "LKR",
first_name: "John",
last_name: "Silva",
email: "john@gmail.com",
phone: "0771234567",
address: "No:123,ABC Road,Colombo",
city: "Colombo",
country: "Sri Lanka",
custom_1: "PROPERTY TYPE",
custom_2: currentUser.uid,
};
Here PAYMENT_CONFIG can be defined as
export const PAYMENT_CONFIG = {
MERCHANT_ID: process.env.MERCHANT_ID!,
SANDBOX: true, // Set to false for production
};
Your MERCHANT_ID should add to the .env file and here you can use it.Also here we use SANDBOX as true because we are on developing stage and testing when you push this to production have to make it true.
PayHere.startPayment(
paymentObject,
async (paymentId: string) => {
// ✅ Payment Success
console.log('✅ Payment successful:', paymentId);
//update payment on database
try {
await updatePaymentRecord(orderId, {
status: 'completed',
paymentId: paymentId,
});
resolve({
success: true,
status: 'completed',
paymentId: paymentId,
orderId: orderId,
amount: paymentInput.amount,
});
} catch (error: any) {
console.error('Error updating payment record:', error);
resolve({
success: true,
status: 'completed',
paymentId: paymentId,
orderId: orderId,
amount: paymentInput.amount,
error: 'Payment successful but record update failed'
});
}
},
async (errorData: string) => {
// ❌ Payment Error
console.log('❌ Payment failed:', errorData);
//update payment on database
try {
await updatePaymentRecord(orderId, {
status: 'failed',
error: errorData,
});
} catch (error) {
console.error('Error updating failed payment:', error);
}
resolve({
success: false,
status: 'failed',
orderId: orderId,
error: errorData,
});
},
async () => {
// ⚠️ Payment Dismissed/Cancelled
console.log('⚠️ Payment cancelled by user');
try {
//update the database
await updatePaymentRecord(orderId, {
status: 'cancelled',
});
} catch (error) {
console.error('Error updating cancelled payment:', error);
}
resolve({
success: false,
status: 'cancelled',
orderId: orderId,
error: 'Payment was cancelled by user',
});
}
);
For PayHere.startPayment(), you need to provide the paymentObject and the callbacks for success, error, and dismissal
At the top of your service file, import PayHere:
import PayHere from '@payhere/payhere-mobilesdk-reactnative';
If you are using TypeScript, create a file named payhere-mobilesdk-reactnative.d.ts in your model folder:
// types/payhere-mobilesdk-reactnative.d.ts // Type definitions for @payhere/payhere-mobilesdk-reactnative declare module '@payhere/payhere-mobilesdk-reactnative' { export interface PayHerePaymentObject { sandbox: boolean; merchant_id: string; notify_url?: string; order_id: string; items: string; amount: string; currency: string; first_name: string; last_name: string; email: string; phone: string; address: string; city: string; country: string; delivery_address?: string; delivery_city?: string; delivery_country?: string; custom_1?: string; custom_2?: string; } export interface PayHere { startPayment( paymentObject: PayHerePaymentObject, onSuccess: (paymentId: string) => void, onError: (errorData: string) => void, onDismiss: () => void ): void; } const PayHere: PayHere; export default PayHere; }
Building and Testing Your App
Now that the implementation is complete, let's build and test the app. As mentioned earlier, you cannot use Expo Go to test PayHere. You must build the app first.
There are two ways to build your app:
Method 1: EAS Build
This is the easiest and most beginner friendly method. The build happens on Expo's cloud servers, so you don't need to install Android Studio or configure anything on your computer.
Step 1: Build Your App
npx eas-cli build --profile development --platform android
What happens:
- First time: You'll be asked to login to your Expo account (create one free at expo.dev/signup)
- Your code uploads to Expo's servers
- Expo compiles your app with PayHere included
- This takes 20-30 minutes
- You'll get a download link when complete
Step 2: Download and Install
- Click the download link in your terminal (or check expo.dev/accounts/[your-account]/builds)
- Download the APK file to your Android phone
- Install the APK (you may need to enable "Install from unknown sources" in your phone settings)
- A new app icon will appear on your phone
Step 3: Run the Development Server
npx expo start --dev-client
Pros:
- ✅ No Android Studio needed
- ✅ No environment setup
- ✅ Works on any computer (Windows/Mac/Linux)
- ✅ Team members can easily replicate
- ✅ Beginner-friendly
Cons:
- ❌ Requires internet connection
- ❌ First build takes 20-30 minutes
- ❌ Uses Expo's build servers
Method 2: Local Build with Prebuild (Advanced Users)
Prerequisites
Before starting, you need to:
- Install Android Studio
- Download from: developer.android.com/studio
- Install with default settings
- Open Android Studio once to complete setup
- Set Up Environment Variables Add a new User Variable:
- Variable name: ANDROID_HOME
- Variable value: C:\Users\YourUsername\AppData\Local\Android\Sdk
%ANDROID_HOME%\platform-tools%ANDROID_HOME%\tools
- Press Windows + R, type sysdm.cpl, press Enter
- Go to "Advanced" tab → "Environment Variables"
- Add the variables as described above
- Click OK and restart your terminal
Step 1: Prebuild (Generate Native Folders)
npx expo prebuild
After executing this code you can see there is folder named android will be on your folder.
Step 2: Create local.properties File
echo sdk.dir=C:\\Users\\exampleuser\\AppData\\Local\\Android\\Sdk > local.properties
Step 3: Build and Run
Now you have two options:
Option A: Run on connected Android phone
npx expo run:android
Make sure:
- Your phone is connected via USB cable
- USB debugging is enabled on your phone
- Your phone is recognized (check with adb devices)
# First, open Android Studio and start an emulator
# Then run:
npx expo run:android
Step 4: Development Mode
npx expo start --dev-client
Then:
- Your app will automatically launch on the connected device/emulator
- You can update JavaScript code and see changes instantly
- Only rebuild if you add new native modules
Pros:
- ✅ Build happens on your machine
- ✅ More control over the build process
- ✅ Can work offline
- ✅ No dependency on Expo's servers
Cons:
- ❌ Requires Android Studio (~5GB download)
- ❌ Complex environment setup
- ❌ Requires powerful computer
- ❌ Build errors can be harder to debug
- ❌ Different setup for each team member
- ❌ Can't easily build for iOS on Windows
Integrating PayHere with your React Native Expo app might feel tricky at first, especially because Expo Go doesn’t support native modules. But don’t worry once you follow the steps above, it’s actually pretty straightforward!
A few friendly reminders:
- Use sandbox mode for testing and switch to production when your app is live.
- Make sure your paymentObject matches the details of your service or project.
- EAS Build is the easiest way to get started, while local builds give more control if you’re up for it.
- Test on a real device so everything works smoothly.
Once it’s set up, PayHere makes handling payments in your app super simple and reliable. Happy coding!
