To implement the EasyMerchantSdk in your React Native App project. Follow the below steps:
To add the path of sdk in your project. Open your package.json
file and inside the dependencies
section, add the below code and set the path of the sdk where you store on your disk.
"dependencies": {
"@jimrising/easymerchantsdk-react-native": "^1.8.5"
},
or using command
npm i @jimrising/easymerchantsdk-react-native
Now open your android
folder and there is a build.gradle
file. Open it and add the below code in it.
allprojects {
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
maven {
url = uri(properties.getProperty('GITHUB_URL'))
credentials {
username = properties.getProperty('GITHUB_USERNAME')
password = properties.getProperty('GITHUB_PASSWORD')
}
}
}
}
Add below content inside the AppDelegate.swift File :-
- Ruby 3.2.8
Create a new file named AppDelegate.swift
import UIKit
import easymerchantsdk
import React
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
let jsCodeLocation: URL
#if DEBUG
jsCodeLocation = URL(string: "http://localhost:8081/index.bundle?platform=ios")!
#else
jsCodeLocation = Bundle.main.url(forResource: "main", withExtension: "jsbundle")!
#endif
let bridge = RCTBridge(
bundleURL: jsCodeLocation,
moduleProvider: nil,
launchOptions: launchOptions
)
guard let validBridge = bridge else {
fatalError("React Native bridge failed to initialize.")
}
let rootView = RCTRootView(
bridge: validBridge,
moduleName: "EasyMerchantTestApp", // replace it with your app name
initialProperties: nil
)
self.window = UIWindow(frame: UIScreen.main.bounds)
let rootViewController = UIViewController()
rootViewController.view = rootView
self.window?.rootViewController = rootViewController
self.window?.makeKeyAndVisible()
if let easyMerchantSdkPlugin = bridge?.module(for: EasyMerchantSdkPlugin.self) as? EasyMerchantSdkPlugin {
easyMerchantSdkPlugin.setViewController(rootViewController)
} else {
print("Failed to retrieve EasyMerchantSdkPlugin instance from React Native bridge.")
}
return true
}
}
inside the PodFile add below
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
platform :ios, 16.0
pod 'easymerchantsdk', :path => '../node_modules/easymerchantsdk-react-native/ios'
you can call the sdk using below example:
import React, { useState, useEffect } from 'react';
import {
StyleSheet,
Text,
View,
TextInput,
Button,
Alert,
ScrollView,
Platform,
NativeModules,
Switch,
TouchableOpacity,
NativeEventEmitter,
} from 'react-native';
const { RNEasymerchantsdk, EasyMerchantSdk } = NativeModules;
const externalConfig = {
amount: '',
email: '',
isRecurring: false,
isAuthenticatedACH: false,
isSecureAuthentication: false,
isBillingVisible: false,
isAdditionalVisible: false,
emailEditable: true,
isEmail: true,
billingInfo: {
visibility: { billing: false, additional: false },
billing: {
address: 'San Fran, Punjab',
country: 'USA',
state: 'California',
city: 'Paris',
postal_code: '234234',
},
billingRequired: {
address: true,
country: true,
state: true,
city: false,
postal_code: true,
},
additional: {
name: 'Test User',
email_address: 'test@gmail.com',
phone_number: '21408713290',
description: 'Test',
},
additionalRequired: {
name: true,
email_address: true,
phone_number: true,
description: false,
},
},
themeConfiguration: {
bodyBackgroundColor: '#0f1715',
containerBackgroundColor: '#152321',
primaryFontColor: '#FFFFFF',
secondaryFontColor: '#A0B5A4',
primaryButtonBackgroundColor: '#10B981',
primaryButtonHoverColor: '#059669',
primaryButtonFontColor: '#FFFFFF',
secondaryButtonBackgroundColor: '#374151',
secondaryButtonHoverColor: '#4B5563',
secondaryButtonFontColor: '#E5E7EB',
borderRadius: '8',
fontSize: '14',
fontWeight: 500,
fontFamily: '"Inter", sans-serif',
},
grailPayParams: {
role: 'business',
timeout: 10,
isSandbox: true,
brandingName: 'Lyfecycle Payments',
finderSubtitle: 'Search for your bank',
searchPlaceholder: 'Enter bank name',
},
recurringData: {
allowCycles: 2,
intervals: ['weekly', 'monthly'],
recurringStartType: 'custom',
recurringStartDate: '07/08/2025',
},
androidConfig: {
currency: 'usd',
saveCard: true,
saveAccount: true,
showReceipt: true,
showDonate: false,
showTotal: true,
showSubmitButton: true,
paymentMethod: ['card', 'ach'],
name: 'Pavan',
fields: {
visibility: { billing: false, additional: false },
billing: [
{ name: 'address', required: true, value: 'New Address' },
{ name: 'country', required: true, value: 'India' },
{ name: 'state', required: true, value: 'California' },
{ name: 'city', required: true, value: 'Goa' },
{ name: 'postal_code', required: true, value: '1432456' },
],
additional: [
{ name: 'name', required: true, value: 'Test User 7' },
{ name: 'email_address', required: true, value: 'usertest@gmail.com' },
{ name: 'phone_number', required: true, value: '8978967895' },
{ name: 'description', required: true, value: 'Hi This is description' },
],
},
appearanceSettings: {
theme: 'dark',
bodyBackgroundColor: '#121212',
containerBackgroundColor: '#1E1E1E',
primaryFontColor: '#FFFFFF',
secondaryFontColor: '#B0B0B0',
primaryButtonBackgroundColor: '#2563EB',
primaryButtonHoverColor: '#1D4ED8',
primaryButtonFontColor: '#FFFFFF',
secondaryButtonBackgroundColor: '#374151',
secondaryButtonHoverColor: '#4B5563',
secondaryButtonFontColor: '#E5E7EB',
borderRadius: '8',
fontSize: '14',
fontWeight: '500',
fontFamily: 'Inter, sans-serif',
},
},
};
const App = () => {
const [amount, setAmount] = useState(externalConfig.amount);
const [email, setEmail] = useState(externalConfig.email);
const [environment, setEnvironment] = useState('sandbox');
const [isRecurring, setIsRecurring] = useState(externalConfig.isRecurring);
const [isAuthenticatedACH, setAuthenticatedACH] = useState(externalConfig.isAuthenticatedACH);
const [isSecureAuthentication, setSecureAuthentication] = useState(externalConfig.isSecureAuthentication);
const [isBillingVisible, setBillingVisible] = useState(externalConfig.isBillingVisible);
const [isAdditionalVisible, setAdditionalVisible] = useState(externalConfig.isAdditionalVisible);
const [emailEditable, setEmailEditable] = useState(externalConfig.emailEditable); // Android
const [isEmail, setIsEmail] = useState(externalConfig.isEmail); // iOS
const [billingInfo, setBillingInfo] = useState(externalConfig.billingInfo);
const [themeConfiguration, setThemeConfiguration] = useState(externalConfig.themeConfiguration);
const [grailPayParams, setGrailPayParams] = useState(externalConfig.grailPayParams);
const [recurringData, setRecurringData] = useState(externalConfig.recurringData);
const [androidConfig, setAndroidConfig] = useState(externalConfig.androidConfig);
const [result, setResult] = useState('');
const [referenceToken, setReferenceToken] = useState('');
const [loading, setLoading] = useState(false);
const [apiKey, setApiKey] = useState('apiKey');
const [apiSecret, setApiSecret] = useState('apiSecret');
const [isEnvironmentLoading, setIsEnvironmentLoading] = useState(false);
const easyMerchantEvents = new NativeEventEmitter(RNEasymerchantsdk);
useEffect(() => {
const successSub = easyMerchantEvents.addListener('PaymentSuccess', (data) => {
console.log('Payment success event:', data);
const parsed = JSON.parse(data.response);
console.log('Parsed PaymentSuccess:', parsed);
if (parsed.billingInfo) {
const billing = JSON.parse(parsed.billingInfo);
console.log('Billing Info:', billing);
}
if (parsed.additional_info) {
const additional = JSON.parse(parsed.additional_info);
console.log('Additional Info:', additional);
}
setResult(JSON.stringify(parsed, null, 2));
});
const statusSub = easyMerchantEvents.addListener('PaymentStatus', (data) => {
console.log('Raw Payment status event:', data);
const parsed = JSON.parse(data.statusResponse);
console.log('Parsed PaymentStatus:', parsed);
setResult(JSON.stringify(parsed, null, 2));
});
const statusErrorSub = easyMerchantEvents.addListener('PaymentStatusError', (data) => {
console.log('Payment status error event:', data);
setResult(`Status Error: ${JSON.stringify(data)}`);
});
return () => {
successSub.remove();
statusSub.remove();
statusErrorSub.remove();
};
}, []);
useEffect(() => {
const updateEnvironment = async () => {
const key = environment === 'staging'
? 'stagingApiKey'
: 'sandboxApiKey';
const secret = environment === 'staging'
? 'stagingApiSecret'
: 'sandboxApiSecret';
setApiKey(key);
setApiSecret(secret);
if (Platform.OS === 'ios') {
setIsEnvironmentLoading(true);
try {
await EasyMerchantSdk.setViewController();
await EasyMerchantSdk.configureEnvironment(environment, key, secret);
console.log(`iOS Environment configured: ${environment} with key ${key}`);
} catch (err) {
console.error('iOS Initialization Error:', err);
Alert.alert('Error', `Failed to configure iOS environment: ${err.message}`);
} finally {
setIsEnvironmentLoading(false);
}
}
};
updateEnvironment();
}, [environment]);
useEffect(() => {
setBillingInfo(prev => ({
...prev,
visibility: {
billing: isBillingVisible,
additional: isAdditionalVisible,
},
}));
}, [isBillingVisible, isAdditionalVisible]);
const updateBillingInfo = (section, field, value) => {
setBillingInfo(prev => ({
...prev,
[section]: {
...prev[section],
[field]: value,
},
}));
};
const updateAndroidConfig = (field, value) => {
setAndroidConfig(prev => ({
...prev,
[field]: value,
}));
};
const updateAndroidConfigFields = (section, index, field, value) => {
setAndroidConfig(prev => ({
...prev,
fields: {
...prev.fields,
[section]: prev.fields[section].map((item, i) =>
i === index ? { ...item, [field]: value } : item
),
},
}));
};
const updateAndroidConfigAppearanceSettings = (field, value) => {
setAndroidConfig(prev => ({
...prev,
appearanceSettings: {
...prev.appearanceSettings,
[field]: value,
},
}));
};
const updateThemeConfiguration = (field, value) => {
setThemeConfiguration(prev => ({
...prev,
[field]: value,
}));
};
const updateGrailPayParams = (field, value) => {
setGrailPayParams(prev => ({
...prev,
[field]: value,
}));
};
const updateRecurringData = (field, value) => {
setRecurringData(prev => ({
...prev,
[field]: value,
}));
};
const togglePaymentMethod = (method) => {
setAndroidConfig(prev => ({
...prev,
paymentMethod: prev.paymentMethod.includes(method)
? prev.paymentMethod.filter(m => m !== method)
: [...prev.paymentMethod, method],
}));
};
const toggleInterval = (interval) => {
setRecurringData(prev => ({
...prev,
intervals: prev.intervals.includes(interval)
? prev.intervals.filter(i => i !== interval)
: [...prev.intervals, interval],
}));
};
const handlePayment = async () => {
if (!amount || isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) {
return Alert.alert('Error', 'Please enter a valid amount');
}
if (!email) {
return Alert.alert('Error', 'Please enter an email address');
}
setLoading(true);
if (Platform.OS === 'android') {
await handleAndroidBilling();
} else {
await handleIosBilling();
}
};
const handleAndroidBilling = async () => {
const config = {
amount,
apiKey,
secretKey: apiSecret,
jsonConfig: {
environment,
amount,
tokenOnly: false,
currency: androidConfig.currency,
saveCard: androidConfig.saveCard,
saveAccount: androidConfig.saveAccount,
authenticatedACH: isAuthenticatedACH,
secureAuthentication: isSecureAuthentication,
showReceipt: androidConfig.showReceipt,
showDonate: androidConfig.showDonate,
showTotal: androidConfig.showTotal,
showSubmitButton: androidConfig.showSubmitButton,
paymentMethod: androidConfig.paymentMethod,
emailEditable,
email,
name: androidConfig.name,
fields: {
...androidConfig.fields,
visibility: {
billing: isBillingVisible,
additional: isAdditionalVisible,
},
},
...(isRecurring && {
recurring: {
enableRecurring: true,
recurringData,
},
}),
grailPayParams,
appearanceSettings: androidConfig.appearanceSettings,
},
};
try {
const response = await RNEasymerchantsdk.makePayment(config);
console.log('Full payment response:', response);
const parsedResponse = {
...response,
billingInfo: response.billingInfo ? JSON.parse(response.billingInfo) : null,
additional_info: response.additional_info ? JSON.parse(response.additional_info) : null,
};
setResult(JSON.stringify(parsedResponse, null, 2));
} catch (error) {
setResult(`Error: ${error.message}`);
Alert.alert('Payment Error', error.message);
} finally {
setLoading(false);
}
};
const handleIosBilling = async () => {
try {
const result = await EasyMerchantSdk.billing(
amount,
androidConfig.currency || 'usd',
billingInfo,
androidConfig.paymentMethod,
themeConfiguration,
false, // tokenOnly
androidConfig.saveCard,
androidConfig.saveAccount,
isAuthenticatedACH,
grailPayParams,
'Submit',
isRecurring,
isRecurring ? recurringData.allowCycles : 0,
isRecurring ? recurringData.intervals : [],
isRecurring ? recurringData.recurringStartType : '',
isRecurring ? recurringData.recurringStartDate : '',
isSecureAuthentication,
androidConfig.showReceipt,
androidConfig.showTotal,
androidConfig.showSubmitButton,
isEmail,
email,
androidConfig.name
);
const refToken = result?.additionalInfo?.threeDSecureStatus?.data?.ref_token;
if (refToken) setReferenceToken(refToken);
setResult(JSON.stringify(result, null, 2));
} catch (error) {
console.error('Billing Error:', error);
setResult(`Billing Error: ${error.message || JSON.stringify(error)}`);
} finally {
setLoading(false);
}
};
const handleCheckStatus = async () => {
setLoading(true);
try {
const response = await RNEasymerchantsdk.checkPaymentStatus();
console.log('Full payment response:', response);
setResult(JSON.stringify(response, null, 2));
} catch (error) {
setResult(`Error: ${error.message}`);
Alert.alert('Status Check Error', error.message);
} finally {
setLoading(false);
}
};
const handlePaymentReference = async () => {
if (Platform.OS === 'android') {
setResult('Payment Reference not supported on Android');
return;
}
if (!referenceToken) {
setResult('No reference token available');
return;
}
try {
const response = await EasyMerchantSdk.paymentReference(referenceToken);
setResult(`Payment Reference:\n${JSON.stringify(response, null, 2)}`);
} catch (error) {
setResult(`Payment Reference Error: ${error.message || JSON.stringify(error)}`);
} finally {
setLoading(false);
}
};
return (
<View style={styles.container}>
<ScrollView contentContainerStyle={styles.scrollContent}>
<Text style={styles.title}>EasyMerchant SDK</Text>
<Text style={styles.sectionTitle}>Basic Info</Text>
<Text style={styles.label}>Amount</Text>
<TextInput
style={styles.input}
placeholder="Enter amount (e.g., 10.00)"
keyboardType="decimal-pad"
value={amount}
onChangeText={setAmount}
/>
<Text style={styles.label}>Email</Text>
<TextInput
style={styles.input}
placeholder="Enter email"
keyboardType="email-address"
value={email}
onChangeText={setEmail}
/>
<Text style={styles.sectionTitle}>Environment</Text>
<View style={styles.pickerContainer}>
<Text style={styles.label}>Select Environment</Text>
<View style={styles.buttonGroup}>
<TouchableOpacity
style={[
styles.environmentButton,
{ backgroundColor: environment === 'sandbox' ? '#2563EB' : '#ccc' },
]}
onPress={() => {
console.log('Sandbox tapped, setting environment to sandbox');
setEnvironment('sandbox');
}}
disabled={isEnvironmentLoading || environment === 'sandbox'}
>
<Text style={styles.buttonText}>Sandbox</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.environmentButton,
{ backgroundColor: environment === 'staging' ? '#2563EB' : '#ccc' },
]}
onPress={() => {
console.log('Staging tapped, setting environment to staging');
setEnvironment('staging');
}}
disabled={isEnvironmentLoading || environment === 'staging'}
>
<Text style={styles.buttonText}>Staging</Text>
</TouchableOpacity>
</View>
</View>
<Text style={styles.sectionTitle}>Payment Options</Text>
<View style={styles.toggleContainer}>
<Text style={styles.label}>Recurring Payment</Text>
<Switch
value={isRecurring}
onValueChange={setIsRecurring}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={isRecurring ? '#fff' : '#f4f3f4'}
/>
</View>
<View style={styles.toggleContainer}>
<Text style={styles.label}>Authenticated ACH</Text>
<Switch
value={isAuthenticatedACH}
onValueChange={setAuthenticatedACH}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={isAuthenticatedACH ? '#fff' : '#f4f3f4'}
/>
</View>
<View style={styles.toggleContainer}>
<Text style={styles.label}>3DS</Text>
<Switch
value={isSecureAuthentication}
onValueChange={setSecureAuthentication}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={isSecureAuthentication ? '#fff' : '#f4f3f4'}
/>
</View>
<View style={styles.toggleContainer}>
<Text style={styles.label}>Billing Visible</Text>
<Switch
value={isBillingVisible}
onValueChange={setBillingVisible}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={isBillingVisible ? '#fff' : '#f4f3f4'}
/>
</View>
<View style={styles.toggleContainer}>
<Text style={styles.label}>Additional Info Visible</Text>
<Switch
value={isAdditionalVisible}
onValueChange={setAdditionalVisible}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={isAdditionalVisible ? '#fff' : '#f4f3f4'}
/>
</View>
<Text style={styles.label}>Payment Methods (Shared)</Text>
<View style={styles.buttonGroup}>
<Button
title="Card"
onPress={() => togglePaymentMethod('card')}
color={androidConfig.paymentMethod.includes('card') ? '#2563EB' : '#ccc'}
/>
<Button
title="ACH"
onPress={() => togglePaymentMethod('ach')}
color={androidConfig.paymentMethod.includes('ach') ? '#2563EB' : '#ccc'}
/>
</View>
{Platform.OS === 'android' && (
<View style={styles.toggleContainer}>
<Text style={styles.label}>Email Editable (Android)</Text>
<Switch
value={emailEditable}
onValueChange={setEmailEditable}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={emailEditable ? '#fff' : '#f4f3f4'}
/>
</View>
)}
{Platform.OS === 'ios' && (
<View style={styles.toggleContainer}>
<Text style={styles.label}>Allow Email (iOS)</Text>
<Switch
value={isEmail}
onValueChange={setIsEmail}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={isEmail ? '#fff' : '#f4f3f4'}
/>
</View>
)}
<Text style={styles.sectionTitle}>Billing Info</Text>
<Text style={styles.label}>Billing Address</Text>
<TextInput
style={styles.input}
value={billingInfo.billing.address}
onChangeText={value => updateBillingInfo('billing', 'address', value)}
/>
<Text style={styles.label}>Billing Country</Text>
<TextInput
style={styles.input}
value={billingInfo.billing.country}
onChangeText={value => updateBillingInfo('billing', 'country', value)}
/>
<Text style={styles.label}>Billing State</Text>
<TextInput
style={styles.input}
value={billingInfo.billing.state}
onChangeText={value => updateBillingInfo('billing', 'state', value)}
/>
<Text style={styles.label}>Billing City</Text>
<TextInput
style={styles.input}
value={billingInfo.billing.city}
onChangeText={value => updateBillingInfo('billing', 'city', value)}
/>
<Text style={styles.label}>Billing Postal Code</Text>
<TextInput
style={styles.input}
value={billingInfo.billing.postal_code}
onChangeText={value => updateBillingInfo('billing', 'postal_code', value)}
/>
<Text style={styles.label}>Billing Required: Address</Text>
<Switch
value={billingInfo.billingRequired.address}
onValueChange={value => updateBillingInfo('billingRequired', 'address', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={billingInfo.billingRequired.address ? '#fff' : '#f4f3f4'}
/>
<Text style={styles.label}>Billing Required: Country</Text>
<Switch
value={billingInfo.billingRequired.country}
onValueChange={value => updateBillingInfo('billingRequired', 'country', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={billingInfo.billingRequired.country ? '#fff' : '#f4f3f4'}
/>
<Text style={styles.label}>Billing Required: State</Text>
<Switch
value={billingInfo.billingRequired.state}
onValueChange={value => updateBillingInfo('billingRequired', 'state', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={billingInfo.billingRequired.state ? '#fff' : '#f4f3f4'}
/>
<Text style={styles.label}>Billing Required: City</Text>
<Switch
value={billingInfo.billingRequired.city}
onValueChange={value => updateBillingInfo('billingRequired', 'city', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={billingInfo.billingRequired.city ? '#fff' : '#f4f3f4'}
/>
<Text style={styles.label}>Billing Required: Postal Code</Text>
<Switch
value={billingInfo.billingRequired.postal_code}
onValueChange={value => updateBillingInfo('billingRequired', 'postal_code', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={billingInfo.billingRequired.postal_code ? '#fff' : '#f4f3f4'}
/>
<Text style={styles.label}>Additional: Name</Text>
<TextInput
style={styles.input}
value={billingInfo.additional.name}
onChangeText={value => updateBillingInfo('additional', 'name', value)}
/>
<Text style={styles.label}>Additional: Email Address</Text>
<TextInput
style={styles.input}
value={billingInfo.additional.email_address}
onChangeText={value => updateBillingInfo('additional', 'email_address', value)}
/>
<Text style={styles.label}>Additional: Phone Number</Text>
<TextInput
style={styles.input}
value={billingInfo.additional.phone_number}
onChangeText={value => updateBillingInfo('additional', 'phone_number', value)}
/>
<Text style={styles.label}>Additional: Description</Text>
<TextInput
style={styles.input}
value={billingInfo.additional.description}
onChangeText={value => updateBillingInfo('additional', 'description', value)}
/>
<Text style={styles.label}>Additional Required: Name</Text>
<Switch
value={billingInfo.additionalRequired.name}
onValueChange={value => updateBillingInfo('additionalRequired', 'name', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={billingInfo.additionalRequired.name ? '#fff' : '#f4f3f4'}
/>
<Text style={styles.label}>Additional Required: Email Address</Text>
<Switch
value={billingInfo.additionalRequired.email_address}
onValueChange={value => updateBillingInfo('additionalRequired', 'email_address', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={billingInfo.additionalRequired.email_address ? '#fff' : '#f4f3f4'}
/>
<Text style={styles.label}>Additional Required: Phone Number</Text>
<Switch
value={billingInfo.additionalRequired.phone_number}
onValueChange={value => updateBillingInfo('additionalRequired', 'phone_number', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={billingInfo.additionalRequired.phone_number ? '#fff' : '#f4f3f4'}
/>
<Text style={styles.label}>Additional Required: Description</Text>
<Switch
value={billingInfo.additionalRequired.description}
onValueChange={value => updateBillingInfo('additionalRequired', 'description', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={billingInfo.additionalRequired.description ? '#fff' : '#f4f3f4'}
/>
{Platform.OS === 'android' && (
<>
<Text style={styles.sectionTitle}>Android Configuration</Text>
<Text style={styles.label}>Currency</Text>
<TextInput
style={styles.input}
value={androidConfig.currency}
onChangeText={value => updateAndroidConfig('currency', value)}
/>
<Text style={styles.label}>Save Card</Text>
<Switch
value={androidConfig.saveCard}
onValueChange={value => updateAndroidConfig('saveCard', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={androidConfig.saveCard ? '#fff' : '#f4f3f4'}
/>
<Text style={styles.label}>Save Account</Text>
<Switch
value={androidConfig.saveAccount}
onValueChange={value => updateAndroidConfig('saveAccount', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={androidConfig.saveAccount ? '#fff' : '#f4f3f4'}
/>
<Text style={styles.label}>Show Receipt</Text>
<Switch
value={androidConfig.showReceipt}
onValueChange={value => updateAndroidConfig('showReceipt', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={androidConfig.showReceipt ? '#fff' : '#f4f3f4'}
/>
<Text style={styles.label}>Show Donate</Text>
<Switch
value={androidConfig.showDonate}
onValueChange={value => updateAndroidConfig('showDonate', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={androidConfig.showDonate ? '#fff' : '#f4f3f4'}
/>
<Text style={styles.label}>Show Total</Text>
<Switch
value={androidConfig.showTotal}
onValueChange={value => updateAndroidConfig('showTotal', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={androidConfig.showTotal ? '#fff' : '#f4f3f4'}
/>
<Text style={styles.label}>Show Submit Button</Text>
<Switch
value={androidConfig.showSubmitButton}
onValueChange={value => updateAndroidConfig('showSubmitButton', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={androidConfig.showSubmitButton ? '#fff' : '#f4f3f4'}
/>
<Text style={styles.label}>Name</Text>
<TextInput
style={styles.input}
value={androidConfig.name}
onChangeText={value => updateAndroidConfig('name', value)}
/>
<Text style={styles.sectionTitle}>Android Fields</Text>
{androidConfig.fields.billing.map((field, index) => (
<View key={`billing-${index}`}>
<Text style={styles.label}>{`Billing ${field.name}`}</Text>
<TextInput
style={styles.input}
value={field.value}
onChangeText={value => updateAndroidConfigFields('billing', index, 'value', value)}
/>
<Text style={styles.label}>{`Billing ${field.name} Required`}</Text>
<Switch
value={field.required}
onValueChange={value => updateAndroidConfigFields('billing', index, 'required', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={field.required ? '#fff' : '#f4f3f4'}
/>
</View>
))}
{androidConfig.fields.additional.map((field, index) => (
<View key={`additional-${index}`}>
<Text style={styles.label}>{`Additional ${field.name}`}</Text>
<TextInput
style={styles.input}
value={field.value}
onChangeText={value => updateAndroidConfigFields('additional', index, 'value', value)}
/>
<Text style={styles.label}>{`Additional ${field.name} Required`}</Text>
<Switch
value={field.required}
onValueChange={value => updateAndroidConfigFields('additional', index, 'required', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={field.required ? '#fff' : '#f4f3f4'}
/>
</View>
))}
<Text style={styles.sectionTitle}>Android Appearance Settings</Text>
<Text style={styles.label}>Theme</Text>
<TextInput
style={styles.input}
value={androidConfig.appearanceSettings.theme}
onChangeText={value => updateAndroidConfigAppearanceSettings('theme', value)}
/>
<Text style={styles.label}>Body Background Color</Text>
<TextInput
style={styles.input}
value={androidConfig.appearanceSettings.bodyBackgroundColor}
onChangeText={value => updateAndroidConfigAppearanceSettings('bodyBackgroundColor', value)}
/>
<Text style={styles.label}>Container Background Color</Text>
<TextInput
style={styles.input}
value={androidConfig.appearanceSettings.containerBackgroundColor}
onChangeText={value => updateAndroidConfigAppearanceSettings('containerBackgroundColor', value)}
/>
<Text style={styles.label}>Primary Font Color</Text>
<TextInput
style={styles.input}
value={androidConfig.appearanceSettings.primaryFontColor}
onChangeText={value => updateAndroidConfigAppearanceSettings('primaryFontColor', value)}
/>
<Text style={styles.label}>Secondary Font Color</Text>
<TextInput
style={styles.input}
value={androidConfig.appearanceSettings.secondaryFontColor}
onChangeText={value => updateAndroidConfigAppearanceSettings('secondaryFontColor', value)}
/>
<Text style={styles.label}>Primary Button Background Color</Text>
<TextInput
style={styles.input}
value={androidConfig.appearanceSettings.primaryButtonBackgroundColor}
onChangeText={value => updateAndroidConfigAppearanceSettings('primaryButtonBackgroundColor', value)}
/>
<Text style={styles.label}>Primary Button Hover Color</Text>
<TextInput
style={styles.input}
value={androidConfig.appearanceSettings.primaryButtonHoverColor}
onChangeText={value => updateAndroidConfigAppearanceSettings('primaryButtonHoverColor', value)}
/>
<Text style={styles.label}>Primary Button Font Color</Text>
<TextInput
style={styles.input}
value={androidConfig.appearanceSettings.primaryButtonFontColor}
onChangeText={value => updateAndroidConfigAppearanceSettings('primaryButtonFontColor', value)}
/>
<Text style={styles.label}>Secondary Button Background Color</Text>
<TextInput
style={styles.input}
value={androidConfig.appearanceSettings.secondaryButtonBackgroundColor}
onChangeText={value => updateAndroidConfigAppearanceSettings('secondaryButtonBackgroundColor', value)}
/>
<Text style={styles.label}>Secondary Button Hover Color</Text>
<TextInput
style={styles.input}
value={androidConfig.appearanceSettings.secondaryButtonHoverColor}
onChangeText={value => updateAndroidConfigAppearanceSettings('secondaryButtonHoverColor', value)}
/>
<Text style={styles.label}>Secondary Button Font Color</Text>
<TextInput
style={styles.input}
value={androidConfig.appearanceSettings.secondaryButtonFontColor}
onChangeText={value => updateAndroidConfigAppearanceSettings('secondaryButtonFontColor', value)}
/>
<Text style={styles.label}>Border Radius</Text>
<TextInput
style={styles.input}
value={androidConfig.appearanceSettings.borderRadius}
onChangeText={value => updateAndroidConfigAppearanceSettings('borderRadius', value)}
/>
<Text style={styles.label}>Font Size</Text>
<TextInput
style={styles.input}
value={androidConfig.appearanceSettings.fontSize}
onChangeText={value => updateAndroidConfigAppearanceSettings('fontSize', value)}
/>
<Text style={styles.label}>Font Weight</Text>
<TextInput
style={styles.input}
value={String(androidConfig.appearanceSettings.fontWeight)}
onChangeText={value => updateAndroidConfigAppearanceSettings('fontWeight', parseInt(value) || '500')}
/>
<Text style={styles.label}>Font Family</Text>
<TextInput
style={styles.input}
value={androidConfig.appearanceSettings.fontFamily}
onChangeText={value => updateAndroidConfigAppearanceSettings('fontFamily', value)}
/>
</>
)}
{Platform.OS === 'ios' && (
<>
<Text style={styles.sectionTitle}>Theme Configuration (iOS)</Text>
<Text style={styles.label}>Body Background Color</Text>
<TextInput
style={styles.input}
value={themeConfiguration.bodyBackgroundColor}
onChangeText={value => updateThemeConfiguration('bodyBackgroundColor', value)}
/>
<Text style={styles.label}>Container Background Color</Text>
<TextInput
style={styles.input}
value={themeConfiguration.containerBackgroundColor}
onChangeText={value => updateThemeConfiguration('containerBackgroundColor', value)}
/>
<Text style={styles.label}>Primary Font Color</Text>
<TextInput
style={styles.input}
value={themeConfiguration.primaryFontColor}
onChangeText={value => updateThemeConfiguration('primaryFontColor', value)}
/>
<Text style={styles.label}>Secondary Font Color</Text>
<TextInput
style={styles.input}
value={themeConfiguration.secondaryFontColor}
onChangeText={value => updateThemeConfiguration('secondaryFontColor', value)}
/>
<Text style={styles.label}>Primary Button Background Color</Text>
<TextInput
style={styles.input}
value={themeConfiguration.primaryButtonBackgroundColor}
onChangeText={value => updateThemeConfiguration('primaryButtonBackgroundColor', value)}
/>
<Text style={styles.label}>Primary Button Hover Color</Text>
<TextInput
style={styles.input}
value={themeConfiguration.primaryButtonHoverColor}
onChangeText={value => updateThemeConfiguration('primaryButtonHoverColor', value)}
/>
<Text style={styles.label}>Primary Button Font Color</Text>
<TextInput
style={styles.input}
value={themeConfiguration.primaryButtonFontColor}
onChangeText={value => updateThemeConfiguration('primaryButtonFontColor', value)}
/>
<Text style={styles.label}>Secondary Button Background Color</Text>
<TextInput
style={styles.input}
value={themeConfiguration.secondaryButtonBackgroundColor}
onChangeText={value => updateThemeConfiguration('secondaryButtonBackgroundColor', value)}
/>
<Text style={styles.label}>Secondary Button Hover Color</Text>
<TextInput
style={styles.input}
value={themeConfiguration.secondaryButtonHoverColor}
onChangeText={value => updateThemeConfiguration('secondaryButtonHoverColor', value)}
/>
<Text style={styles.label}>Secondary Button Font Color</Text>
<TextInput
style={styles.input}
value={themeConfiguration.secondaryButtonFontColor}
onChangeText={value => updateThemeConfiguration('secondaryButtonFontColor', value)}
/>
<Text style={styles.label}>Border Radius</Text>
<TextInput
style={styles.input}
value={themeConfiguration.borderRadius}
onChangeText={value => updateThemeConfiguration('borderRadius', value)}
/>
<Text style={styles.label}>Font Size</Text>
<TextInput
style={styles.input}
value={themeConfiguration.fontSize}
onChangeText={value => updateThemeConfiguration('fontSize', value)}
/>
<Text style={styles.label}>Font Weight</Text>
<TextInput
style={styles.input}
value={String(themeConfiguration.fontWeight)}
onChangeText={value => updateThemeConfiguration('fontWeight', parseInt(value) || 500)}
/>
<Text style={styles.label}>Font Family</Text>
<TextInput
style={styles.input}
value={themeConfiguration.fontFamily}
onChangeText={value => updateThemeConfiguration('fontFamily', value)}
/>
</>
)}
<Text style={styles.sectionTitle}>GrailPay Parameters</Text>
<Text style={styles.label}>Role</Text>
<TextInput
style={styles.input}
value={grailPayParams.role}
onChangeText={value => updateGrailPayParams('role', value)}
/>
<Text style={styles.label}>Timeout</Text>
<TextInput
style={styles.input}
value={String(grailPayParams.timeout)}
onChangeText={value => updateGrailPayParams('timeout', parseInt(value) || 10)}
/>
<Text style={styles.label}>Is Sandbox</Text>
<Switch
value={grailPayParams.isSandbox}
onValueChange={value => updateGrailPayParams('isSandbox', value)}
trackColor={{ false: '#ccc', true: '#2563EB' }}
thumbColor={grailPayParams.isSandbox ? '#fff' : '#f4f3f4'}
/>
<Text style={styles.label}>Branding Name</Text>
<TextInput
style={styles.input}
value={grailPayParams.brandingName}
onChangeText={value => updateGrailPayParams('brandingName', value)}
/>
<Text style={styles.label}>Finder Subtitle</Text>
<TextInput
style={styles.input}
value={grailPayParams.finderSubtitle}
onChangeText={value => updateGrailPayParams('finderSubtitle', value)}
/>
<Text style={styles.label}>Search Placeholder</Text>
<TextInput
style={styles.input}
value={grailPayParams.searchPlaceholder}
onChangeText={value => updateGrailPayParams('searchPlaceholder', value)}
/>
<Text style={styles.sectionTitle}>Recurring Data</Text>
<Text style={styles.label}>Allow Cycles</Text>
<TextInput
style={styles.input}
value={String(recurringData.allowCycles)}
onChangeText={value => updateRecurringData('allowCycles', parseInt(value) || 2)}
/>
<Text style={styles.label}>Intervals</Text>
<View style={styles.buttonGroup}>
<Button
title="Weekly"
onPress={() => toggleInterval('weekly')}
color={recurringData.intervals.includes('weekly') ? '#2563EB' : '#ccc'}
/>
<Button
title="Monthly"
onPress={() => toggleInterval('monthly')}
color={recurringData.intervals.includes('monthly') ? '#2563EB' : '#ccc'}
/>
</View>
<Text style={styles.label}>Recurring Start Type</Text>
<TextInput
style={styles.input}
value={recurringData.recurringStartType}
onChangeText={value => updateRecurringData('recurringStartType', value)}
/>
<Text style={styles.label}>Recurring Start Date</Text>
<TextInput
style={styles.input}
value={recurringData.recurringStartDate}
onChangeText={value => updateRecurringData('recurringStartDate', value)}
/>
<View style={styles.buttonGroup}>
<Button title="Pay" onPress={handlePayment} />
{Platform.OS === 'ios' ? (
<Button title="Payment Ref" onPress={handlePaymentReference} disabled={loading} />
) : (
<></>
)
}
</View>
<Text selectable style={styles.result}>{result}</Text>
</ScrollView>
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#F9FAFB' },
scrollContent: { flexGrow: 1, padding: 20 },
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 16,
marginTop: 50,
textAlign: 'center',
},
sectionTitle: {
fontSize: 20,
fontWeight: 'bold',
marginTop: 20,
marginBottom: 10,
},
input: {
height: 40,
borderColor: '#ccc',
borderWidth: 1,
borderRadius: 5,
paddingHorizontal: 10,
marginBottom: 10,
},
pickerContainer: {
marginBottom: 20,
},
toggleContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 10,
},
label: {
fontSize: 16,
fontWeight: '500',
marginBottom: 5,
},
buttonGroup: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 20,
marginTop: 20,
gap: 10,
},
result: {
fontSize: 14,
fontFamily: 'monospace',
color: '#333',
marginTop: 20,
},
environmentButton: {
flex: 1,
paddingVertical: 10,
paddingHorizontal: 20,
borderRadius: 5,
marginHorizontal: 5,
alignItems: 'center',
justifyContent: 'center',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '500',
},
});
You can send null
if billing info not available.