@jimrising/easymerchantsdk-react-native 2.0.7 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +248 -1801
- package/android/build/.transforms/15b6a8a60a6b32d0dcaf609723cf365b/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/.transforms/8508f1428f740032c45a43f48b1bbe1e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$1$1.dex +0 -0
- package/android/build/.transforms/8508f1428f740032c45a43f48b1bbe1e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$1.dex +0 -0
- package/android/build/.transforms/8508f1428f740032c45a43f48b1bbe1e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$2.dex +0 -0
- package/android/build/.transforms/8508f1428f740032c45a43f48b1bbe1e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule.dex +0 -0
- package/android/build/.transforms/eef2d06269ef2e204b4f065513034fca/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/.transforms/ffabb40f9809b32eb9546ce76fc51764/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/reactlibrary/RNEasymerchantsdkModule$1$1.dex +0 -0
- package/android/build/.transforms/ffabb40f9809b32eb9546ce76fc51764/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/reactlibrary/RNEasymerchantsdkModule$1.dex +0 -0
- package/android/build/.transforms/ffabb40f9809b32eb9546ce76fc51764/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/reactlibrary/RNEasymerchantsdkModule$2.dex +0 -0
- package/android/build/.transforms/ffabb40f9809b32eb9546ce76fc51764/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/reactlibrary/RNEasymerchantsdkModule.dex +0 -0
- package/android/build/intermediates/aar_main_jar/release/syncReleaseLibJars/classes.jar +0 -0
- package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/compile_library_classes_jar/release/bundleLibCompileToJarRelease/classes.jar +0 -0
- package/android/build/intermediates/full_jar/release/createFullJarRelease/full.jar +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
- package/android/build/intermediates/incremental/lintVitalAnalyzeRelease/release-artifact-dependencies.xml +4 -4
- package/android/build/intermediates/incremental/lintVitalAnalyzeRelease/release-artifact-libraries.xml +4 -4
- package/android/build/intermediates/incremental/release/mergeReleaseResources/compile-file-map.properties +838 -838
- package/android/build/intermediates/incremental/release/mergeReleaseResources/merger.xml +3 -3
- package/android/build/intermediates/incremental/release/packageReleaseResources/compile-file-map.properties +1 -1
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
- package/android/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
- package/android/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
- package/android/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
- package/android/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
- package/android/build/intermediates/lint-cache/lintVitalAnalyzeRelease/maven.google/androidx/core/group-index.xml +4 -3
- package/android/build/intermediates/lint-cache/lintVitalAnalyzeRelease/maven.google/androidx/lifecycle/group-index.xml +165 -165
- package/android/build/intermediates/lint-cache/lintVitalAnalyzeRelease/maven.google/com/android/tools/build/group-index.xml +15 -15
- package/android/build/intermediates/lint-cache/lintVitalAnalyzeRelease/sdk_index/snapshot.gz +0 -0
- package/android/build/intermediates/lint_model/release/generateReleaseLintModel/release-artifact-dependencies.xml +4 -4
- package/android/build/intermediates/lint_model/release/generateReleaseLintModel/release-artifact-libraries.xml +4 -4
- package/android/build/intermediates/lint_vital_lint_model/release/generateReleaseLintVitalModel/release-artifact-dependencies.xml +4 -4
- package/android/build/intermediates/lint_vital_lint_model/release/generateReleaseLintVitalModel/release-artifact-libraries.xml +4 -4
- package/android/build/intermediates/local_aar_for_lint/release/out.aar +0 -0
- package/android/build/intermediates/merged_res/release/mergeReleaseResources/layout/activity_card_addition_ifo.xml +1 -0
- package/android/build/intermediates/merged_res/release/mergeReleaseResources/layout/activity_card_billing_info.xml +1 -0
- package/android/build/intermediates/merged_res/release/mergeReleaseResources/layout/activity_card_scan.xml +1 -0
- package/android/build/intermediates/merged_res/release/mergeReleaseResources/layout/activity_payment_done.xml +1 -0
- package/android/build/intermediates/merged_res/release/mergeReleaseResources/layout/activity_payment_done_url.xml +1 -0
- package/android/build/intermediates/merged_res/release/mergeReleaseResources/layout/activity_payment_error.xml +1 -0
- package/android/build/intermediates/merged_res/release/mergeReleaseResources/layout/activity_select_payment_method.xml +1 -0
- package/android/build/intermediates/merged_res/release/mergeReleaseResources/layout/activity_verify_email.xml +1 -0
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/multi-v2/values-night-v8.json +19 -19
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/multi-v2/values.json +38 -38
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/anim-v21.json +9 -9
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/anim.json +24 -24
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/animator-v21.json +1 -1
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/animator.json +34 -34
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/color-night-v8.json +3 -3
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/color-v31.json +10 -10
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/color.json +153 -153
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-hdpi-v4.json +1 -1
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-mdpi-v4.json +1 -1
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-v21.json +3 -3
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-v23.json +7 -7
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-xhdpi-v4.json +1 -1
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-xxhdpi-v4.json +1 -1
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-xxxhdpi-v4.json +1 -1
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable.json +391 -391
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/font.json +9 -9
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/interpolator-v21.json +10 -10
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/interpolator.json +11 -11
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/layout-land.json +3 -3
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/layout-sw600dp-v13.json +2 -2
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/layout-v26.json +1 -1
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/layout.json +98 -98
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-anydpi-v26.json +2 -2
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-hdpi-v4.json +2 -2
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-mdpi-v4.json +2 -2
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-xhdpi-v4.json +2 -2
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-xxhdpi-v4.json +2 -2
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-xxxhdpi-v4.json +2 -2
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/raw.json +46 -46
- package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/xml.json +3 -3
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
- package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/runtime_library_classes_jar/release/bundleLibRuntimeToJarRelease/classes.jar +0 -0
- package/android/build/intermediates/source_set_path_map/release/mapReleaseSourceSetPaths/file-map.txt +24 -24
- package/android/build/intermediates/verified_library_resources/release/verifyReleaseResources/compiled/layout_activity_card_addition_ifo.xml.flat +0 -0
- package/android/build/intermediates/verified_library_resources/release/verifyReleaseResources/compiled/layout_activity_card_billing_info.xml.flat +0 -0
- package/android/build/intermediates/verified_library_resources/release/verifyReleaseResources/compiled/layout_activity_card_scan.xml.flat +0 -0
- package/android/build/intermediates/verified_library_resources/release/verifyReleaseResources/compiled/layout_activity_payment_done.xml.flat +0 -0
- package/android/build/intermediates/verified_library_resources/release/verifyReleaseResources/compiled/layout_activity_payment_done_url.xml.flat +0 -0
- package/android/build/intermediates/verified_library_resources/release/verifyReleaseResources/compiled/layout_activity_payment_error.xml.flat +0 -0
- package/android/build/intermediates/verified_library_resources/release/verifyReleaseResources/compiled/layout_activity_select_payment_method.xml.flat +0 -0
- package/android/build/intermediates/verified_library_resources/release/verifyReleaseResources/compiled/layout_activity_verify_email.xml.flat +0 -0
- package/android/build/outputs/aar/jimrising_easymerchantsdk-react-native-release.aar +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkModule$1$1.class.uniqueId1 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkModule$1.class.uniqueId0 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkModule$2.class.uniqueId3 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkModule.class.uniqueId2 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkPackage.class.uniqueId4 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
- package/android/build/tmp/compileReleaseJavaWithJavac/previous-compilation-data.bin +0 -0
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/reactlibrary/RNEasymerchantsdkModule.java +93 -26
- package/ios/Classes/EasyMerchantSdk.m +6 -6
- package/ios/Classes/EasyMerchantSdk.swift +116 -60
- package/ios/Classes/EasyPayViewController.swift +3 -5
- package/ios/CustomComponents/CheckboxButton.swift +66 -0
- package/ios/Helper/GrailPayHelper.swift +1 -0
- package/ios/Models/Request.swift +8 -8
- package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +233 -97
- package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +376 -104
- package/ios/Pods/ViewControllers/OTPVerificationVC.swift +28 -27
- package/ios/Pods/ViewControllers/PaymentErrorVC.swift +22 -25
- package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +575 -190
- package/ios/easymerchantsdk.podspec +1 -1
- package/ios/easymerchantsdk.storyboard +15 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,41 +1,62 @@
|
|
|
1
1
|
# EasyMerchantSdk React Native Implementation
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This guide provides step-by-step instructions to integrate the EasyMerchantSdk into your React Native application for both Android and iOS platforms. The SDK supports identical parameters for both platforms, with differences only in the method calls for initiating payments and handling responses.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Node.js and npm installed
|
|
8
|
+
- React Native development environment set up
|
|
9
|
+
- Ruby 3.2.8 (for iOS setup)
|
|
10
|
+
- Xcode (for iOS development)
|
|
11
|
+
- Android Studio (for Android development)
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### 1. Add the SDK to Your Project
|
|
16
|
+
|
|
17
|
+
Add the EasyMerchantSdk to your project by including it in your `package.json` file under the `dependencies` section:
|
|
7
18
|
|
|
8
19
|
```json
|
|
9
20
|
"dependencies": {
|
|
10
|
-
"@jimrising/easymerchantsdk-react-native": "^2.0
|
|
11
|
-
}
|
|
21
|
+
"@jimrising/easymerchantsdk-react-native": "^2.1.0"
|
|
22
|
+
}
|
|
12
23
|
```
|
|
13
24
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
25
|
+
Alternatively, install it using the following command:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @jimrising/easymerchantsdk-react-native
|
|
17
29
|
```
|
|
18
30
|
|
|
19
|
-
|
|
20
|
-
|
|
31
|
+
### 2. Android Configuration
|
|
32
|
+
|
|
33
|
+
1. Open the `android/build.gradle` file in your project.
|
|
34
|
+
2. Add the following code to the `allprojects.repositories` section to ensure the SDK can access required dependencies:
|
|
35
|
+
|
|
21
36
|
```gradle
|
|
22
37
|
allprojects {
|
|
23
38
|
repositories {
|
|
24
39
|
google()
|
|
25
40
|
mavenCentral()
|
|
26
|
-
|
|
41
|
+
maven { url 'https://jitpack.io' }
|
|
42
|
+
maven {
|
|
43
|
+
url = uri(properties.getProperty('GITHUB_URL'))
|
|
44
|
+
credentials {
|
|
45
|
+
username = properties.getProperty('GITHUB_USERNAME')
|
|
46
|
+
password = properties.getProperty('GITHUB_PASSWORD')
|
|
47
|
+
}
|
|
48
|
+
}
|
|
27
49
|
}
|
|
28
50
|
}
|
|
29
51
|
```
|
|
30
52
|
|
|
31
|
-
|
|
32
|
-
Add below content inside the AppDelegate.swift File :-
|
|
53
|
+
3. Sync your project with Gradle to apply the changes.
|
|
33
54
|
|
|
34
|
-
|
|
35
|
-
- Ruby 3.2.8
|
|
55
|
+
### 3. iOS Configuration
|
|
36
56
|
|
|
57
|
+
1. **Update AppDelegate.swift**
|
|
58
|
+
Create or modify the `AppDelegate.swift` file in your iOS project to include the following code:
|
|
37
59
|
|
|
38
|
-
Create a new file named AppDelegate.swift
|
|
39
60
|
```swift
|
|
40
61
|
import UIKit
|
|
41
62
|
import easymerchantsdk
|
|
@@ -43,7 +64,7 @@ import React
|
|
|
43
64
|
|
|
44
65
|
@UIApplicationMain
|
|
45
66
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
46
|
-
var window: UIWindow?
|
|
67
|
+
var window: UIWindow?
|
|
47
68
|
|
|
48
69
|
func application(
|
|
49
70
|
_ application: UIApplication,
|
|
@@ -69,7 +90,7 @@ var window: UIWindow?
|
|
|
69
90
|
|
|
70
91
|
let rootView = RCTRootView(
|
|
71
92
|
bridge: validBridge,
|
|
72
|
-
moduleName: "EasyMerchantTestApp",
|
|
93
|
+
moduleName: "EasyMerchantTestApp", // Replace with your app name
|
|
73
94
|
initialProperties: nil
|
|
74
95
|
)
|
|
75
96
|
|
|
@@ -78,8 +99,8 @@ var window: UIWindow?
|
|
|
78
99
|
rootViewController.view = rootView
|
|
79
100
|
self.window?.rootViewController = rootViewController
|
|
80
101
|
self.window?.makeKeyAndVisible()
|
|
81
|
-
|
|
82
|
-
|
|
102
|
+
|
|
103
|
+
if let easyMerchantSdkPlugin = bridge?.module(for: EasyMerchantSdkPlugin.self) as? EasyMerchantSdkPlugin {
|
|
83
104
|
easyMerchantSdkPlugin.setViewController(rootViewController)
|
|
84
105
|
} else {
|
|
85
106
|
print("Failed to retrieve EasyMerchantSdkPlugin instance from React Native bridge.")
|
|
@@ -89,154 +110,45 @@ var window: UIWindow?
|
|
|
89
110
|
}
|
|
90
111
|
```
|
|
91
112
|
|
|
92
|
-
|
|
93
|
-
|
|
113
|
+
2. **Update Podfile**
|
|
114
|
+
Open your `ios/Podfile` and add the following to include the EasyMerchantSdk pod:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
require_relative '../node_modules/react-native/scripts/react_native_pods'
|
|
118
|
+
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
|
|
119
|
+
|
|
120
|
+
platform :ios, '16.0'
|
|
94
121
|
|
|
95
|
-
|
|
96
|
-
|
|
122
|
+
pod 'easymerchantsdk', :path => '../node_modules/easymerchantsdk-react-native/ios'
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
3. Run `pod install` in the `ios` directory to install the dependencies:
|
|
97
126
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
127
|
+
```bash
|
|
128
|
+
cd ios
|
|
129
|
+
pod install
|
|
101
130
|
```
|
|
102
131
|
|
|
132
|
+
## Usage in Your React Native App
|
|
103
133
|
|
|
134
|
+
To integrate the EasyMerchantSdk into your React Native application, you can use the provided `App.js` as a reference. The parameters for Android and iOS are identical, but the method calls differ slightly.
|
|
104
135
|
|
|
105
|
-
|
|
106
|
-
you can call the sdk using below example:
|
|
136
|
+
### 1. Import Necessary Modules
|
|
107
137
|
|
|
138
|
+
Ensure you import the required React Native components and the EasyMerchantSdk NativeModules:
|
|
108
139
|
|
|
109
140
|
```javascript
|
|
110
|
-
|
|
111
141
|
import React, { useState, useEffect } from 'react';
|
|
112
|
-
import {
|
|
113
|
-
StyleSheet,
|
|
114
|
-
Text,
|
|
115
|
-
View,
|
|
116
|
-
TextInput,
|
|
117
|
-
Button,
|
|
118
|
-
Alert,
|
|
119
|
-
ScrollView,
|
|
120
|
-
Platform,
|
|
121
|
-
NativeModules,
|
|
122
|
-
Switch,
|
|
123
|
-
TouchableOpacity,
|
|
124
|
-
NativeEventEmitter,
|
|
125
|
-
KeyboardAvoidingView,
|
|
126
|
-
Keyboard,
|
|
127
|
-
TouchableWithoutFeedback,
|
|
128
|
-
Modal,
|
|
129
|
-
} from 'react-native';
|
|
142
|
+
import { StyleSheet, Text, View, TextInput, Button, Alert, ScrollView, Platform, NativeModules, Switch, TouchableOpacity, NativeEventEmitter, KeyboardAvoidingView, Keyboard, TouchableWithoutFeedback, Modal } from 'react-native';
|
|
130
143
|
|
|
131
144
|
const { RNEasymerchantsdk, EasyMerchantSdk } = NativeModules;
|
|
145
|
+
```
|
|
132
146
|
|
|
133
|
-
|
|
134
|
-
const Dropdown = ({ value, onValueChange, options, placeholder }) => {
|
|
135
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
136
|
-
|
|
137
|
-
// Handler to close modal when tapping outside
|
|
138
|
-
const handleClose = () => setIsOpen(false);
|
|
139
|
-
|
|
140
|
-
return (
|
|
141
|
-
<View style={styles.dropdownContainer} pointerEvents="box-none">
|
|
142
|
-
<TouchableOpacity
|
|
143
|
-
style={styles.dropdownButton}
|
|
144
|
-
onPress={() => setIsOpen(!isOpen)}
|
|
145
|
-
>
|
|
146
|
-
<Text style={styles.dropdownButtonText}>
|
|
147
|
-
{value || placeholder}
|
|
148
|
-
</Text>
|
|
149
|
-
<Text style={styles.dropdownArrow}>
|
|
150
|
-
{isOpen ? '▲' : '▼'}
|
|
151
|
-
</Text>
|
|
152
|
-
</TouchableOpacity>
|
|
153
|
-
{Platform.OS === 'android' ? (
|
|
154
|
-
<Modal
|
|
155
|
-
visible={isOpen}
|
|
156
|
-
transparent={true}
|
|
157
|
-
animationType="fade"
|
|
158
|
-
onRequestClose={handleClose}
|
|
159
|
-
>
|
|
160
|
-
<TouchableWithoutFeedback onPress={handleClose}>
|
|
161
|
-
<View style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.2)' }}>
|
|
162
|
-
<View style={[styles.dropdownOptions, { position: 'absolute', top: '30%', left: 20, right: 20, maxHeight: 300, elevation: 20 }]}>
|
|
163
|
-
<ScrollView
|
|
164
|
-
style={[styles.dropdownScrollView, { maxHeight: 280 }]}
|
|
165
|
-
showsVerticalScrollIndicator={true}
|
|
166
|
-
persistentScrollbar={true}
|
|
167
|
-
nestedScrollEnabled={true}
|
|
168
|
-
keyboardShouldPersistTaps="handled"
|
|
169
|
-
>
|
|
170
|
-
{options.map((option, index) => (
|
|
171
|
-
<TouchableOpacity
|
|
172
|
-
key={index}
|
|
173
|
-
style={styles.dropdownOption}
|
|
174
|
-
onPress={() => {
|
|
175
|
-
onValueChange(option);
|
|
176
|
-
setIsOpen(false);
|
|
177
|
-
}}
|
|
178
|
-
>
|
|
179
|
-
<Text style={styles.dropdownOptionText}>{option}</Text>
|
|
180
|
-
</TouchableOpacity>
|
|
181
|
-
))}
|
|
182
|
-
</ScrollView>
|
|
183
|
-
</View>
|
|
184
|
-
</View>
|
|
185
|
-
</TouchableWithoutFeedback>
|
|
186
|
-
</Modal>
|
|
187
|
-
) : (
|
|
188
|
-
isOpen && (
|
|
189
|
-
<View style={styles.dropdownOptions}>
|
|
190
|
-
<ScrollView
|
|
191
|
-
style={styles.dropdownScrollView}
|
|
192
|
-
showsVerticalScrollIndicator={true}
|
|
193
|
-
persistentScrollbar={true}
|
|
194
|
-
nestedScrollEnabled={true}
|
|
195
|
-
keyboardShouldPersistTaps="handled"
|
|
196
|
-
>
|
|
197
|
-
{options.map((option, index) => (
|
|
198
|
-
<TouchableOpacity
|
|
199
|
-
key={index}
|
|
200
|
-
style={styles.dropdownOption}
|
|
201
|
-
onPress={() => {
|
|
202
|
-
onValueChange(option);
|
|
203
|
-
setIsOpen(false);
|
|
204
|
-
}}
|
|
205
|
-
>
|
|
206
|
-
<Text style={styles.dropdownOptionText}>{option}</Text>
|
|
207
|
-
</TouchableOpacity>
|
|
208
|
-
))}
|
|
209
|
-
</ScrollView>
|
|
210
|
-
</View>
|
|
211
|
-
)
|
|
212
|
-
)}
|
|
213
|
-
</View>
|
|
214
|
-
);
|
|
215
|
-
};
|
|
147
|
+
### 2. Set Up Configuration
|
|
216
148
|
|
|
217
|
-
|
|
218
|
-
const FilledButton = ({ title, onPress, disabled = false, style = {}, textStyle = {} }) => {
|
|
219
|
-
return (
|
|
220
|
-
<TouchableOpacity
|
|
221
|
-
style={[
|
|
222
|
-
styles.filledButton,
|
|
223
|
-
disabled && styles.filledButtonDisabled,
|
|
224
|
-
style,
|
|
225
|
-
]}
|
|
226
|
-
onPress={onPress}
|
|
227
|
-
disabled={disabled}
|
|
228
|
-
>
|
|
229
|
-
<Text style={[
|
|
230
|
-
styles.filledButtonText,
|
|
231
|
-
disabled && styles.filledButtonTextDisabled,
|
|
232
|
-
textStyle,
|
|
233
|
-
]}>
|
|
234
|
-
{title}
|
|
235
|
-
</Text>
|
|
236
|
-
</TouchableOpacity>
|
|
237
|
-
);
|
|
238
|
-
};
|
|
149
|
+
Define the configuration object for the SDK, which is shared between Android and iOS. The provided `App.js` includes a comprehensive `externalConfig` object that you can customize:
|
|
239
150
|
|
|
151
|
+
```javascript
|
|
240
152
|
const externalConfig = {
|
|
241
153
|
amount: '',
|
|
242
154
|
email: '',
|
|
@@ -250,11 +162,11 @@ const externalConfig = {
|
|
|
250
162
|
billingInfo: {
|
|
251
163
|
visibility: { billing: false, additional: false },
|
|
252
164
|
billing: {
|
|
253
|
-
address: '
|
|
254
|
-
country: '
|
|
255
|
-
state: '
|
|
256
|
-
city: '
|
|
257
|
-
postal_code: '
|
|
165
|
+
address: 'Address',
|
|
166
|
+
country: 'Country',
|
|
167
|
+
state: 'State',
|
|
168
|
+
city: 'City',
|
|
169
|
+
postal_code: '000000',
|
|
258
170
|
},
|
|
259
171
|
billingRequired: {
|
|
260
172
|
address: true,
|
|
@@ -276,20 +188,20 @@ const externalConfig = {
|
|
|
276
188
|
description: false,
|
|
277
189
|
},
|
|
278
190
|
},
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
191
|
+
themeConfiguration: {
|
|
192
|
+
bodyBackgroundColor: '#1E3A8A',
|
|
193
|
+
containerBackgroundColor: '#1E40AF',
|
|
194
|
+
primaryFontColor: '#FFFFFF',
|
|
195
|
+
secondaryFontColor: '#BFDBFE',
|
|
196
|
+
primaryButtonBackgroundColor: '#3B82F6',
|
|
197
|
+
primaryButtonHoverColor: '#2563EB',
|
|
198
|
+
primaryButtonFontColor: '#FFFFFF',
|
|
199
|
+
secondaryButtonBackgroundColor: '#1D4ED8',
|
|
200
|
+
secondaryButtonHoverColor: '#1E40AF',
|
|
201
|
+
secondaryButtonFontColor: '#FFFFFF',
|
|
202
|
+
borderRadius: '8',
|
|
203
|
+
fontSize: '14',
|
|
204
|
+
},
|
|
293
205
|
grailPayParams: {
|
|
294
206
|
role: 'business',
|
|
295
207
|
timeout: 10,
|
|
@@ -301,10 +213,10 @@ const externalConfig = {
|
|
|
301
213
|
recurringData: {
|
|
302
214
|
allowCycles: 2,
|
|
303
215
|
intervals: ['daily', 'weekly', 'monthly'],
|
|
304
|
-
|
|
216
|
+
recurringStartType: Platform.OS === 'android' ? 'Custom' : 'custom',
|
|
305
217
|
recurringStartDate: new Date().toLocaleDateString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' }),
|
|
306
218
|
},
|
|
307
|
-
|
|
219
|
+
configur: {
|
|
308
220
|
currency: 'usd',
|
|
309
221
|
saveCard: true,
|
|
310
222
|
saveAccount: true,
|
|
@@ -349,139 +261,173 @@ const externalConfig = {
|
|
|
349
261
|
},
|
|
350
262
|
},
|
|
351
263
|
};
|
|
264
|
+
```
|
|
352
265
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
266
|
+
### 3. Handle Payments
|
|
267
|
+
|
|
268
|
+
The `handlePayment` function validates inputs and calls platform-specific billing functions. The parameters are identical for both platforms, but the method calls differ.
|
|
269
|
+
|
|
270
|
+
#### Android: `handleAndroidBilling`
|
|
271
|
+
|
|
272
|
+
```javascript
|
|
273
|
+
const handleAndroidBilling = async () => {
|
|
274
|
+
const { apiKey, secretKey } = apiKeys[environment];
|
|
275
|
+
const selectedPaymentMethods = [...configur.paymentMethod];
|
|
276
|
+
|
|
277
|
+
const config = {
|
|
278
|
+
amount,
|
|
279
|
+
apiKey,
|
|
280
|
+
secretKey,
|
|
281
|
+
jsonConfig: {
|
|
282
|
+
environment,
|
|
283
|
+
amount,
|
|
284
|
+
tokenOnly: false,
|
|
285
|
+
currency: configur.currency,
|
|
286
|
+
saveCard: configur.saveCard,
|
|
287
|
+
saveAccount: configur.saveAccount,
|
|
288
|
+
authenticatedACH: isAuthenticatedACH,
|
|
289
|
+
secureAuthentication: isSecureAuthentication,
|
|
290
|
+
showReceipt: configur.showReceipt,
|
|
291
|
+
showDonate: configur.showDonate,
|
|
292
|
+
showTotal: configur.showTotal,
|
|
293
|
+
showSubmitButton: configur.showSubmitButton,
|
|
294
|
+
paymentMethod: selectedPaymentMethods,
|
|
295
|
+
emailEditable,
|
|
296
|
+
email,
|
|
297
|
+
name,
|
|
298
|
+
fields: {
|
|
299
|
+
...configur.fields,
|
|
300
|
+
visibility: {
|
|
301
|
+
billing: isBillingVisible,
|
|
302
|
+
additional: isAdditionalVisible,
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
metadata,
|
|
306
|
+
...(isRecurring && {
|
|
307
|
+
recurring: {
|
|
308
|
+
enableRecurring: true,
|
|
309
|
+
recurringData,
|
|
310
|
+
},
|
|
311
|
+
}),
|
|
312
|
+
grailPayParams,
|
|
313
|
+
appearanceSettings: configur.appearanceSettings,
|
|
314
|
+
},
|
|
380
315
|
};
|
|
381
316
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
apiKey: 'stagingApiKey',
|
|
389
|
-
secretKey: 'stagingSecretKey',
|
|
390
|
-
},
|
|
391
|
-
});
|
|
392
|
-
const [isEnvironmentLoading, setIsEnvironmentLoading] = useState(false);
|
|
393
|
-
const [showConfig, setShowConfig] = useState(true);
|
|
394
|
-
|
|
395
|
-
// Debug useEffect to track payment method changes
|
|
396
|
-
useEffect(() => {
|
|
397
|
-
console.log('Payment methods state changed:', androidConfig.paymentMethod);
|
|
398
|
-
}, [androidConfig.paymentMethod]);
|
|
399
|
-
|
|
400
|
-
// Debug useEffect to track theme configuration changes
|
|
401
|
-
useEffect(() => {
|
|
402
|
-
console.log('Theme configuration changed:', themeConfiguration);
|
|
403
|
-
}, [themeConfiguration]);
|
|
404
|
-
|
|
405
|
-
// Debug useEffect to track Android appearance settings changes
|
|
406
|
-
useEffect(() => {
|
|
407
|
-
console.log('Android appearance settings changed:', androidConfig.appearanceSettings);
|
|
408
|
-
}, [androidConfig.appearanceSettings]);
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
useEffect(() => {
|
|
412
|
-
const updateEnvironment = async () => {
|
|
413
|
-
const { apiKey, secretKey } = apiKeys[environment];
|
|
414
|
-
|
|
415
|
-
if (Platform.OS === 'ios') {
|
|
416
|
-
setIsEnvironmentLoading(true);
|
|
417
|
-
try {
|
|
418
|
-
await EasyMerchantSdk.setViewController();
|
|
419
|
-
await EasyMerchantSdk.configureEnvironment(environment, apiKey, secretKey);
|
|
420
|
-
console.log(`iOS Environment configured: ${environment} with key ${apiKey}`);
|
|
421
|
-
} catch (err) {
|
|
422
|
-
console.error('iOS Initialization Error:', err);
|
|
423
|
-
Alert.alert('Error', `Failed to configure iOS environment: ${err.message}`);
|
|
424
|
-
} finally {
|
|
425
|
-
setIsEnvironmentLoading(false);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
317
|
+
try {
|
|
318
|
+
const response = await RNEasymerchantsdk.makePayment(config);
|
|
319
|
+
const parsedResponse = {
|
|
320
|
+
...JSON.parse(response?.response || '{}'),
|
|
321
|
+
billingInfo: safeParseMaybeJSON(response?.billingInfo),
|
|
322
|
+
additional_info: safeParseMaybeJSON(response?.additional_info),
|
|
428
323
|
};
|
|
324
|
+
setResult(JSON.stringify(parsedResponse, null, 2));
|
|
325
|
+
} catch (error) {
|
|
326
|
+
setResult(`Error: ${error.message ?? JSON.stringify(error)}`);
|
|
327
|
+
Alert.alert('Payment Error', error.message ?? 'Unknown error');
|
|
328
|
+
} finally {
|
|
329
|
+
setLoading(false);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
```
|
|
429
333
|
|
|
430
|
-
|
|
431
|
-
}, [environment, apiKeys]);
|
|
334
|
+
#### iOS: `handleIosBilling`
|
|
432
335
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
336
|
+
```javascript
|
|
337
|
+
const handleIosBilling = async () => {
|
|
338
|
+
const selectedPaymentMethodsIOS = [...configur.paymentMethod].map(method =>
|
|
339
|
+
method === 'card' ? 'Card' : method === 'ach' ? 'Bank' : method
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
const result = await EasyMerchantSdk.billing(
|
|
344
|
+
amount,
|
|
345
|
+
'usd', // Default currency
|
|
346
|
+
billingInfo,
|
|
347
|
+
selectedPaymentMethodsIOS,
|
|
348
|
+
themeConfiguration,
|
|
349
|
+
false, // tokenOnly
|
|
350
|
+
true, // saveCard
|
|
351
|
+
true, // saveAccount
|
|
352
|
+
isAuthenticatedACH,
|
|
353
|
+
grailPayParams,
|
|
354
|
+
'Submit',
|
|
355
|
+
isRecurring,
|
|
356
|
+
isRecurring ? recurringData.allowCycles : 0,
|
|
357
|
+
isRecurring ? recurringData.intervals : [],
|
|
358
|
+
isRecurring ? recurringData.recurringStartType : '',
|
|
359
|
+
isRecurring ? recurringData.recurringStartDate : '',
|
|
360
|
+
isSecureAuthentication,
|
|
361
|
+
true, // showReceipt
|
|
362
|
+
true, // showTotal
|
|
363
|
+
true, // showSubmitButton
|
|
364
|
+
isEmail,
|
|
365
|
+
email,
|
|
366
|
+
name,
|
|
367
|
+
metadata
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
const refToken = result?.additionalInfo?.threeDSecureStatus?.data?.ref_token;
|
|
371
|
+
if (refToken) setReferenceToken(refToken);
|
|
372
|
+
setResult(JSON.stringify(result, null, 2));
|
|
373
|
+
} catch (error) {
|
|
374
|
+
setResult(`Billing Error: ${error.message || JSON.stringify(error)}`);
|
|
375
|
+
} finally {
|
|
376
|
+
setLoading(false);
|
|
437
377
|
}
|
|
378
|
+
};
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
#### Main Payment Handler
|
|
438
382
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
383
|
+
```javascript
|
|
384
|
+
const handlePayment = async () => {
|
|
385
|
+
if (!amount || isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) {
|
|
386
|
+
return Alert.alert('Error', 'Please enter a valid amount');
|
|
442
387
|
}
|
|
443
388
|
|
|
444
|
-
|
|
389
|
+
if (isRecurring && (!recurringData.intervals || recurringData.intervals.length === 0)) {
|
|
390
|
+
return Alert.alert('Error', 'Please select at least one interval for recurring payment');
|
|
391
|
+
}
|
|
445
392
|
|
|
446
|
-
|
|
447
|
-
|
|
393
|
+
setLoading(true);
|
|
394
|
+
const timeoutId = setTimeout(() => setLoading(false), 3000);
|
|
448
395
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
396
|
+
try {
|
|
397
|
+
if (Platform.OS === 'android') {
|
|
398
|
+
await handleAndroidBilling();
|
|
399
|
+
} else {
|
|
400
|
+
await handleIosBilling();
|
|
401
|
+
}
|
|
402
|
+
} finally {
|
|
403
|
+
clearTimeout(timeoutId);
|
|
404
|
+
setLoading(false);
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
```
|
|
452
408
|
|
|
453
|
-
|
|
454
|
-
const additional = safeParseMaybeJSON(parsed.additional_info);
|
|
409
|
+
### 4. Event Listeners for Android
|
|
455
410
|
|
|
456
|
-
|
|
457
|
-
console.log('Additional Info:', additional);
|
|
411
|
+
Set up event listeners for payment success, status, and errors using `NativeEventEmitter` (Android only):
|
|
458
412
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
413
|
+
```javascript
|
|
414
|
+
useEffect(() => {
|
|
415
|
+
if (Platform.OS !== 'android') return;
|
|
462
416
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
417
|
+
const easyMerchantEvents = new NativeEventEmitter(RNEasymerchantsdk);
|
|
418
|
+
|
|
419
|
+
const successSub = easyMerchantEvents.addListener('PaymentSuccess', (data) => {
|
|
420
|
+
const parsed = JSON.parse(data.response);
|
|
421
|
+
setResult(JSON.stringify(parsed, null, 2));
|
|
467
422
|
});
|
|
468
423
|
|
|
469
424
|
const statusSub = easyMerchantEvents.addListener('PaymentStatus', (data) => {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
try {
|
|
473
|
-
const parsed = JSON.parse(data.statusResponse);
|
|
474
|
-
console.log('Parsed PaymentStatus:', parsed);
|
|
475
|
-
|
|
476
|
-
setResult(JSON.stringify(parsed, null, 2));
|
|
477
|
-
} catch (err) {
|
|
478
|
-
console.error('Error parsing PaymentStatus response:', err);
|
|
479
|
-
}
|
|
425
|
+
const parsed = JSON.parse(data.statusResponse);
|
|
426
|
+
setResult(JSON.stringify(parsed, null, 2));
|
|
480
427
|
});
|
|
481
428
|
|
|
482
429
|
const statusErrorSub = easyMerchantEvents.addListener('PaymentStatusError', (data) => {
|
|
483
|
-
|
|
484
|
-
setResult(`Status Error: ${JSON.stringify(data)}`);
|
|
430
|
+
setResult(`Status Error: ${JSON.stringify(data.error, null, 2)}`);
|
|
485
431
|
});
|
|
486
432
|
|
|
487
433
|
return () => {
|
|
@@ -490,1525 +436,26 @@ useEffect(() => {
|
|
|
490
436
|
statusErrorSub.remove();
|
|
491
437
|
};
|
|
492
438
|
}, []);
|
|
439
|
+
```
|
|
493
440
|
|
|
441
|
+
### 5. UI Components
|
|
494
442
|
|
|
495
|
-
|
|
496
|
-
if (typeof value === 'string') {
|
|
497
|
-
try {
|
|
498
|
-
return JSON.parse(value);
|
|
499
|
-
} catch (e) {
|
|
500
|
-
console.warn('Failed to parse JSON string:', value);
|
|
501
|
-
return value;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
return value;
|
|
505
|
-
};
|
|
506
|
-
|
|
507
|
-
useEffect(() => {
|
|
508
|
-
setBillingInfo(prev => ({
|
|
509
|
-
...prev,
|
|
510
|
-
visibility: {
|
|
511
|
-
billing: isBillingVisible,
|
|
512
|
-
additional: isAdditionalVisible,
|
|
513
|
-
},
|
|
514
|
-
}));
|
|
515
|
-
}, [isBillingVisible, isAdditionalVisible]);
|
|
516
|
-
|
|
517
|
-
const updateBillingInfo = (section, field, value) => {
|
|
518
|
-
setBillingInfo(prev => ({
|
|
519
|
-
...prev,
|
|
520
|
-
[section]: {
|
|
521
|
-
...prev[section],
|
|
522
|
-
[field]: value,
|
|
523
|
-
},
|
|
524
|
-
}));
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
const updateAndroidConfig = (field, value) => {
|
|
528
|
-
setAndroidConfig(prev => ({
|
|
529
|
-
...prev,
|
|
530
|
-
[field]: value,
|
|
531
|
-
}));
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
const updateAndroidConfigFields = (section, index, field, value) => {
|
|
535
|
-
setAndroidConfig(prev => ({
|
|
536
|
-
...prev,
|
|
537
|
-
fields: {
|
|
538
|
-
...prev.fields,
|
|
539
|
-
[section]: prev.fields[section].map((item, i) =>
|
|
540
|
-
i === index ? { ...item, [field]: value } : item
|
|
541
|
-
),
|
|
542
|
-
},
|
|
543
|
-
}));
|
|
544
|
-
};
|
|
545
|
-
|
|
546
|
-
const updateAndroidConfigAppearanceSettings = (field, value) => {
|
|
547
|
-
setAndroidConfig(prev => ({
|
|
548
|
-
...prev,
|
|
549
|
-
appearanceSettings: {
|
|
550
|
-
...prev.appearanceSettings,
|
|
551
|
-
[field]: value,
|
|
552
|
-
},
|
|
553
|
-
}));
|
|
554
|
-
};
|
|
555
|
-
|
|
556
|
-
const updateThemeConfiguration = (field, value) => {
|
|
557
|
-
setThemeConfiguration(prev => ({
|
|
558
|
-
...prev,
|
|
559
|
-
[field]: value,
|
|
560
|
-
}));
|
|
561
|
-
};
|
|
562
|
-
|
|
563
|
-
const updateGrailPayParams = (field, value) => {
|
|
564
|
-
setGrailPayParams(prev => ({
|
|
565
|
-
...prev,
|
|
566
|
-
[field]: value,
|
|
567
|
-
}));
|
|
568
|
-
};
|
|
569
|
-
|
|
570
|
-
const updateRecurringData = (field, value) => {
|
|
571
|
-
setRecurringData(prev => ({
|
|
572
|
-
...prev,
|
|
573
|
-
[field]: value,
|
|
574
|
-
}));
|
|
575
|
-
};
|
|
576
|
-
|
|
577
|
-
const togglePaymentMethod = (method) => {
|
|
578
|
-
console.log('Toggling payment method:', method);
|
|
579
|
-
setAndroidConfig(prev => {
|
|
580
|
-
const currentMethods = prev.paymentMethod;
|
|
581
|
-
console.log('Current payment methods before toggle:', currentMethods);
|
|
582
|
-
const isCurrentlySelected = currentMethods.includes(method);
|
|
583
|
-
console.log('Is currently selected:', isCurrentlySelected);
|
|
584
|
-
|
|
585
|
-
let newMethods;
|
|
586
|
-
if (isCurrentlySelected) {
|
|
587
|
-
// If deselecting and it's the only method, don't allow deselection
|
|
588
|
-
if (currentMethods.length === 1) {
|
|
589
|
-
console.log('Cannot deselect last payment method, keeping current state');
|
|
590
|
-
return prev; // Keep the current state
|
|
591
|
-
}
|
|
592
|
-
// Remove the method
|
|
593
|
-
newMethods = currentMethods.filter(m => m !== method);
|
|
594
|
-
console.log('Removing method, new methods:', newMethods);
|
|
595
|
-
} else {
|
|
596
|
-
// Add the method
|
|
597
|
-
newMethods = [...currentMethods, method];
|
|
598
|
-
console.log('Adding method, new methods:', newMethods);
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
const updatedConfig = {
|
|
602
|
-
...prev,
|
|
603
|
-
paymentMethod: newMethods,
|
|
604
|
-
};
|
|
605
|
-
console.log('Updated android config payment methods:', updatedConfig.paymentMethod);
|
|
606
|
-
return updatedConfig;
|
|
607
|
-
});
|
|
608
|
-
};
|
|
609
|
-
|
|
610
|
-
const toggleInterval = (interval) => {
|
|
611
|
-
setRecurringData(prev => ({
|
|
612
|
-
...prev,
|
|
613
|
-
intervals: prev.intervals.includes(interval)
|
|
614
|
-
? prev.intervals.filter(i => i !== interval)
|
|
615
|
-
: [...prev.intervals, interval],
|
|
616
|
-
}));
|
|
617
|
-
};
|
|
618
|
-
|
|
619
|
-
const handlePayment = async () => {
|
|
620
|
-
if (!amount || isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) {
|
|
621
|
-
return Alert.alert('Error', 'Please enter a valid amount');
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
// Validate that at least one interval is selected when recurring payment is enabled
|
|
625
|
-
if (isRecurring && (!recurringData.intervals || recurringData.intervals.length === 0)) {
|
|
626
|
-
return Alert.alert('Error', 'Please select at least one interval for recurring payment');
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
setLoading(true);
|
|
630
|
-
|
|
631
|
-
// Add a timeout to ensure loading state is reset even if SDK blocks execution
|
|
632
|
-
const timeoutId = setTimeout(() => {
|
|
633
|
-
setLoading(false);
|
|
634
|
-
}, 3000); // 3 second timeout
|
|
635
|
-
|
|
636
|
-
try {
|
|
637
|
-
if (Platform.OS === 'android') {
|
|
638
|
-
await handleAndroidBilling();
|
|
639
|
-
} else {
|
|
640
|
-
await handleIosBilling();
|
|
641
|
-
}
|
|
642
|
-
} finally {
|
|
643
|
-
clearTimeout(timeoutId);
|
|
644
|
-
setLoading(false);
|
|
645
|
-
}
|
|
646
|
-
};
|
|
647
|
-
|
|
648
|
-
const handleAndroidBilling = async () => {
|
|
649
|
-
|
|
650
|
-
const { apiKey, secretKey } = apiKeys[environment];
|
|
651
|
-
|
|
652
|
-
console.log('Selected payment methods for Android:', androidConfig.paymentMethod);
|
|
653
|
-
console.log('Full androidConfig.paymentMethod:', JSON.stringify(androidConfig.paymentMethod));
|
|
654
|
-
|
|
655
|
-
// Create a fresh copy of payment methods to ensure no reference issues
|
|
656
|
-
const selectedPaymentMethods = [...androidConfig.paymentMethod];
|
|
657
|
-
console.log('Fresh copy of payment methods:', selectedPaymentMethods);
|
|
658
|
-
console.log('Payment method contains "ach":', selectedPaymentMethods.includes('ach'));
|
|
659
|
-
console.log('Payment method contains "card":', selectedPaymentMethods.includes('card'));
|
|
660
|
-
|
|
661
|
-
// Log ACH-specific configurations when ACH is selected
|
|
662
|
-
if (selectedPaymentMethods.includes('ach')) {
|
|
663
|
-
console.log('=== ACH CONFIGURATION DEBUG ===');
|
|
664
|
-
console.log('authenticatedACH setting:', isAuthenticatedACH);
|
|
665
|
-
console.log('secureAuthentication setting:', isSecureAuthentication);
|
|
666
|
-
console.log('saveAccount setting:', androidConfig.saveAccount);
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// Try different payment method format for ACH-only scenario
|
|
670
|
-
let finalPaymentMethods = selectedPaymentMethods;
|
|
671
|
-
if (selectedPaymentMethods.length === 1 && selectedPaymentMethods.includes('ach')) {
|
|
672
|
-
// Try using a different format for ACH-only
|
|
673
|
-
finalPaymentMethods = ['ach'];
|
|
674
|
-
console.log('Using ACH-only format:', finalPaymentMethods);
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
const config = {
|
|
678
|
-
amount,
|
|
679
|
-
apiKey: apiKey,
|
|
680
|
-
secretKey: secretKey,
|
|
681
|
-
jsonConfig: {
|
|
682
|
-
environment,
|
|
683
|
-
amount,
|
|
684
|
-
tokenOnly: false,
|
|
685
|
-
currency: androidConfig.currency,
|
|
686
|
-
saveCard: androidConfig.saveCard,
|
|
687
|
-
saveAccount: androidConfig.saveAccount,
|
|
688
|
-
authenticatedACH: isAuthenticatedACH,
|
|
689
|
-
secureAuthentication: isSecureAuthentication,
|
|
690
|
-
|
|
691
|
-
showReceipt: androidConfig.showReceipt,
|
|
692
|
-
showDonate: androidConfig.showDonate,
|
|
693
|
-
showTotal: androidConfig.showTotal,
|
|
694
|
-
showSubmitButton: androidConfig.showSubmitButton,
|
|
695
|
-
paymentMethod: finalPaymentMethods,
|
|
696
|
-
// Try different configuration approaches for ACH-only
|
|
697
|
-
...(finalPaymentMethods.length === 1 && finalPaymentMethods.includes('ach') && {
|
|
698
|
-
paymentMethods: ['ach'],
|
|
699
|
-
allowedPaymentMethods: ['ach'],
|
|
700
|
-
disableCard: true,
|
|
701
|
-
}),
|
|
702
|
-
|
|
703
|
-
emailEditable,
|
|
704
|
-
email,
|
|
705
|
-
name: name,
|
|
706
|
-
fields: {
|
|
707
|
-
...androidConfig.fields,
|
|
708
|
-
visibility: {
|
|
709
|
-
billing: isBillingVisible,
|
|
710
|
-
additional: isAdditionalVisible,
|
|
711
|
-
},
|
|
712
|
-
},
|
|
713
|
-
|
|
714
|
-
metadata,
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
...(isRecurring && {
|
|
718
|
-
recurring: {
|
|
719
|
-
enableRecurring: true,
|
|
720
|
-
recurringData,
|
|
721
|
-
},
|
|
722
|
-
}),
|
|
723
|
-
grailPayParams,
|
|
724
|
-
appearanceSettings: androidConfig.appearanceSettings,
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
},
|
|
728
|
-
};
|
|
729
|
-
|
|
730
|
-
console.log('ENV API Keys =>', {
|
|
731
|
-
env: environment,
|
|
732
|
-
apiKeyFromEnv: apiKey,
|
|
733
|
-
secretKeyFromEnv: secretKey,
|
|
734
|
-
});
|
|
735
|
-
|
|
736
|
-
console.log('Config API Key =>', config.apiKey);
|
|
737
|
-
console.log('Config Secret Key =>', config.secretKey);
|
|
738
|
-
console.log('Full config being sent to Android SDK:', JSON.stringify(config, null, 2));
|
|
739
|
-
console.log('=== THEME CONFIGURATION DEBUG ===');
|
|
740
|
-
console.log('Android appearanceSettings:', JSON.stringify(androidConfig.appearanceSettings, null, 2));
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
try {
|
|
745
|
-
const response = await RNEasymerchantsdk.makePayment(config);
|
|
746
|
-
console.log('Full payment response:', response);
|
|
747
|
-
|
|
748
|
-
const parsedResponse = {
|
|
749
|
-
...response,
|
|
750
|
-
billingInfo: safeParseMaybeJSON(response.billingInfo),
|
|
751
|
-
additional_info: safeParseMaybeJSON(response.additional_info),
|
|
752
|
-
};
|
|
753
|
-
|
|
754
|
-
console.log('Parsed safe response:', parsedResponse);
|
|
755
|
-
|
|
756
|
-
setResult(JSON.stringify(parsedResponse, null, 2));
|
|
757
|
-
} catch (error) {
|
|
758
|
-
setResult(`Error: ${error.message}`);
|
|
759
|
-
Alert.alert('Payment Error', error.message);
|
|
760
|
-
} finally {
|
|
761
|
-
setLoading(false);
|
|
762
|
-
}
|
|
763
|
-
};
|
|
764
|
-
|
|
765
|
-
const handleIosBilling = async () => {
|
|
766
|
-
|
|
767
|
-
console.log('Selected payment methods for iOS:', androidConfig.paymentMethod);
|
|
768
|
-
console.log('Full androidConfig.paymentMethod for iOS:', JSON.stringify(androidConfig.paymentMethod));
|
|
769
|
-
|
|
770
|
-
// Create a fresh copy of payment methods for iOS and convert to iOS format
|
|
771
|
-
const selectedPaymentMethodsIOS = [...androidConfig.paymentMethod];
|
|
772
|
-
console.log('Fresh copy of payment methods for iOS:', selectedPaymentMethodsIOS);
|
|
773
|
-
console.log('Payment method contains "ach" (iOS):', selectedPaymentMethodsIOS.includes('ach'));
|
|
774
|
-
console.log('Payment method contains "card" (iOS):', selectedPaymentMethodsIOS.includes('card'));
|
|
775
|
-
|
|
776
|
-
// Convert Android payment method names to iOS format
|
|
777
|
-
let finalPaymentMethodsIOS = selectedPaymentMethodsIOS.map(method => {
|
|
778
|
-
if (method === 'card') return 'Card';
|
|
779
|
-
if (method === 'ach') return 'Bank';
|
|
780
|
-
return method;
|
|
781
|
-
});
|
|
782
|
-
|
|
783
|
-
console.log('Converted payment methods for iOS:', finalPaymentMethodsIOS);
|
|
784
|
-
console.log('=== iOS THEME CONFIGURATION DEBUG ===');
|
|
785
|
-
console.log('iOS themeConfiguration:', JSON.stringify(themeConfiguration, null, 2));
|
|
786
|
-
|
|
787
|
-
try {
|
|
788
|
-
const result = await EasyMerchantSdk.billing(
|
|
789
|
-
amount,
|
|
790
|
-
androidConfig.currency || 'usd',
|
|
791
|
-
billingInfo,
|
|
792
|
-
finalPaymentMethodsIOS,
|
|
793
|
-
themeConfiguration,
|
|
794
|
-
false, // tokenOnly
|
|
795
|
-
androidConfig.saveCard,
|
|
796
|
-
androidConfig.saveAccount,
|
|
797
|
-
isAuthenticatedACH,
|
|
798
|
-
grailPayParams,
|
|
799
|
-
'Submit',
|
|
800
|
-
isRecurring,
|
|
801
|
-
isRecurring ? recurringData.allowCycles : 0,
|
|
802
|
-
isRecurring ? recurringData.intervals : [],
|
|
803
|
-
isRecurring ? recurringData.recurringStartType : '',
|
|
804
|
-
isRecurring ? recurringData.recurringStartDate : '',
|
|
805
|
-
isSecureAuthentication,
|
|
806
|
-
androidConfig.showReceipt,
|
|
807
|
-
androidConfig.showTotal,
|
|
808
|
-
androidConfig.showSubmitButton,
|
|
809
|
-
isEmail,
|
|
810
|
-
email,
|
|
811
|
-
name,
|
|
812
|
-
metadata
|
|
813
|
-
);
|
|
814
|
-
|
|
815
|
-
const refToken = result?.additionalInfo?.threeDSecureStatus?.data?.ref_token;
|
|
816
|
-
if (refToken) setReferenceToken(refToken);
|
|
817
|
-
setResult(JSON.stringify(result, null, 2));
|
|
818
|
-
} catch (error) {
|
|
819
|
-
console.error('Billing Error:', error);
|
|
820
|
-
setResult(`Billing Error: ${error.message || JSON.stringify(error)}`);
|
|
821
|
-
} finally {
|
|
822
|
-
setLoading(false);
|
|
823
|
-
}
|
|
824
|
-
};
|
|
825
|
-
|
|
826
|
-
const handleCheckStatus = async () => {
|
|
827
|
-
setLoading(true);
|
|
828
|
-
try {
|
|
829
|
-
const response = await RNEasymerchantsdk.checkPaymentStatus();
|
|
830
|
-
console.log('Full payment response:', response);
|
|
831
|
-
setResult(JSON.stringify(response, null, 2));
|
|
832
|
-
} catch (error) {
|
|
833
|
-
setResult(`Error: ${error.message}`);
|
|
834
|
-
Alert.alert('Status Check Error', error.message);
|
|
835
|
-
} finally {
|
|
836
|
-
setLoading(false);
|
|
837
|
-
}
|
|
838
|
-
};
|
|
839
|
-
|
|
840
|
-
const handlePaymentReference = async () => {
|
|
841
|
-
if (Platform.OS === 'android') {
|
|
842
|
-
setResult('Payment Reference not supported on Android');
|
|
843
|
-
return;
|
|
844
|
-
}
|
|
845
|
-
try {
|
|
846
|
-
const response = await EasyMerchantSdk.paymentReference(referenceToken);
|
|
847
|
-
setResult(`Payment Reference:\n${JSON.stringify(response, null, 2)}`);
|
|
848
|
-
} catch (error) {
|
|
849
|
-
setResult(`Payment Reference Error: ${error.message || JSON.stringify(error)}`);
|
|
850
|
-
} finally {
|
|
851
|
-
setLoading(false);
|
|
852
|
-
}
|
|
853
|
-
};
|
|
854
|
-
|
|
855
|
-
return (
|
|
856
|
-
<KeyboardAvoidingView
|
|
857
|
-
style={styles.container}
|
|
858
|
-
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
859
|
-
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 20}
|
|
860
|
-
>
|
|
861
|
-
<ScrollView
|
|
862
|
-
contentContainerStyle={styles.scrollContent}
|
|
863
|
-
keyboardShouldPersistTaps="handled"
|
|
864
|
-
showsVerticalScrollIndicator={false}
|
|
865
|
-
>
|
|
866
|
-
<Text style={styles.title}>EasyMerchant SDK</Text>
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
<Text style={styles.sectionTitle}>Basic Info</Text>
|
|
871
|
-
<Text style={styles.label}>Amount</Text>
|
|
872
|
-
<TextInput
|
|
873
|
-
style={styles.input}
|
|
874
|
-
placeholder="Enter amount (e.g., 10.00)"
|
|
875
|
-
placeholderTextColor="#999999"
|
|
876
|
-
keyboardType="decimal-pad"
|
|
877
|
-
value={amount}
|
|
878
|
-
onChangeText={setAmount}
|
|
879
|
-
/>
|
|
880
|
-
<Text style={styles.label}>Email</Text>
|
|
881
|
-
<TextInput
|
|
882
|
-
style={styles.input}
|
|
883
|
-
placeholder="Enter email"
|
|
884
|
-
placeholderTextColor="#999999"
|
|
885
|
-
keyboardType="email-address"
|
|
886
|
-
value={email}
|
|
887
|
-
onChangeText={setEmail}
|
|
888
|
-
/>
|
|
889
|
-
<Text style={styles.label}>Name</Text>
|
|
890
|
-
<TextInput
|
|
891
|
-
style={styles.input}
|
|
892
|
-
placeholder="Enter name"
|
|
893
|
-
placeholderTextColor="#999999"
|
|
894
|
-
value={name}
|
|
895
|
-
onChangeText={setName}
|
|
896
|
-
/>
|
|
897
|
-
|
|
898
|
-
<View style={styles.buttonGroup}>
|
|
899
|
-
<FilledButton
|
|
900
|
-
title="Pay"
|
|
901
|
-
onPress={handlePayment}
|
|
902
|
-
disabled={loading}
|
|
903
|
-
/>
|
|
904
|
-
{Platform.OS === 'ios' && (
|
|
905
|
-
<FilledButton
|
|
906
|
-
title="Payment Ref"
|
|
907
|
-
onPress={handlePaymentReference}
|
|
908
|
-
disabled={loading}
|
|
909
|
-
/>
|
|
910
|
-
)}
|
|
911
|
-
</View>
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
<View style={styles.toggleContainer}>
|
|
918
|
-
<Text style={styles.label}>Show Configurations</Text>
|
|
919
|
-
<Switch
|
|
920
|
-
value={showConfig}
|
|
921
|
-
onValueChange={setShowConfig}
|
|
922
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
923
|
-
thumbColor={showConfig ? '#fff' : '#f4f3f4'}
|
|
924
|
-
/>
|
|
925
|
-
</View>
|
|
926
|
-
|
|
927
|
-
{showConfig && (
|
|
928
|
-
<>
|
|
929
|
-
<Text style={styles.sectionTitle}>Environment</Text>
|
|
930
|
-
<View style={styles.pickerContainer}>
|
|
931
|
-
<Text style={styles.label}>Select Environment</Text>
|
|
932
|
-
<View style={styles.buttonGroup}>
|
|
933
|
-
<TouchableOpacity
|
|
934
|
-
style={[
|
|
935
|
-
styles.environmentButton,
|
|
936
|
-
{ backgroundColor: environment === 'sandbox' ? '#2563EB' : '#ccc' },
|
|
937
|
-
]}
|
|
938
|
-
onPress={() => {
|
|
939
|
-
console.log('Sandbox tapped, setting environment to sandbox');
|
|
940
|
-
setEnvironment('sandbox');
|
|
941
|
-
}}
|
|
942
|
-
disabled={isEnvironmentLoading || environment === 'sandbox'}
|
|
943
|
-
>
|
|
944
|
-
<Text style={styles.buttonText}>Sandbox</Text>
|
|
945
|
-
</TouchableOpacity>
|
|
946
|
-
<TouchableOpacity
|
|
947
|
-
style={[
|
|
948
|
-
styles.environmentButton,
|
|
949
|
-
{ backgroundColor: environment === 'staging' ? '#2563EB' : '#ccc' },
|
|
950
|
-
]}
|
|
951
|
-
onPress={() => {
|
|
952
|
-
console.log('Staging tapped, setting environment to staging');
|
|
953
|
-
setEnvironment('staging');
|
|
954
|
-
}}
|
|
955
|
-
disabled={isEnvironmentLoading || environment === 'staging'}
|
|
956
|
-
>
|
|
957
|
-
<Text style={styles.buttonText}>Staging</Text>
|
|
958
|
-
</TouchableOpacity>
|
|
959
|
-
</View>
|
|
960
|
-
</View>
|
|
961
|
-
|
|
962
|
-
<Text style={styles.sectionTitle}>{environment === 'sandbox' ? 'Sandbox' : 'Staging'} API Credentials</Text>
|
|
963
|
-
<Text style={styles.label}>API Key</Text>
|
|
964
|
-
<TextInput
|
|
965
|
-
style={styles.input}
|
|
966
|
-
value={apiKeys[environment].apiKey}
|
|
967
|
-
onChangeText={value =>
|
|
968
|
-
setApiKeys(prev => ({
|
|
969
|
-
...prev,
|
|
970
|
-
[environment]: { ...prev[environment], apiKey: value },
|
|
971
|
-
}))
|
|
972
|
-
}
|
|
973
|
-
placeholder={`Enter ${environment === 'sandbox' ? 'Sandbox' : 'Staging'} API Key`}
|
|
974
|
-
placeholderTextColor="#999999"
|
|
975
|
-
/>
|
|
976
|
-
<Text style={styles.label}>Secret Key</Text>
|
|
977
|
-
<View style={styles.inputContainer}>
|
|
978
|
-
<TextInput
|
|
979
|
-
style={[styles.input, { flex: 1 }]}
|
|
980
|
-
value={apiKeys[environment].secretKey}
|
|
981
|
-
onChangeText={value =>
|
|
982
|
-
setApiKeys(prev => ({
|
|
983
|
-
...prev,
|
|
984
|
-
[environment]: { ...prev[environment], secretKey: value },
|
|
985
|
-
}))
|
|
986
|
-
}
|
|
987
|
-
placeholder={`Enter ${environment === 'sandbox' ? 'Sandbox' : 'Staging'} Secret Key`}
|
|
988
|
-
placeholderTextColor="#999999"
|
|
989
|
-
secureTextEntry={!showSecretKey}
|
|
990
|
-
/>
|
|
991
|
-
<TouchableOpacity
|
|
992
|
-
style={styles.eyeButton}
|
|
993
|
-
onPress={() => setShowSecretKey(prev => !prev)}
|
|
994
|
-
>
|
|
995
|
-
<Text style={styles.eyeIcon}>{showSecretKey ? '👁️' : '👁️🗨️'}</Text>
|
|
996
|
-
</TouchableOpacity>
|
|
997
|
-
</View>
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
<Text style={styles.sectionTitle}>Payment Options</Text>
|
|
1004
|
-
<View style={styles.toggleContainer}>
|
|
1005
|
-
<Text style={styles.label}>Recurring Payment</Text>
|
|
1006
|
-
<Switch
|
|
1007
|
-
value={isRecurring}
|
|
1008
|
-
onValueChange={setIsRecurring}
|
|
1009
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
1010
|
-
thumbColor={isRecurring ? '#fff' : '#f4f3f4'}
|
|
1011
|
-
/>
|
|
1012
|
-
</View>
|
|
1013
|
-
<View style={styles.toggleContainer}>
|
|
1014
|
-
<Text style={styles.label}>Authenticated ACH</Text>
|
|
1015
|
-
<Switch
|
|
1016
|
-
value={isAuthenticatedACH}
|
|
1017
|
-
onValueChange={setAuthenticatedACH}
|
|
1018
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
1019
|
-
thumbColor={isAuthenticatedACH ? '#fff' : '#f4f3f4'}
|
|
1020
|
-
/>
|
|
1021
|
-
</View>
|
|
1022
|
-
<View style={styles.toggleContainer}>
|
|
1023
|
-
<Text style={styles.label}>3DS</Text>
|
|
1024
|
-
<Switch
|
|
1025
|
-
value={isSecureAuthentication}
|
|
1026
|
-
onValueChange={setSecureAuthentication}
|
|
1027
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
1028
|
-
thumbColor={isSecureAuthentication ? '#fff' : '#f4f3f4'}
|
|
1029
|
-
/>
|
|
1030
|
-
</View>
|
|
1031
|
-
<View style={styles.toggleContainer}>
|
|
1032
|
-
<Text style={styles.label}>Billing Visible</Text>
|
|
1033
|
-
<Switch
|
|
1034
|
-
value={isBillingVisible}
|
|
1035
|
-
onValueChange={setBillingVisible}
|
|
1036
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
1037
|
-
thumbColor={isBillingVisible ? '#fff' : '#f4f3f4'}
|
|
1038
|
-
/>
|
|
1039
|
-
</View>
|
|
1040
|
-
<View style={styles.toggleContainer}>
|
|
1041
|
-
<Text style={styles.label}>Additional Info Visible</Text>
|
|
1042
|
-
<Switch
|
|
1043
|
-
value={isAdditionalVisible}
|
|
1044
|
-
onValueChange={setAdditionalVisible}
|
|
1045
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
1046
|
-
thumbColor={isAdditionalVisible ? '#fff' : '#f4f3f4'}
|
|
1047
|
-
/>
|
|
1048
|
-
</View>
|
|
1049
|
-
<Text style={styles.label}>Payment Methods (Shared)</Text>
|
|
1050
|
-
<Text style={styles.debugText}>
|
|
1051
|
-
Selected: {androidConfig.paymentMethod.join(', ') || 'None'}
|
|
1052
|
-
</Text>
|
|
1053
|
-
<View style={styles.buttonGroup}>
|
|
1054
|
-
<FilledButton
|
|
1055
|
-
title="Card"
|
|
1056
|
-
onPress={() => togglePaymentMethod('card')}
|
|
1057
|
-
disabled={loading}
|
|
1058
|
-
style={[
|
|
1059
|
-
{ flex: 1 },
|
|
1060
|
-
androidConfig.paymentMethod.includes('card')
|
|
1061
|
-
? { backgroundColor: '#2563EB' }
|
|
1062
|
-
: { backgroundColor: '#E5E7EB' }
|
|
1063
|
-
]}
|
|
1064
|
-
textStyle={androidConfig.paymentMethod.includes('card')
|
|
1065
|
-
? { color: '#fff' }
|
|
1066
|
-
: { color: '#374151' }
|
|
1067
|
-
}
|
|
1068
|
-
/>
|
|
1069
|
-
<FilledButton
|
|
1070
|
-
title="ACH"
|
|
1071
|
-
onPress={() => togglePaymentMethod('ach')}
|
|
1072
|
-
disabled={loading}
|
|
1073
|
-
style={[
|
|
1074
|
-
{ flex: 1 },
|
|
1075
|
-
androidConfig.paymentMethod.includes('ach')
|
|
1076
|
-
? { backgroundColor: '#2563EB' }
|
|
1077
|
-
: { backgroundColor: '#E5E7EB' }
|
|
1078
|
-
]}
|
|
1079
|
-
textStyle={androidConfig.paymentMethod.includes('ach')
|
|
1080
|
-
? { color: '#fff' }
|
|
1081
|
-
: { color: '#374151' }
|
|
1082
|
-
}
|
|
1083
|
-
/>
|
|
1084
|
-
</View>
|
|
1085
|
-
{Platform.OS === 'android' && (
|
|
1086
|
-
<View style={styles.toggleContainer}>
|
|
1087
|
-
<Text style={styles.label}>Email Editable</Text>
|
|
1088
|
-
<Switch
|
|
1089
|
-
value={emailEditable}
|
|
1090
|
-
onValueChange={setEmailEditable}
|
|
1091
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
1092
|
-
thumbColor={emailEditable ? '#fff' : '#f4f3f4'}
|
|
1093
|
-
/>
|
|
1094
|
-
</View>
|
|
1095
|
-
)}
|
|
1096
|
-
{Platform.OS === 'ios' && (
|
|
1097
|
-
<View style={styles.toggleContainer}>
|
|
1098
|
-
<Text style={styles.label}>Allow Email Editable</Text>
|
|
1099
|
-
<Switch
|
|
1100
|
-
value={isEmail}
|
|
1101
|
-
onValueChange={setIsEmail}
|
|
1102
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
1103
|
-
thumbColor={isEmail ? '#fff' : '#f4f3f4'}
|
|
1104
|
-
/>
|
|
1105
|
-
</View>
|
|
1106
|
-
)}
|
|
1107
|
-
|
|
1108
|
-
<Text style={styles.sectionTitle}>Billing Info</Text>
|
|
1109
|
-
|
|
1110
|
-
{/* Billing Required Switches */}
|
|
1111
|
-
<Text style={styles.subsectionTitle}>Billing Required Fields</Text>
|
|
1112
|
-
<Text style={styles.label}>Billing Required: Address</Text>
|
|
1113
|
-
<Switch
|
|
1114
|
-
value={billingInfo.billingRequired.address}
|
|
1115
|
-
onValueChange={value => updateBillingInfo('billingRequired', 'address', value)}
|
|
1116
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
1117
|
-
thumbColor={billingInfo.billingRequired.address ? '#fff' : '#f4f3f4'}
|
|
1118
|
-
/>
|
|
1119
|
-
<Text style={styles.label}>Billing Required: Country</Text>
|
|
1120
|
-
<Switch
|
|
1121
|
-
value={billingInfo.billingRequired.country}
|
|
1122
|
-
onValueChange={value => updateBillingInfo('billingRequired', 'country', value)}
|
|
1123
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
1124
|
-
thumbColor={billingInfo.billingRequired.country ? '#fff' : '#f4f3f4'}
|
|
1125
|
-
/>
|
|
1126
|
-
<Text style={styles.label}>Billing Required: State</Text>
|
|
1127
|
-
<Switch
|
|
1128
|
-
value={billingInfo.billingRequired.state}
|
|
1129
|
-
onValueChange={value => updateBillingInfo('billingRequired', 'state', value)}
|
|
1130
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
1131
|
-
thumbColor={billingInfo.billingRequired.state ? '#fff' : '#f4f3f4'}
|
|
1132
|
-
/>
|
|
1133
|
-
<Text style={styles.label}>Billing Required: City</Text>
|
|
1134
|
-
<Switch
|
|
1135
|
-
value={billingInfo.billingRequired.city}
|
|
1136
|
-
onValueChange={value => updateBillingInfo('billingRequired', 'city', value)}
|
|
1137
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
1138
|
-
thumbColor={billingInfo.billingRequired.city ? '#fff' : '#f4f3f4'}
|
|
1139
|
-
/>
|
|
1140
|
-
<Text style={styles.label}>Billing Required: Postal Code</Text>
|
|
1141
|
-
<Switch
|
|
1142
|
-
value={billingInfo.billingRequired.postal_code}
|
|
1143
|
-
onValueChange={value => updateBillingInfo('billingRequired', 'postal_code', value)}
|
|
1144
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
1145
|
-
thumbColor={billingInfo.billingRequired.postal_code ? '#fff' : '#f4f3f4'}
|
|
1146
|
-
/>
|
|
1147
|
-
|
|
1148
|
-
{/* Billing Fields Section */}
|
|
1149
|
-
<Text style={styles.subsectionTitle}>Billing Fields</Text>
|
|
1150
|
-
{billingInfo.billingRequired.address && (
|
|
1151
|
-
<>
|
|
1152
|
-
<Text style={styles.label}>Billing Address</Text>
|
|
1153
|
-
<TextInput
|
|
1154
|
-
style={styles.input}
|
|
1155
|
-
value={billingInfo.billing.address}
|
|
1156
|
-
onChangeText={value => updateBillingInfo('billing', 'address', value)}
|
|
1157
|
-
placeholder="Enter billing address"
|
|
1158
|
-
placeholderTextColor="#999999"
|
|
1159
|
-
/>
|
|
1160
|
-
</>
|
|
1161
|
-
)}
|
|
1162
|
-
{billingInfo.billingRequired.country && (
|
|
1163
|
-
<>
|
|
1164
|
-
<Text style={styles.label}>Billing Country</Text>
|
|
1165
|
-
<Dropdown
|
|
1166
|
-
value={billingInfo.billing.country}
|
|
1167
|
-
onValueChange={value => updateBillingInfo('billing', 'country', value)}
|
|
1168
|
-
options={[
|
|
1169
|
-
"Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra",
|
|
1170
|
-
"Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina",
|
|
1171
|
-
"Armenia", "Aruba", "Australia", "Austria", "Azerbaijan",
|
|
1172
|
-
"Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus",
|
|
1173
|
-
"Belgium", "Belize", "Benin", "Bermuda", "Bhutan",
|
|
1174
|
-
"Bolivia", "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil",
|
|
1175
|
-
"British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi",
|
|
1176
|
-
"Cabo Verde", "Cambodia", "Cameroon", "Canada", "Cayman Islands",
|
|
1177
|
-
"Central African Republic", "Chad", "Chile", "China", "Christmas Island",
|
|
1178
|
-
"Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo", "Congo, Democratic Republic of the",
|
|
1179
|
-
"Cook Islands", "Costa Rica", "Croatia", "Cuba", "Curaçao",
|
|
1180
|
-
"Cyprus", "Czech Republic", "Côte d'Ivoire", "Denmark", "Djibouti",
|
|
1181
|
-
"Dominica", "Dominican Republic", "Ecuador", "Egypt", "El Salvador",
|
|
1182
|
-
"Equatorial Guinea", "Eritrea", "Estonia", "Eswatini", "Ethiopia",
|
|
1183
|
-
"Falkland Islands (Malvinas)", "Faroe Islands", "Fiji", "Finland", "France",
|
|
1184
|
-
"French Guiana", "French Polynesia", "French Southern Territories", "Gabon", "Gambia",
|
|
1185
|
-
"Georgia", "Germany", "Ghana", "Gibraltar", "Greece",
|
|
1186
|
-
"Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala",
|
|
1187
|
-
"Guernsey", "Guinea", "Guinea-Bissau", "Guyana", "Haiti",
|
|
1188
|
-
"Heard Island and McDonald Islands", "Holy See", "Honduras", "Hong Kong", "Hungary",
|
|
1189
|
-
"Iceland", "India", "Indonesia", "Iran", "Iraq",
|
|
1190
|
-
"Ireland", "Isle of Man", "Israel", "Italy", "Jamaica",
|
|
1191
|
-
"Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
|
|
1192
|
-
"Kiribati", "Korea (Democratic People's Republic of)", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
|
|
1193
|
-
"Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia",
|
|
1194
|
-
"Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macao",
|
|
1195
|
-
"Madagascar", "Malawi", "Malaysia", "Maldives", "Mali",
|
|
1196
|
-
"Malta", "Marshall Islands", "Martinique", "Mauritania", "Mauritius",
|
|
1197
|
-
"Mayotte", "Mexico", "Micronesia (Federated States of)", "Moldova", "Monaco",
|
|
1198
|
-
"Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique",
|
|
1199
|
-
"Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands",
|
|
1200
|
-
"New Caledonia", "New Zealand", "Nicaragua", "Niger", "Nigeria",
|
|
1201
|
-
"Niue", "Norfolk Island", "North Macedonia", "Northern Mariana Islands", "Norway",
|
|
1202
|
-
"Oman", "Pakistan", "Palau", "Palestine, State of", "Panama",
|
|
1203
|
-
"Papua New Guinea", "Paraguay", "Peru", "Philippines", "Pitcairn",
|
|
1204
|
-
"Poland", "Portugal", "Puerto Rico", "Qatar", "Romania",
|
|
1205
|
-
"Russian Federation", "Rwanda", "Réunion", "Saint Barthélemy", "Saint Helena, Ascension and Tristan da Cunha",
|
|
1206
|
-
"Saint Kitts and Nevis", "Saint Lucia", "Saint Martin (French part)", "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines",
|
|
1207
|
-
"Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal",
|
|
1208
|
-
"Serbia", "Seychelles", "Sierra Leone", "Singapore", "Sint Maarten (Dutch part)",
|
|
1209
|
-
"Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
|
|
1210
|
-
"South Georgia and the South Sandwich Islands", "South Sudan", "Spain", "Sri Lanka", "Sudan",
|
|
1211
|
-
"Suriname", "Svalbard and Jan Mayen", "Sweden", "Switzerland", "Syrian Arab Republic",
|
|
1212
|
-
"Taiwan, Province of China", "Tajikistan", "Tanzania", "Thailand", "Timor-Leste",
|
|
1213
|
-
"Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia",
|
|
1214
|
-
"Turkey", "Turkmenistan", "Tuvalu", "Uganda", "Ukraine",
|
|
1215
|
-
"United Arab Emirates", "United Kingdom", "United States", "Uruguay", "Uzbekistan",
|
|
1216
|
-
"Vanuatu", "Venezuela", "Viet Nam", "Western Sahara", "Yemen",
|
|
1217
|
-
"Zambia", "Zimbabwe", "Åland Islands"
|
|
1218
|
-
]}
|
|
1219
|
-
placeholder="Select country"
|
|
1220
|
-
/>
|
|
1221
|
-
</>
|
|
1222
|
-
)}
|
|
1223
|
-
{billingInfo.billingRequired.state && (
|
|
1224
|
-
<>
|
|
1225
|
-
<Text style={styles.label}>Billing State</Text>
|
|
1226
|
-
<TextInput
|
|
1227
|
-
style={styles.input}
|
|
1228
|
-
value={billingInfo.billing.state}
|
|
1229
|
-
onChangeText={value => updateBillingInfo('billing', 'state', value)}
|
|
1230
|
-
placeholder="Enter state"
|
|
1231
|
-
placeholderTextColor="#999999"
|
|
1232
|
-
/>
|
|
1233
|
-
</>
|
|
1234
|
-
)}
|
|
1235
|
-
{billingInfo.billingRequired.city && (
|
|
1236
|
-
<>
|
|
1237
|
-
<Text style={styles.label}>Billing City</Text>
|
|
1238
|
-
<TextInput
|
|
1239
|
-
style={styles.input}
|
|
1240
|
-
value={billingInfo.billing.city}
|
|
1241
|
-
onChangeText={value => updateBillingInfo('billing', 'city', value)}
|
|
1242
|
-
placeholder="Enter city"
|
|
1243
|
-
placeholderTextColor="#999999"
|
|
1244
|
-
/>
|
|
1245
|
-
</>
|
|
1246
|
-
)}
|
|
1247
|
-
{billingInfo.billingRequired.postal_code && (
|
|
1248
|
-
<>
|
|
1249
|
-
<Text style={styles.label}>Billing Postal Code</Text>
|
|
1250
|
-
<TextInput
|
|
1251
|
-
style={styles.input}
|
|
1252
|
-
value={billingInfo.billing.postal_code}
|
|
1253
|
-
onChangeText={value => updateBillingInfo('billing', 'postal_code', value)}
|
|
1254
|
-
placeholder="Enter postal code"
|
|
1255
|
-
placeholderTextColor="#999999"
|
|
1256
|
-
/>
|
|
1257
|
-
</>
|
|
1258
|
-
)}
|
|
1259
|
-
|
|
1260
|
-
<Text style={styles.sectionTitle}>Additional Info</Text>
|
|
1261
|
-
|
|
1262
|
-
{/* Additional Required Switches */}
|
|
1263
|
-
<Text style={styles.subsectionTitle}>Additional Required Fields</Text>
|
|
1264
|
-
<Text style={styles.label}>Additional Required: Name</Text>
|
|
1265
|
-
<Switch
|
|
1266
|
-
value={billingInfo.additionalRequired.name}
|
|
1267
|
-
onValueChange={value => updateBillingInfo('additionalRequired', 'name', value)}
|
|
1268
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
1269
|
-
thumbColor={billingInfo.additionalRequired.name ? '#fff' : '#f4f3f4'}
|
|
1270
|
-
/>
|
|
1271
|
-
<Text style={styles.label}>Additional Required: Email Address</Text>
|
|
1272
|
-
<Switch
|
|
1273
|
-
value={billingInfo.additionalRequired.email_address}
|
|
1274
|
-
onValueChange={value => updateBillingInfo('additionalRequired', 'email_address', value)}
|
|
1275
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
1276
|
-
thumbColor={billingInfo.additionalRequired.email_address ? '#fff' : '#f4f3f4'}
|
|
1277
|
-
/>
|
|
1278
|
-
<Text style={styles.label}>Additional Required: Phone Number</Text>
|
|
1279
|
-
<Switch
|
|
1280
|
-
value={billingInfo.additionalRequired.phone_number}
|
|
1281
|
-
onValueChange={value => updateBillingInfo('additionalRequired', 'phone_number', value)}
|
|
1282
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
1283
|
-
thumbColor={billingInfo.additionalRequired.phone_number ? '#fff' : '#f4f3f4'}
|
|
1284
|
-
/>
|
|
1285
|
-
<Text style={styles.label}>Additional Required: Description</Text>
|
|
1286
|
-
<Switch
|
|
1287
|
-
value={billingInfo.additionalRequired.description}
|
|
1288
|
-
onValueChange={value => updateBillingInfo('additionalRequired', 'description', value)}
|
|
1289
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
1290
|
-
thumbColor={billingInfo.additionalRequired.description ? '#fff' : '#f4f3f4'}
|
|
1291
|
-
/>
|
|
1292
|
-
|
|
1293
|
-
{/* Additional Fields Section */}
|
|
1294
|
-
<Text style={styles.subsectionTitle}>Additional Fields</Text>
|
|
1295
|
-
{billingInfo.additionalRequired.name && (
|
|
1296
|
-
<>
|
|
1297
|
-
<Text style={styles.label}>Additional: Name</Text>
|
|
1298
|
-
<TextInput
|
|
1299
|
-
style={styles.input}
|
|
1300
|
-
value={billingInfo.additional.name}
|
|
1301
|
-
onChangeText={value => updateBillingInfo('additional', 'name', value)}
|
|
1302
|
-
placeholder="Enter name"
|
|
1303
|
-
placeholderTextColor="#999999"
|
|
1304
|
-
/>
|
|
1305
|
-
</>
|
|
1306
|
-
)}
|
|
1307
|
-
{billingInfo.additionalRequired.email_address && (
|
|
1308
|
-
<>
|
|
1309
|
-
<Text style={styles.label}>Additional: Email Address</Text>
|
|
1310
|
-
<TextInput
|
|
1311
|
-
style={styles.input}
|
|
1312
|
-
value={billingInfo.additional.email_address}
|
|
1313
|
-
onChangeText={value => updateBillingInfo('additional', 'email_address', value)}
|
|
1314
|
-
placeholder="Enter email address"
|
|
1315
|
-
placeholderTextColor="#999999"
|
|
1316
|
-
/>
|
|
1317
|
-
</>
|
|
1318
|
-
)}
|
|
1319
|
-
{billingInfo.additionalRequired.phone_number && (
|
|
1320
|
-
<>
|
|
1321
|
-
<Text style={styles.label}>Additional: Phone Number</Text>
|
|
1322
|
-
<View style={styles.phoneRow}>
|
|
1323
|
-
<TextInput
|
|
1324
|
-
style={styles.countryCodeInput}
|
|
1325
|
-
value={billingInfo.additional.country_code || '+1'}
|
|
1326
|
-
onChangeText={value => {
|
|
1327
|
-
// Only allow a '+' at the start, followed by up to 4 digits
|
|
1328
|
-
let filtered = value.replace(/[^+0-9]/g, '');
|
|
1329
|
-
if (!filtered.startsWith('+')) filtered = '+' + filtered.replace(/\+/g, '');
|
|
1330
|
-
filtered = filtered.slice(0, 5);
|
|
1331
|
-
// Update country code and combined phone number
|
|
1332
|
-
const phone = billingInfo.additional.phone_number || '';
|
|
1333
|
-
const phoneOnly = phone.slice((billingInfo.additional.country_code || '+1').replace('+', '').length);
|
|
1334
|
-
const combined = filtered.replace('+', '') + phoneOnly;
|
|
1335
|
-
updateBillingInfo('additional', 'country_code', filtered);
|
|
1336
|
-
updateBillingInfo('additional', 'phone_number', combined);
|
|
1337
|
-
}}
|
|
1338
|
-
placeholder="+1"
|
|
1339
|
-
placeholderTextColor="#999999"
|
|
1340
|
-
keyboardType="phone-pad"
|
|
1341
|
-
maxLength={5}
|
|
1342
|
-
/>
|
|
1343
|
-
<TextInput
|
|
1344
|
-
style={[styles.input, { flex: 1, marginBottom: 0 }]}
|
|
1345
|
-
value={(billingInfo.additional.phone_number || '').slice((billingInfo.additional.country_code || '+1').replace('+', '').length)}
|
|
1346
|
-
onChangeText={value => {
|
|
1347
|
-
// Only allow digits and limit to 10 characters
|
|
1348
|
-
const numericValue = value.replace(/[^0-9]/g, '').slice(0, 10);
|
|
1349
|
-
const code = (billingInfo.additional.country_code || '+1').replace('+', '');
|
|
1350
|
-
updateBillingInfo('additional', 'phone_number', code + numericValue);
|
|
1351
|
-
}}
|
|
1352
|
-
placeholder="Enter phone number"
|
|
1353
|
-
placeholderTextColor="#999999"
|
|
1354
|
-
keyboardType="phone-pad"
|
|
1355
|
-
maxLength={10}
|
|
1356
|
-
/>
|
|
1357
|
-
</View>
|
|
1358
|
-
</>
|
|
1359
|
-
)}
|
|
1360
|
-
{billingInfo.additionalRequired.description && (
|
|
1361
|
-
<>
|
|
1362
|
-
<Text style={styles.label}>Additional: Description</Text>
|
|
1363
|
-
<TextInput
|
|
1364
|
-
style={styles.input}
|
|
1365
|
-
value={billingInfo.additional.description}
|
|
1366
|
-
onChangeText={value => updateBillingInfo('additional', 'description', value)}
|
|
1367
|
-
placeholder="Enter description"
|
|
1368
|
-
placeholderTextColor="#999999"
|
|
1369
|
-
/>
|
|
1370
|
-
</>
|
|
1371
|
-
)}
|
|
1372
|
-
|
|
1373
|
-
{/* Android Appearance Settings */}
|
|
1374
|
-
{Platform.OS === 'android' && (
|
|
1375
|
-
<>
|
|
1376
|
-
<Text style={styles.sectionTitle}>Android Appearance Settings</Text>
|
|
1377
|
-
<Text style={styles.label}>Body Background Color</Text>
|
|
1378
|
-
<View style={styles.colorInputContainer}>
|
|
1379
|
-
<TextInput
|
|
1380
|
-
style={[styles.input, { flex: 1 }]}
|
|
1381
|
-
value={androidConfig.appearanceSettings.bodyBackgroundColor}
|
|
1382
|
-
onChangeText={value => updateAndroidConfigAppearanceSettings('bodyBackgroundColor', value)}
|
|
1383
|
-
placeholder="Enter body background color"
|
|
1384
|
-
placeholderTextColor="#999999"
|
|
1385
|
-
/>
|
|
1386
|
-
<View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.bodyBackgroundColor }]} />
|
|
1387
|
-
</View>
|
|
1388
|
-
<Text style={styles.label}>Container Background Color</Text>
|
|
1389
|
-
<View style={styles.colorInputContainer}>
|
|
1390
|
-
<TextInput
|
|
1391
|
-
style={[styles.input, { flex: 1 }]}
|
|
1392
|
-
value={androidConfig.appearanceSettings.containerBackgroundColor}
|
|
1393
|
-
onChangeText={value => updateAndroidConfigAppearanceSettings('containerBackgroundColor', value)}
|
|
1394
|
-
placeholder="Enter container background color"
|
|
1395
|
-
placeholderTextColor="#999999"
|
|
1396
|
-
/>
|
|
1397
|
-
<View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.containerBackgroundColor }]} />
|
|
1398
|
-
</View>
|
|
1399
|
-
<Text style={styles.label}>Primary Font Color</Text>
|
|
1400
|
-
<View style={styles.colorInputContainer}>
|
|
1401
|
-
<TextInput
|
|
1402
|
-
style={[styles.input, { flex: 1 }]}
|
|
1403
|
-
value={androidConfig.appearanceSettings.primaryFontColor}
|
|
1404
|
-
onChangeText={value => updateAndroidConfigAppearanceSettings('primaryFontColor', value)}
|
|
1405
|
-
placeholder="Enter primary font color"
|
|
1406
|
-
placeholderTextColor="#999999"
|
|
1407
|
-
/>
|
|
1408
|
-
<View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.primaryFontColor }]} />
|
|
1409
|
-
</View>
|
|
1410
|
-
<Text style={styles.label}>Secondary Font Color</Text>
|
|
1411
|
-
<View style={styles.colorInputContainer}>
|
|
1412
|
-
<TextInput
|
|
1413
|
-
style={[styles.input, { flex: 1 }]}
|
|
1414
|
-
value={androidConfig.appearanceSettings.secondaryFontColor}
|
|
1415
|
-
onChangeText={value => updateAndroidConfigAppearanceSettings('secondaryFontColor', value)}
|
|
1416
|
-
placeholder="Enter secondary font color"
|
|
1417
|
-
placeholderTextColor="#999999"
|
|
1418
|
-
/>
|
|
1419
|
-
<View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.secondaryFontColor }]} />
|
|
1420
|
-
</View>
|
|
1421
|
-
<Text style={styles.label}>Primary Button Background Color</Text>
|
|
1422
|
-
<View style={styles.colorInputContainer}>
|
|
1423
|
-
<TextInput
|
|
1424
|
-
style={[styles.input, { flex: 1 }]}
|
|
1425
|
-
value={androidConfig.appearanceSettings.primaryButtonBackgroundColor}
|
|
1426
|
-
onChangeText={value => updateAndroidConfigAppearanceSettings('primaryButtonBackgroundColor', value)}
|
|
1427
|
-
placeholder="Enter primary button background color"
|
|
1428
|
-
placeholderTextColor="#999999"
|
|
1429
|
-
/>
|
|
1430
|
-
<View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.primaryButtonBackgroundColor }]} />
|
|
1431
|
-
</View>
|
|
1432
|
-
<Text style={styles.label}>Primary Button Hover Color</Text>
|
|
1433
|
-
<View style={styles.colorInputContainer}>
|
|
1434
|
-
<TextInput
|
|
1435
|
-
style={[styles.input, { flex: 1 }]}
|
|
1436
|
-
value={androidConfig.appearanceSettings.primaryButtonHoverColor}
|
|
1437
|
-
onChangeText={value => updateAndroidConfigAppearanceSettings('primaryButtonHoverColor', value)}
|
|
1438
|
-
placeholder="Enter primary button hover color"
|
|
1439
|
-
placeholderTextColor="#999999"
|
|
1440
|
-
/>
|
|
1441
|
-
<View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.primaryButtonHoverColor }]} />
|
|
1442
|
-
</View>
|
|
1443
|
-
<Text style={styles.label}>Primary Button Font Color</Text>
|
|
1444
|
-
<View style={styles.colorInputContainer}>
|
|
1445
|
-
<TextInput
|
|
1446
|
-
style={[styles.input, { flex: 1 }]}
|
|
1447
|
-
value={androidConfig.appearanceSettings.primaryButtonFontColor}
|
|
1448
|
-
onChangeText={value => updateAndroidConfigAppearanceSettings('primaryButtonFontColor', value)}
|
|
1449
|
-
placeholder="Enter primary button font color"
|
|
1450
|
-
placeholderTextColor="#999999"
|
|
1451
|
-
/>
|
|
1452
|
-
<View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.primaryButtonFontColor }]} />
|
|
1453
|
-
</View>
|
|
1454
|
-
<Text style={styles.label}>Secondary Button Background Color</Text>
|
|
1455
|
-
<View style={styles.colorInputContainer}>
|
|
1456
|
-
<TextInput
|
|
1457
|
-
style={[styles.input, { flex: 1 }]}
|
|
1458
|
-
value={androidConfig.appearanceSettings.secondaryButtonBackgroundColor}
|
|
1459
|
-
onChangeText={value => updateAndroidConfigAppearanceSettings('secondaryButtonBackgroundColor', value)}
|
|
1460
|
-
placeholder="Enter secondary button background color"
|
|
1461
|
-
placeholderTextColor="#999999"
|
|
1462
|
-
/>
|
|
1463
|
-
<View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.secondaryButtonBackgroundColor }]} />
|
|
1464
|
-
</View>
|
|
1465
|
-
<Text style={styles.label}>Secondary Button Hover Color</Text>
|
|
1466
|
-
<View style={styles.colorInputContainer}>
|
|
1467
|
-
<TextInput
|
|
1468
|
-
style={[styles.input, { flex: 1 }]}
|
|
1469
|
-
value={androidConfig.appearanceSettings.secondaryButtonHoverColor}
|
|
1470
|
-
onChangeText={value => updateAndroidConfigAppearanceSettings('secondaryButtonHoverColor', value)}
|
|
1471
|
-
placeholder="Enter secondary button hover color"
|
|
1472
|
-
placeholderTextColor="#999999"
|
|
1473
|
-
/>
|
|
1474
|
-
<View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.secondaryButtonHoverColor }]} />
|
|
1475
|
-
</View>
|
|
1476
|
-
<Text style={styles.label}>Secondary Button Font Color</Text>
|
|
1477
|
-
<View style={styles.colorInputContainer}>
|
|
1478
|
-
<TextInput
|
|
1479
|
-
style={[styles.input, { flex: 1 }]}
|
|
1480
|
-
value={androidConfig.appearanceSettings.secondaryButtonFontColor}
|
|
1481
|
-
onChangeText={value => updateAndroidConfigAppearanceSettings('secondaryButtonFontColor', value)}
|
|
1482
|
-
placeholder="Enter secondary button font color"
|
|
1483
|
-
placeholderTextColor="#999999"
|
|
1484
|
-
/>
|
|
1485
|
-
<View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.secondaryButtonFontColor }]} />
|
|
1486
|
-
</View>
|
|
1487
|
-
<Text style={styles.label}>Border Radius</Text>
|
|
1488
|
-
<TextInput
|
|
1489
|
-
style={styles.input}
|
|
1490
|
-
value={androidConfig.appearanceSettings.borderRadius}
|
|
1491
|
-
onChangeText={value => updateAndroidConfigAppearanceSettings('borderRadius', value)}
|
|
1492
|
-
placeholder="Enter border radius"
|
|
1493
|
-
placeholderTextColor="#999999"
|
|
1494
|
-
/>
|
|
1495
|
-
<Text style={styles.label}>Font Size</Text>
|
|
1496
|
-
<TextInput
|
|
1497
|
-
style={styles.input}
|
|
1498
|
-
value={androidConfig.appearanceSettings.fontSize}
|
|
1499
|
-
onChangeText={value => updateAndroidConfigAppearanceSettings('fontSize', value)}
|
|
1500
|
-
placeholder="Enter font size"
|
|
1501
|
-
placeholderTextColor="#999999"
|
|
1502
|
-
/>
|
|
1503
|
-
|
|
1504
|
-
</>
|
|
1505
|
-
)}
|
|
1506
|
-
|
|
1507
|
-
{/* iOS Theme Configuration */}
|
|
1508
|
-
{Platform.OS === 'ios' && (
|
|
1509
|
-
<>
|
|
1510
|
-
<Text style={styles.sectionTitle}>Theme Configuration (iOS)</Text>
|
|
1511
|
-
<Text style={styles.label}>Body Background Color</Text>
|
|
1512
|
-
<View style={styles.colorInputContainer}>
|
|
1513
|
-
<TextInput
|
|
1514
|
-
style={[styles.input, { flex: 1 }]}
|
|
1515
|
-
value={themeConfiguration.bodyBackgroundColor}
|
|
1516
|
-
onChangeText={value => updateThemeConfiguration('bodyBackgroundColor', value)}
|
|
1517
|
-
placeholder="Enter body background color"
|
|
1518
|
-
placeholderTextColor="#999999"
|
|
1519
|
-
/>
|
|
1520
|
-
<View style={[styles.colorPreview, { backgroundColor: themeConfiguration.bodyBackgroundColor }]} />
|
|
1521
|
-
</View>
|
|
1522
|
-
<Text style={styles.label}>Container Background Color</Text>
|
|
1523
|
-
<View style={styles.colorInputContainer}>
|
|
1524
|
-
<TextInput
|
|
1525
|
-
style={[styles.input, { flex: 1 }]}
|
|
1526
|
-
value={themeConfiguration.containerBackgroundColor}
|
|
1527
|
-
onChangeText={value => updateThemeConfiguration('containerBackgroundColor', value)}
|
|
1528
|
-
placeholder="Enter container background color"
|
|
1529
|
-
placeholderTextColor="#999999"
|
|
1530
|
-
/>
|
|
1531
|
-
<View style={[styles.colorPreview, { backgroundColor: themeConfiguration.containerBackgroundColor }]} />
|
|
1532
|
-
</View>
|
|
1533
|
-
<Text style={styles.label}>Primary Font Color</Text>
|
|
1534
|
-
<View style={styles.colorInputContainer}>
|
|
1535
|
-
<TextInput
|
|
1536
|
-
style={[styles.input, { flex: 1 }]}
|
|
1537
|
-
value={themeConfiguration.primaryFontColor}
|
|
1538
|
-
onChangeText={value => updateThemeConfiguration('primaryFontColor', value)}
|
|
1539
|
-
placeholder="Enter primary font color"
|
|
1540
|
-
placeholderTextColor="#999999"
|
|
1541
|
-
/>
|
|
1542
|
-
<View style={[styles.colorPreview, { backgroundColor: themeConfiguration.primaryFontColor }]} />
|
|
1543
|
-
</View>
|
|
1544
|
-
<Text style={styles.label}>Secondary Font Color</Text>
|
|
1545
|
-
<View style={styles.colorInputContainer}>
|
|
1546
|
-
<TextInput
|
|
1547
|
-
style={[styles.input, { flex: 1 }]}
|
|
1548
|
-
value={themeConfiguration.secondaryFontColor}
|
|
1549
|
-
onChangeText={value => updateThemeConfiguration('secondaryFontColor', value)}
|
|
1550
|
-
placeholder="Enter secondary font color"
|
|
1551
|
-
placeholderTextColor="#999999"
|
|
1552
|
-
/>
|
|
1553
|
-
<View style={[styles.colorPreview, { backgroundColor: themeConfiguration.secondaryFontColor }]} />
|
|
1554
|
-
</View>
|
|
1555
|
-
<Text style={styles.label}>Primary Button Background Color</Text>
|
|
1556
|
-
<View style={styles.colorInputContainer}>
|
|
1557
|
-
<TextInput
|
|
1558
|
-
style={[styles.input, { flex: 1 }]}
|
|
1559
|
-
value={themeConfiguration.primaryButtonBackgroundColor}
|
|
1560
|
-
onChangeText={value => updateThemeConfiguration('primaryButtonBackgroundColor', value)}
|
|
1561
|
-
placeholder="Enter primary button background color"
|
|
1562
|
-
placeholderTextColor="#999999"
|
|
1563
|
-
/>
|
|
1564
|
-
<View style={[styles.colorPreview, { backgroundColor: themeConfiguration.primaryButtonBackgroundColor }]} />
|
|
1565
|
-
</View>
|
|
1566
|
-
<Text style={styles.label}>Primary Button Hover Color</Text>
|
|
1567
|
-
<View style={styles.colorInputContainer}>
|
|
1568
|
-
<TextInput
|
|
1569
|
-
style={[styles.input, { flex: 1 }]}
|
|
1570
|
-
value={themeConfiguration.primaryButtonHoverColor}
|
|
1571
|
-
onChangeText={value => updateThemeConfiguration('primaryButtonHoverColor', value)}
|
|
1572
|
-
placeholder="Enter primary button hover color"
|
|
1573
|
-
placeholderTextColor="#999999"
|
|
1574
|
-
/>
|
|
1575
|
-
<View style={[styles.colorPreview, { backgroundColor: themeConfiguration.primaryButtonHoverColor }]} />
|
|
1576
|
-
</View>
|
|
1577
|
-
<Text style={styles.label}>Primary Button Font Color</Text>
|
|
1578
|
-
<View style={styles.colorInputContainer}>
|
|
1579
|
-
<TextInput
|
|
1580
|
-
style={[styles.input, { flex: 1 }]}
|
|
1581
|
-
value={themeConfiguration.primaryButtonFontColor}
|
|
1582
|
-
onChangeText={value => updateThemeConfiguration('primaryButtonFontColor', value)}
|
|
1583
|
-
placeholder="Enter primary button font color"
|
|
1584
|
-
placeholderTextColor="#999999"
|
|
1585
|
-
/>
|
|
1586
|
-
<View style={[styles.colorPreview, { backgroundColor: themeConfiguration.primaryButtonFontColor }]} />
|
|
1587
|
-
</View>
|
|
1588
|
-
<Text style={styles.label}>Secondary Button Background Color</Text>
|
|
1589
|
-
<View style={styles.colorInputContainer}>
|
|
1590
|
-
<TextInput
|
|
1591
|
-
style={[styles.input, { flex: 1 }]}
|
|
1592
|
-
value={themeConfiguration.secondaryButtonBackgroundColor}
|
|
1593
|
-
onChangeText={value => updateThemeConfiguration('secondaryButtonBackgroundColor', value)}
|
|
1594
|
-
placeholder="Enter secondary button background color"
|
|
1595
|
-
placeholderTextColor="#999999"
|
|
1596
|
-
/>
|
|
1597
|
-
<View style={[styles.colorPreview, { backgroundColor: themeConfiguration.secondaryButtonBackgroundColor }]} />
|
|
1598
|
-
</View>
|
|
1599
|
-
<Text style={styles.label}>Secondary Button Hover Color</Text>
|
|
1600
|
-
<View style={styles.colorInputContainer}>
|
|
1601
|
-
<TextInput
|
|
1602
|
-
style={[styles.input, { flex: 1 }]}
|
|
1603
|
-
value={themeConfiguration.secondaryButtonHoverColor}
|
|
1604
|
-
onChangeText={value => updateThemeConfiguration('secondaryButtonHoverColor', value)}
|
|
1605
|
-
placeholder="Enter secondary button hover color"
|
|
1606
|
-
placeholderTextColor="#999999"
|
|
1607
|
-
/>
|
|
1608
|
-
<View style={[styles.colorPreview, { backgroundColor: themeConfiguration.secondaryButtonHoverColor }]} />
|
|
1609
|
-
</View>
|
|
1610
|
-
<Text style={styles.label}>Secondary Button Font Color</Text>
|
|
1611
|
-
<View style={styles.colorInputContainer}>
|
|
1612
|
-
<TextInput
|
|
1613
|
-
style={[styles.input, { flex: 1 }]}
|
|
1614
|
-
value={themeConfiguration.secondaryButtonFontColor}
|
|
1615
|
-
onChangeText={value => updateThemeConfiguration('secondaryButtonFontColor', value)}
|
|
1616
|
-
placeholder="Enter secondary button font color"
|
|
1617
|
-
placeholderTextColor="#999999"
|
|
1618
|
-
/>
|
|
1619
|
-
<View style={[styles.colorPreview, { backgroundColor: themeConfiguration.secondaryButtonFontColor }]} />
|
|
1620
|
-
</View>
|
|
1621
|
-
<Text style={styles.label}>Border Radius</Text>
|
|
1622
|
-
<TextInput
|
|
1623
|
-
style={styles.input}
|
|
1624
|
-
value={themeConfiguration.borderRadius}
|
|
1625
|
-
onChangeText={value => updateThemeConfiguration('borderRadius', value)}
|
|
1626
|
-
placeholder="Enter border radius"
|
|
1627
|
-
placeholderTextColor="#999999"
|
|
1628
|
-
/>
|
|
1629
|
-
<Text style={styles.label}>Font Size</Text>
|
|
1630
|
-
<TextInput
|
|
1631
|
-
style={styles.input}
|
|
1632
|
-
value={themeConfiguration.fontSize}
|
|
1633
|
-
onChangeText={value => updateThemeConfiguration('fontSize', value)}
|
|
1634
|
-
placeholder="Enter font size"
|
|
1635
|
-
placeholderTextColor="#999999"
|
|
1636
|
-
/>
|
|
1637
|
-
|
|
1638
|
-
</>
|
|
1639
|
-
)}
|
|
1640
|
-
|
|
1641
|
-
{/* GrailPay Parameters */}
|
|
1642
|
-
<Text style={styles.sectionTitle}>GrailPay Parameters</Text>
|
|
1643
|
-
<Text style={styles.label}>Role</Text>
|
|
1644
|
-
<TextInput
|
|
1645
|
-
style={styles.input}
|
|
1646
|
-
value={grailPayParams.role}
|
|
1647
|
-
onChangeText={value => updateGrailPayParams('role', value)}
|
|
1648
|
-
placeholder="Enter role"
|
|
1649
|
-
placeholderTextColor="#999999"
|
|
1650
|
-
/>
|
|
1651
|
-
<Text style={styles.label}>Timeout</Text>
|
|
1652
|
-
<TextInput
|
|
1653
|
-
style={styles.input}
|
|
1654
|
-
value={String(grailPayParams.timeout)}
|
|
1655
|
-
onChangeText={value => updateGrailPayParams('timeout', parseInt(value) || 10)}
|
|
1656
|
-
placeholder="Enter timeout"
|
|
1657
|
-
placeholderTextColor="#999999"
|
|
1658
|
-
/>
|
|
1659
|
-
<Text style={styles.label}>Is Sandbox</Text>
|
|
1660
|
-
<Switch
|
|
1661
|
-
value={grailPayParams.isSandbox}
|
|
1662
|
-
onValueChange={value => updateGrailPayParams('isSandbox', value)}
|
|
1663
|
-
trackColor={{ false: '#ccc', true: '#2563EB' }}
|
|
1664
|
-
thumbColor={grailPayParams.isSandbox ? '#fff' : '#f4f3f4'}
|
|
1665
|
-
/>
|
|
1666
|
-
<Text style={styles.label}>Branding Name</Text>
|
|
1667
|
-
<TextInput
|
|
1668
|
-
style={styles.input}
|
|
1669
|
-
value={grailPayParams.brandingName}
|
|
1670
|
-
onChangeText={value => updateGrailPayParams('brandingName', value)}
|
|
1671
|
-
placeholder="Enter branding name"
|
|
1672
|
-
placeholderTextColor="#999999"
|
|
1673
|
-
/>
|
|
1674
|
-
<Text style={styles.label}>Finder Subtitle</Text>
|
|
1675
|
-
<TextInput
|
|
1676
|
-
style={styles.input}
|
|
1677
|
-
value={grailPayParams.finderSubtitle}
|
|
1678
|
-
onChangeText={value => updateGrailPayParams('finderSubtitle', value)}
|
|
1679
|
-
placeholder="Enter finder subtitle"
|
|
1680
|
-
placeholderTextColor="#999999"
|
|
1681
|
-
/>
|
|
1682
|
-
<Text style={styles.label}>Search Placeholder</Text>
|
|
1683
|
-
<TextInput
|
|
1684
|
-
style={styles.input}
|
|
1685
|
-
value={grailPayParams.searchPlaceholder}
|
|
1686
|
-
onChangeText={value => updateGrailPayParams('searchPlaceholder', value)}
|
|
1687
|
-
placeholder="Enter search placeholder"
|
|
1688
|
-
placeholderTextColor="#999999"
|
|
1689
|
-
/>
|
|
1690
|
-
|
|
1691
|
-
{/* Recurring Data */}
|
|
1692
|
-
<Text style={styles.sectionTitle}>Recurring Data</Text>
|
|
1693
|
-
<Text style={styles.label}>Allow Cycles (Minimum 2)</Text>
|
|
1694
|
-
<TextInput
|
|
1695
|
-
style={styles.input}
|
|
1696
|
-
value={String(recurringData.allowCycles)}
|
|
1697
|
-
onChangeText={value => updateRecurringData('allowCycles', parseInt(value) || 2)}
|
|
1698
|
-
placeholder="Enter allowed cycles"
|
|
1699
|
-
placeholderTextColor="#999999"
|
|
1700
|
-
/>
|
|
1701
|
-
<Text style={styles.label}>Intervals</Text>
|
|
1702
|
-
<View style={styles.buttonGroup}>
|
|
1703
|
-
<FilledButton
|
|
1704
|
-
title="Daily"
|
|
1705
|
-
onPress={() => toggleInterval('daily')}
|
|
1706
|
-
disabled={loading}
|
|
1707
|
-
style={[
|
|
1708
|
-
{ flex: 1 },
|
|
1709
|
-
recurringData.intervals.includes('daily')
|
|
1710
|
-
? { backgroundColor: '#2563EB' }
|
|
1711
|
-
: { backgroundColor: '#E5E7EB' }
|
|
1712
|
-
]}
|
|
1713
|
-
textStyle={[
|
|
1714
|
-
{ fontSize: 14 },
|
|
1715
|
-
recurringData.intervals.includes('daily')
|
|
1716
|
-
? { color: '#fff' }
|
|
1717
|
-
: { color: '#374151' }
|
|
1718
|
-
]}
|
|
1719
|
-
/>
|
|
1720
|
-
<FilledButton
|
|
1721
|
-
title="Weekly"
|
|
1722
|
-
onPress={() => toggleInterval('weekly')}
|
|
1723
|
-
disabled={loading}
|
|
1724
|
-
style={[
|
|
1725
|
-
{ flex: 1 },
|
|
1726
|
-
recurringData.intervals.includes('weekly')
|
|
1727
|
-
? { backgroundColor: '#2563EB' }
|
|
1728
|
-
: { backgroundColor: '#E5E7EB' }
|
|
1729
|
-
]}
|
|
1730
|
-
textStyle={[
|
|
1731
|
-
{ fontSize: 14 },
|
|
1732
|
-
recurringData.intervals.includes('weekly')
|
|
1733
|
-
? { color: '#fff' }
|
|
1734
|
-
: { color: '#374151' }
|
|
1735
|
-
]}
|
|
1736
|
-
/>
|
|
1737
|
-
<FilledButton
|
|
1738
|
-
title="Monthly"
|
|
1739
|
-
onPress={() => toggleInterval('monthly')}
|
|
1740
|
-
disabled={loading}
|
|
1741
|
-
style={[
|
|
1742
|
-
{ flex: 1 },
|
|
1743
|
-
recurringData.intervals.includes('monthly')
|
|
1744
|
-
? { backgroundColor: '#2563EB' }
|
|
1745
|
-
: { backgroundColor: '#E5E7EB' }
|
|
1746
|
-
]}
|
|
1747
|
-
textStyle={[
|
|
1748
|
-
{ fontSize: 14 },
|
|
1749
|
-
recurringData.intervals.includes('monthly')
|
|
1750
|
-
? { color: '#fff' }
|
|
1751
|
-
: { color: '#374151' }
|
|
1752
|
-
]}
|
|
1753
|
-
/>
|
|
1754
|
-
</View>
|
|
1755
|
-
<Text style={styles.label}>Recurring Start Type</Text>
|
|
1756
|
-
<Dropdown
|
|
1757
|
-
value={recurringData.recurringStartType}
|
|
1758
|
-
onValueChange={value => updateRecurringData('recurringStartType', value)}
|
|
1759
|
-
options={Platform.OS === 'android' ? ['Custom', 'Fixed'] : ['custom', 'fixed']}
|
|
1760
|
-
placeholder="Select start type"
|
|
1761
|
-
/>
|
|
1762
|
-
<Text style={styles.label}>Recurring Start Date</Text>
|
|
1763
|
-
<TextInput
|
|
1764
|
-
style={[styles.input, styles.dateInput]}
|
|
1765
|
-
value={recurringData.recurringStartDate}
|
|
1766
|
-
onChangeText={value => updateRecurringData('recurringStartDate', value)}
|
|
1767
|
-
placeholder="MM/DD/YYYY"
|
|
1768
|
-
placeholderTextColor="#999999"
|
|
1769
|
-
returnKeyType="done"
|
|
1770
|
-
onSubmitEditing={() => Keyboard.dismiss()}
|
|
1771
|
-
/>
|
|
1772
|
-
</>
|
|
1773
|
-
)}
|
|
1774
|
-
|
|
1775
|
-
<Text style={styles.sectionTitle}>SDK Response</Text>
|
|
1776
|
-
<Text selectable style={styles.result}>{result || 'No response yet'}</Text>
|
|
1777
|
-
</ScrollView>
|
|
1778
|
-
</KeyboardAvoidingView>
|
|
1779
|
-
);
|
|
1780
|
-
|
|
1781
|
-
};
|
|
1782
|
-
|
|
1783
|
-
export default App;
|
|
1784
|
-
|
|
1785
|
-
const styles = StyleSheet.create({
|
|
1786
|
-
container: { flex: 1, backgroundColor: '#F9FAFB' },
|
|
1787
|
-
scrollContent: { flexGrow: 1, padding: 20 },
|
|
1788
|
-
title: {
|
|
1789
|
-
fontSize: 24,
|
|
1790
|
-
fontWeight: 'bold',
|
|
1791
|
-
marginBottom: 16,
|
|
1792
|
-
marginTop: 50,
|
|
1793
|
-
textAlign: 'center',
|
|
1794
|
-
},
|
|
1795
|
-
sectionTitle: {
|
|
1796
|
-
fontSize: 20,
|
|
1797
|
-
fontWeight: 'bold',
|
|
1798
|
-
marginTop: 20,
|
|
1799
|
-
marginBottom: 10,
|
|
1800
|
-
},
|
|
1801
|
-
subsectionTitle: {
|
|
1802
|
-
fontSize: 16,
|
|
1803
|
-
fontWeight: '600',
|
|
1804
|
-
marginTop: 15,
|
|
1805
|
-
marginBottom: 8,
|
|
1806
|
-
color: '#374151',
|
|
1807
|
-
},
|
|
1808
|
-
input: {
|
|
1809
|
-
height: 40,
|
|
1810
|
-
borderColor: '#ccc',
|
|
1811
|
-
borderWidth: 1,
|
|
1812
|
-
borderRadius: 5,
|
|
1813
|
-
paddingHorizontal: 10,
|
|
1814
|
-
marginBottom: 10,
|
|
1815
|
-
backgroundColor: '#FFFFFF',
|
|
1816
|
-
color: '#000000',
|
|
1817
|
-
},
|
|
1818
|
-
dateInput: {
|
|
1819
|
-
backgroundColor: '#fff',
|
|
1820
|
-
shadowColor: '#000',
|
|
1821
|
-
shadowOffset: {
|
|
1822
|
-
width: 0,
|
|
1823
|
-
height: 1,
|
|
1824
|
-
},
|
|
1825
|
-
shadowOpacity: 0.1,
|
|
1826
|
-
shadowRadius: 2,
|
|
1827
|
-
elevation: 2,
|
|
1828
|
-
},
|
|
1829
|
-
pickerContainer: {
|
|
1830
|
-
marginBottom: 20,
|
|
1831
|
-
},
|
|
1832
|
-
toggleContainer: {
|
|
1833
|
-
flexDirection: 'row',
|
|
1834
|
-
justifyContent: 'space-between',
|
|
1835
|
-
alignItems: 'center',
|
|
1836
|
-
marginBottom: 10,
|
|
1837
|
-
},
|
|
1838
|
-
label: {
|
|
1839
|
-
fontSize: 16,
|
|
1840
|
-
fontWeight: '500',
|
|
1841
|
-
marginBottom: 5,
|
|
1842
|
-
},
|
|
1843
|
-
buttonGroup: {
|
|
1844
|
-
flexDirection: 'row',
|
|
1845
|
-
justifyContent: 'space-between',
|
|
1846
|
-
marginBottom: 20,
|
|
1847
|
-
marginTop: 20,
|
|
1848
|
-
gap: 10,
|
|
443
|
+
The provided `App.js` includes custom components like `Dropdown` and `FilledButton` for a user-friendly interface. Customize the styles in the `StyleSheet` to match your app's design.
|
|
1849
444
|
|
|
1850
|
-
|
|
1851
|
-
result: {
|
|
1852
|
-
fontSize: 14,
|
|
1853
|
-
fontFamily: 'monospace',
|
|
1854
|
-
color: '#333',
|
|
1855
|
-
marginTop: 20,
|
|
1856
|
-
},
|
|
1857
|
-
environmentButton: {
|
|
1858
|
-
flex: 1,
|
|
1859
|
-
paddingVertical: 10,
|
|
1860
|
-
paddingHorizontal: 20,
|
|
1861
|
-
borderRadius: 5,
|
|
1862
|
-
marginHorizontal: 5,
|
|
1863
|
-
alignItems: 'center',
|
|
1864
|
-
justifyContent: 'center',
|
|
1865
|
-
},
|
|
1866
|
-
buttonText: {
|
|
1867
|
-
color: '#fff',
|
|
1868
|
-
fontSize: 16,
|
|
1869
|
-
fontWeight: '500',
|
|
1870
|
-
},
|
|
1871
|
-
inputContainer: {
|
|
1872
|
-
flexDirection: 'row',
|
|
1873
|
-
alignItems: 'center',
|
|
1874
|
-
borderColor: '#ccc',
|
|
1875
|
-
borderWidth: 1,
|
|
1876
|
-
borderRadius: 5,
|
|
1877
|
-
marginBottom: 10,
|
|
1878
|
-
},
|
|
1879
|
-
eyeButton: {
|
|
1880
|
-
padding: 10,
|
|
1881
|
-
},
|
|
1882
|
-
eyeIcon: {
|
|
1883
|
-
fontSize: 20,
|
|
1884
|
-
color: '#333',
|
|
1885
|
-
},
|
|
1886
|
-
// New styles for FilledButton
|
|
1887
|
-
filledButton: {
|
|
1888
|
-
flex: 1,
|
|
1889
|
-
paddingVertical: 10,
|
|
1890
|
-
paddingHorizontal: 20,
|
|
1891
|
-
borderRadius: 5,
|
|
1892
|
-
marginHorizontal: 5,
|
|
1893
|
-
alignItems: 'center',
|
|
1894
|
-
justifyContent: 'center',
|
|
1895
|
-
backgroundColor: '#2563EB', // Primary color for filled buttons
|
|
1896
|
-
},
|
|
1897
|
-
filledButtonDisabled: {
|
|
1898
|
-
backgroundColor: '#ccc',
|
|
1899
|
-
opacity: 0.7,
|
|
1900
|
-
},
|
|
1901
|
-
filledButtonText: {
|
|
1902
|
-
color: '#fff',
|
|
1903
|
-
fontSize: 16,
|
|
1904
|
-
fontWeight: '500',
|
|
1905
|
-
},
|
|
1906
|
-
filledButtonTextDisabled: {
|
|
1907
|
-
color: '#888',
|
|
1908
|
-
},
|
|
1909
|
-
debugText: {
|
|
1910
|
-
fontSize: 12,
|
|
1911
|
-
color: '#666',
|
|
1912
|
-
fontStyle: 'italic',
|
|
1913
|
-
marginBottom: 5,
|
|
1914
|
-
},
|
|
1915
|
-
// Dropdown Styles
|
|
1916
|
-
dropdownContainer: {
|
|
1917
|
-
position: 'relative',
|
|
1918
|
-
marginBottom: 20,
|
|
1919
|
-
zIndex: 9998,
|
|
1920
|
-
},
|
|
1921
|
-
dropdownButton: {
|
|
1922
|
-
height: 40,
|
|
1923
|
-
borderColor: '#ccc',
|
|
1924
|
-
borderWidth: 1,
|
|
1925
|
-
borderRadius: 5,
|
|
1926
|
-
paddingHorizontal: 10,
|
|
1927
|
-
flexDirection: 'row',
|
|
1928
|
-
justifyContent: 'space-between',
|
|
1929
|
-
alignItems: 'center',
|
|
1930
|
-
backgroundColor: '#fff',
|
|
1931
|
-
},
|
|
1932
|
-
dropdownButtonText: {
|
|
1933
|
-
fontSize: 16,
|
|
1934
|
-
color: '#333',
|
|
1935
|
-
},
|
|
1936
|
-
dropdownArrow: {
|
|
1937
|
-
fontSize: 12,
|
|
1938
|
-
color: '#666',
|
|
1939
|
-
},
|
|
1940
|
-
dropdownOptions: {
|
|
1941
|
-
position: 'absolute',
|
|
1942
|
-
top: 45,
|
|
1943
|
-
left: 0,
|
|
1944
|
-
right: 0,
|
|
1945
|
-
backgroundColor: '#fff',
|
|
1946
|
-
borderColor: '#ccc',
|
|
1947
|
-
borderWidth: 1,
|
|
1948
|
-
borderRadius: 5,
|
|
1949
|
-
zIndex: 9999,
|
|
1950
|
-
maxHeight: 200,
|
|
1951
|
-
elevation: 5,
|
|
1952
|
-
shadowColor: '#000',
|
|
1953
|
-
shadowOffset: {
|
|
1954
|
-
width: 0,
|
|
1955
|
-
height: 2,
|
|
1956
|
-
},
|
|
1957
|
-
shadowOpacity: 0.25,
|
|
1958
|
-
shadowRadius: 3.84,
|
|
1959
|
-
},
|
|
1960
|
-
dropdownScrollView: {
|
|
1961
|
-
maxHeight: 180,
|
|
1962
|
-
},
|
|
1963
|
-
dropdownOption: {
|
|
1964
|
-
paddingVertical: 12,
|
|
1965
|
-
paddingHorizontal: 15,
|
|
1966
|
-
borderBottomWidth: 1,
|
|
1967
|
-
borderBottomColor: '#f0f0f0',
|
|
1968
|
-
backgroundColor: '#fff',
|
|
1969
|
-
},
|
|
1970
|
-
dropdownOptionText: {
|
|
1971
|
-
fontSize: 16,
|
|
1972
|
-
color: '#333',
|
|
1973
|
-
},
|
|
1974
|
-
// Color preview styles
|
|
1975
|
-
colorInputContainer: {
|
|
1976
|
-
flexDirection: 'row',
|
|
1977
|
-
alignItems: 'center',
|
|
1978
|
-
marginBottom: 10,
|
|
1979
|
-
gap: 10,
|
|
1980
|
-
},
|
|
1981
|
-
colorPreview: {
|
|
1982
|
-
width: 30,
|
|
1983
|
-
height: 30,
|
|
1984
|
-
borderRadius: 4,
|
|
1985
|
-
borderWidth: 1,
|
|
1986
|
-
borderColor: '#ccc',
|
|
1987
|
-
},
|
|
1988
|
-
phoneRow: {
|
|
1989
|
-
flexDirection: 'row',
|
|
1990
|
-
alignItems: 'center',
|
|
1991
|
-
marginBottom: 10,
|
|
1992
|
-
gap: 8,
|
|
1993
|
-
},
|
|
1994
|
-
countryCodeInput: {
|
|
1995
|
-
width: 70,
|
|
1996
|
-
height: 40,
|
|
1997
|
-
borderColor: '#ccc',
|
|
1998
|
-
borderWidth: 1,
|
|
1999
|
-
borderRadius: 5,
|
|
2000
|
-
paddingHorizontal: 10,
|
|
2001
|
-
backgroundColor: '#FFFFFF',
|
|
2002
|
-
color: '#000000',
|
|
2003
|
-
textAlign: 'center',
|
|
2004
|
-
alignSelf: 'stretch',
|
|
2005
|
-
marginBottom: 0,
|
|
2006
|
-
},
|
|
2007
|
-
});
|
|
2008
|
-
|
|
2009
|
-
```
|
|
445
|
+
## Notes
|
|
2010
446
|
|
|
2011
|
-
You can send `null` if
|
|
447
|
+
- **Billing Info**: You can send `null` for `billingInfo` if it is not available.
|
|
448
|
+
- **Environment Configuration**: The SDK supports `sandbox` and `staging` environments. Ensure you configure the correct API keys for each environment.
|
|
449
|
+
- **Payment Methods**: The SDK supports `card` and `ach` payment methods (mapped to `Card` and `Bank` on iOS). Toggle them in the UI as needed.
|
|
450
|
+
- **Recurring Payments**: Ensure at least one interval (`daily`, `weekly`, `monthly`) is selected when enabling recurring payments.
|
|
451
|
+
- **Theming**: Customize the appearance using `themeConfiguration` for iOS or `configur.appearanceSettings` for Android to match your app's branding. The parameters are identical for both platforms.
|
|
452
|
+
- **Method Differences**:
|
|
453
|
+
- **Android**: Use `RNEasymerchantsdk.makePayment(config)` with a JSON configuration object.
|
|
454
|
+
- **iOS**: Use `EasyMerchantSdk.billing(...)` with parameters passed directly.
|
|
455
|
+
- iOS supports an additional `paymentReference` method for checking payment status using a reference token.
|
|
2012
456
|
|
|
457
|
+
## Troubleshooting
|
|
2013
458
|
|
|
2014
|
-
|
|
459
|
+
- **iOS Bridge Initialization**: If you see "Failed to retrieve EasyMerchantSdkPlugin instance" in the console, ensure the `easymerchantsdk` pod is correctly installed and linked.
|
|
460
|
+
- **Android Payment Issues**: Verify that `paymentMethod` is correctly formatted (`['card', 'ach']`) and that API keys are valid. Ensure `GITHUB_URL`, `GITHUB_USERNAME`, and `GITHUB_PASSWORD` are correctly set in your Gradle properties.
|
|
461
|
+
- **Pod Installation**: If `pod install` fails, ensure Ruby 3.2.8 is installed and run `pod install --repo-update`.
|