@geideasdk/payment-sdk-react-native
v0.0.1
Published
A comprehensive guide for integrating the GDPaymentSDK into React Native applications for both iOS and Android platforms.
Readme
GDPaymentSDK React Native Integration
A comprehensive guide for integrating the GDPaymentSDK into React Native applications for both iOS and Android platforms.
Table of Contents
- Prerequisites
- Project Setup
- Android Integration
- iOS Integration
- React Native Bridge Implementation
- Testing the Integration
Prerequisites
Before starting the integration, ensure you have:
- React Native development environment configured
- Node.js and npm/yarn installed
- Android Studio for Android development
- Xcode for iOS development (macOS only)
- GDPaymentSDK files:
- Android:
PGW-SDK-1.0.0.aar,PGW-SDK-1.0.0.module,PGW-SDK-1.0.0.pom - iOS:
CardScan.xcframework,GeideaPaymentSDK.xcframework
- Android:
Project Setup
1. Create a New React Native Project
npx react-native init GeideaPaymentApp
cd GeideaPaymentApp2. Install Required Dependencies
npm install react-native-safe-area-context3. Create Project Structure
GeideaPaymentApp/
├── App.tsx
├── native/
│ └── GeideaBridge.ts
├── android/
│ └── app/
│ ├── maven-repo/
│ │ └── net/
│ │ └── geidea/
│ │ └── PGW-SDK/
│ │ └── 1.0.0/
│ └── src/
│ └── main/
│ └── java/
│ └── net/
│ └── geidea/
│ └── paymentsdk/
│ ├── GeideaBridgeModule.kt
│ └── GeideaBridgePackage.kt
└── ios/
├── Frameworks/
│ ├── CardScan.xcframework
│ └── GeideaPaymentSDK.xcframework
└── GeideaPaymentApp/
├── GeideaBridge.m
└── GeideaBridge.swiftAndroid Integration
Step 1: Add SDK Files to Maven Repository
- Create the directory structure:
android/app/maven-repo/net/geidea/PGW-SDK/1.0.0/ - Copy the following files into this directory:
PGW-SDK-1.0.0.aarPGW-SDK-1.0.0.modulePGW-SDK-1.0.0.pom
Step 2: Configure Build Gradle (Project Level)
Edit android/build.gradle:
allprojects {
repositories {
google()
mavenCentral()
maven {
url "$rootDir/app/maven-repo"
}
}
}Step 3: Configure Build Gradle (App Level)
Edit android/app/build.gradle:
dependencies {
implementation("com.facebook.react:react-android")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("net.geidea:PGW-SDK:1.0.0")
implementation("com.google.android.material:material:1.9.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.core:core-ktx:1.10.1")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
implementation jscFlavor
}
}Step 4: Create Bridge Module
Create android/app/src/main/java/net/geidea/paymentsdk/GeideaBridgeModule.kt:
package net.geidea.paymentsdk
import android.app.Activity
import android.content.Intent
import com.facebook.react.bridge.*
import net.geidea.sdk.sdk.*
import android.util.Log
class GeideaBridgeModule(private val reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
private var currentPromise: Promise? = null
private val activityEventListener = object : ActivityEventListener {
override fun onActivityResult(
activity: Activity,
requestCode: Int,
resultCode: Int,
data: Intent?
) {
// SDK handles results via listener
}
override fun onNewIntent(intent: Intent) {
// Not needed
}
}
init {
reactContext.addActivityEventListener(activityEventListener)
}
override fun getName(): String = "GeideaBridge"
@ReactMethod
fun startWithConfig(params: ReadableMap, promise: Promise) {
if (currentPromise != null) {
promise.reject("E_IN_PROGRESS", "Payment already in progress")
return
}
val sessionId = params.getString("sessionId")
if (sessionId.isNullOrEmpty()) {
promise.reject("E_ARGS", "sessionId is required")
return
}
val language = mapLanguage(params.getString("language"))
val isSandbox = params.getString("environment") == "sandbox"
val region = mapRegion(params.getString("region"))
currentPromise = promise
try {
val activity = reactContext.currentActivity
if (activity == null) {
currentPromise = null
promise.reject("E_ACTIVITY", "Activity not found")
return
}
val theme = SDKTheme()
val config = GDPaymentSDKConfiguration(
theme = theme,
sessionId = sessionId,
language = language,
isSandbox = isSandbox,
region = region
)
val resultListener = object : GDPaymentResultListener {
override fun onPaymentCompleted(result: GDPaymentResult) {
val promise = currentPromise ?: return
currentPromise = null
val resultMap = convertPaymentResultToMap(result)
val response = Arguments.createMap().apply {
putString("status", "completed")
putMap("result", resultMap)
}
promise.resolve(response)
}
override fun onPaymentFailure(error: GDPaymentError) {
val promise = currentPromise ?: return
currentPromise = null
val errorMap = convertPaymentErrorToMap(error)
val errorCode = errorMap.getString("code") ?: "E_PAYMENT_FAILED"
val errorMessage = errorMap.getString("message") ?: "Payment failed"
promise.reject(errorCode, errorMessage, errorMap)
}
override fun onPaymentCanceled() {
val promise = currentPromise ?: return
currentPromise = null
val response = Arguments.createMap().apply {
putString("status", "canceled")
}
promise.resolve(response)
}
}
val sdk = GDPaymentSDK.sharedInstance()
sdk.setPaymentCallback(resultListener)
val presentationStyle = SDKPresentationStyle.Push()
sdk.start(config, activity, presentationStyle)
} catch (e: Exception) {
currentPromise = null
promise.reject("E_SDK_START", "Failed to start SDK: ${e.localizedMessage}", e)
}
}
private fun mapLanguage(raw: String?): SDKLanguage {
return when (raw?.lowercase()) {
"ar", "arabic", "ar-eg", "ar-sa" -> SDKLanguage.ARABIC
else -> SDKLanguage.ENGLISH
}
}
private fun mapRegion(raw: String?): REGION {
return when (raw?.lowercase()) {
"sa", "ksa", "saudi", "saudi arabia" -> REGION.KSA
"ae", "uae", "emirates" -> REGION.UAE
"eg", "egypt", "egy" -> REGION.EGY
else -> REGION.EGY
}
}
private fun convertPaymentResultToMap(result: GDPaymentResult): WritableMap {
val map = Arguments.createMap()
try {
result.orderId?.let { map.putString("orderId", it) } ?: map.putNull("orderId")
result.tokenId?.let { map.putString("tokenId", it) } ?: map.putNull("tokenId")
result.agreementId?.let { map.putString("agreementId", it) } ?: map.putNull("agreementId")
result.paymentMethod?.let { pm ->
val paymentMethodMap = Arguments.createMap()
pm.type?.let { paymentMethodMap.putString("type", it) }
pm.brand?.let { paymentMethodMap.putString("brand", it) }
pm.cardholderName?.let { paymentMethodMap.putString("cardholderName", it) }
pm.maskedCardNumber?.let { paymentMethodMap.putString("maskedCardNumber", it) }
pm.wallet?.let { paymentMethodMap.putString("wallet", it) } ?: paymentMethodMap.putNull("wallet")
pm.expiryDate?.let { expiry ->
val expiryMap = Arguments.createMap()
expiryMap.putInt("month", expiry.month ?: 0)
expiryMap.putInt("year", expiry.year ?: 0)
paymentMethodMap.putMap("expiryDate", expiryMap)
} ?: paymentMethodMap.putNull("expiryDate")
map.putMap("paymentMethod", paymentMethodMap)
} ?: map.putNull("paymentMethod")
} catch (e: Exception) {
map.putString("_parseError", e.localizedMessage ?: "Unknown error")
}
return map
}
private fun convertPaymentErrorToMap(error: GDPaymentError): WritableMap {
val map = Arguments.createMap()
try {
error.javaClass.declaredFields.forEach { field ->
field.isAccessible = true
val value = field.get(error)
when (value) {
null -> map.putNull(field.name)
is String -> map.putString(field.name, value)
is Int -> map.putInt(field.name, value)
is Long -> map.putDouble(field.name, value.toDouble())
is Double -> map.putDouble(field.name, value)
is Float -> map.putDouble(field.name, value.toDouble())
is Boolean -> map.putBoolean(field.name, value)
else -> map.putString(field.name, value.toString())
}
}
} catch (e: Exception) {
map.putString("code", "E_PAYMENT_ERROR")
map.putString("message", e.localizedMessage)
}
return map
}
}Step 5: Create Bridge Package
Create android/app/src/main/java/net/geidea/paymentsdk/GeideaBridgePackage.kt:
package net.geidea.paymentsdk
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class GeideaBridgePackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(GeideaBridgeModule(reactContext))
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}
}Step 6: Register Package
Edit android/app/src/main/java/com/geideapaymentapp/MainApplication.java (or .kt):
import net.geidea.paymentsdk.GeideaBridgePackage; // Add this import
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new GeideaBridgePackage()); // Add this line
return packages;
}iOS Integration
Step 1: Add Framework Files
- Create a
Frameworksfolder inside theiosdirectory - Copy
CardScan.xcframeworkandGeideaPaymentSDK.xcframeworkinto this folder
Step 2: Link Frameworks in Xcode
- Open
ios/GeideaPaymentApp.xcworkspacein Xcode - Select your project in the Project Navigator
- Select your app target
- Go to General tab
- Scroll to Frameworks, Libraries, and Embedded Content
- Click the + button and add:
CardScan.xcframeworkGeideaPaymentSDK.xcframework
- Set both to Embed & Sign
Step 3: Create Bridging Header
If you don't have a bridging header, create one:
- In Xcode, go to File → New → File
- Select Header File
- Name it
GeideaPaymentApp-Bridging-Header.h - Add the following content:
#import <React/RCTBridgeModule.h>- In Build Settings, set Objective-C Bridging Header to
GeideaPaymentApp/GeideaPaymentApp-Bridging-Header.h
Step 4: Create Objective-C Bridge File
Create ios/GeideaPaymentApp/GeideaBridge.m:
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(GeideaBridge, NSObject)
RCT_EXTERN_METHOD(startWithConfig:(NSDictionary *)params
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
@endStep 5: Create Swift Implementation
Create ios/GeideaPaymentApp/GeideaBridge.swift:
import Foundation
import React
import GeideaPaymentSDK
import UIKit
@objc(GeideaBridge)
class GeideaBridge: NSObject {
@objc static func requiresMainQueueSetup() -> Bool { true }
private var currentDelegate: RNGeideaDelegate?
@objc(startWithConfig:resolver:rejecter:)
func startWithConfig(
_ params: NSDictionary,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock
) {
guard let sessionId = params["sessionId"] as? String, !sessionId.isEmpty else {
return reject("E_ARGS", "sessionId is required", nil)
}
let lang = mapLanguage(params["language"] as? String)
let env = mapEnvironment(params["environment"] as? String)
let region = mapRegion(params["region"] as? String)
let cfg = GeideaPaymentSDK.GDPaymentSDKConfiguration(
sessionId: sessionId,
applePayConfig: ApplePayConfigurations(merchantId: "merchant.com.geidea.sdk.payment.mock"),
language: lang,
environmentType: env,
region: region
)
Task { @MainActor in
let delegate = RNGeideaDelegate(
onCompleted: { result in
self.currentDelegate = nil
resolve([
"status": "completed",
"result": result
])
},
onCanceled: {
self.currentDelegate = nil
resolve([
"status": "canceled"
])
},
onFailed: { code, message, error in
self.currentDelegate = nil
reject(code, message, error)
}
)
self.currentDelegate = delegate
do {
let instance = GeideaPaymentSDK.GDPaymentSDK.sharedInstance()
try instance.start(configuration: cfg, delegate: delegate)
} catch {
self.currentDelegate = nil
reject("E_SDK_START", "Failed to start SDK: \(error.localizedDescription)", error as NSError)
}
}
}
private func mapLanguage(_ raw: String?) -> GeideaPaymentSDK.AppLanguage {
if let key = raw?.lowercased(), let val = GeideaPaymentSDK.AppLanguage(rawValue: key) {
return val
}
switch (raw ?? "").lowercased() {
case "ar", "arabic", "ar-eg", "ar-sa": return .arabic
default: return .english
}
}
private func mapEnvironment(_ raw: String?) -> GeideaPaymentSDK.EnvironmentType {
if let key = raw?.lowercased(), let val = GeideaPaymentSDK.EnvironmentType(rawValue: key) {
return val
}
switch (raw ?? "").lowercased() {
case "production": return .production
default: return .sandbox
}
}
private func mapRegion(_ raw: String?) -> GeideaPaymentSDK.Region {
if let s = raw?.lowercased(), let v = GeideaPaymentSDK.Region(rawValue: s) {
return v
}
switch (raw ?? "").lowercased() {
case "sa", "ksa": return .ksa
case "ae", "uae": return .uae
case "eg", "egypt": return .egy
default: return .egy
}
}
}
final class RNGeideaDelegate: NSObject, GeideaPaymentSDK.GDSDKProtocol {
private let onCompleted: (_ result: [String: Any]) -> Void
private let onCanceled: () -> Void
private let onFailed: (_ code: String, _ message: String, _ error: NSError?) -> Void
init(onCompleted: @escaping (_ result: [String: Any]) -> Void,
onCanceled: @escaping () -> Void,
onFailed: @escaping (_ code: String, _ message: String, _ error: NSError?) -> Void) {
self.onCompleted = onCompleted
self.onCanceled = onCanceled
self.onFailed = onFailed
}
@MainActor
func onPaymentCompleted(result: GeideaPaymentSDK.GDPaymentResult) {
onCompleted(Self.flattenToDictionary(result))
}
@MainActor
func onPaymentFailed(error: GeideaPaymentSDK.GDSDKError) {
let dict = Self.flattenToDictionary(error)
let code = (dict["code"] as? String) ?? "E_PAYMENT_FAILED"
let msg = (dict["message"] as? String)
?? (dict["localizedDescription"] as? String)
?? "Payment failed"
onFailed(code, msg, NSError(domain: code, code: -1, userInfo: dict))
}
@MainActor
func onPaymentCanceled() {
onCanceled()
}
private static func flattenToDictionary(_ value: Any) -> [String: Any] {
if let d = value as? [String: Any] { return d }
if let e = value as? NSError {
return ["code": e.domain, "message": e.localizedDescription]
}
let m = Mirror(reflecting: value)
var out: [String: Any] = ["description": String(describing: value)]
for child in m.children {
if let label = child.label {
out[label] = unwrap(child.value)
}
}
return out
}
private static func unwrap(_ any: Any) -> Any {
let mirror = Mirror(reflecting: any)
if mirror.displayStyle != .optional { return any }
if let child = mirror.children.first { return child.value }
return NSNull()
}
}React Native Bridge Implementation
Create TypeScript Bridge
Create native/GeideaBridge.ts:
import { NativeModules } from 'react-native';
type Language = 'en' | 'ar';
type Environment = 'sandbox' | 'production';
type Region = 'egypt' | 'ksa' | 'uae';
type StartWithConfigOptions = {
sessionId: string;
language?: Language;
environment?: Environment;
region?: Region;
};
type PaymentMethod = {
type?: string;
brand?: string;
maskedCardNumber?: string;
cardholderName?: string;
wallet?: string;
expiryDate?: {
month: number;
year: number;
};
};
type GeideaResult = {
status: 'completed' | 'canceled';
result?: {
orderId?: string;
tokenId?: string;
agreementId?: string;
paymentMethod?: PaymentMethod;
};
};
const { GeideaBridge } = NativeModules as {
GeideaBridge: { startWithConfig(opts: StartWithConfigOptions): Promise<GeideaResult> };
};
let inFlight: Promise<GeideaResult> | null = null;
export function payWithGeidea(opts: StartWithConfigOptions): Promise<GeideaResult> {
if (!opts?.sessionId) return Promise.reject(new Error('sessionId is required'));
if (inFlight) return inFlight;
inFlight = GeideaBridge.startWithConfig({
language: 'en',
environment: 'sandbox',
region: 'egypt',
...opts,
}).finally(() => {
inFlight = null;
});
return inFlight;
}Testing the Integration
Android Testing
npx react-native run-androidiOS Testing
cd ios
pod install
cd ..
npx react-native run-iosTest Flow
- Select region (Egypt, KSA, or UAE)
- Select environment (Testing or Pre-Production)
- Select language (English or Arabic)
- Enter a valid session ID
- Tap the PAY button
- Complete the payment flow in the SDK
- Verify the response is displayed correctly
Support
For issues or questions, please refer to the official GDPaymentSDK documentation or contact Geidea support.
License
This integration guide is provided as-is for developers implementing the GDPaymentSDK in React Native applications.
