@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.
Files changed (121) hide show
  1. package/README.md +248 -1801
  2. package/android/build/.transforms/15b6a8a60a6b32d0dcaf609723cf365b/transformed/classes/classes_dex/classes.dex +0 -0
  3. package/android/build/.transforms/8508f1428f740032c45a43f48b1bbe1e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$1$1.dex +0 -0
  4. package/android/build/.transforms/8508f1428f740032c45a43f48b1bbe1e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$1.dex +0 -0
  5. package/android/build/.transforms/8508f1428f740032c45a43f48b1bbe1e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$2.dex +0 -0
  6. package/android/build/.transforms/8508f1428f740032c45a43f48b1bbe1e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule.dex +0 -0
  7. package/android/build/.transforms/eef2d06269ef2e204b4f065513034fca/transformed/classes/classes_dex/classes.dex +0 -0
  8. package/android/build/.transforms/ffabb40f9809b32eb9546ce76fc51764/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/reactlibrary/RNEasymerchantsdkModule$1$1.dex +0 -0
  9. package/android/build/.transforms/ffabb40f9809b32eb9546ce76fc51764/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/reactlibrary/RNEasymerchantsdkModule$1.dex +0 -0
  10. package/android/build/.transforms/ffabb40f9809b32eb9546ce76fc51764/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/reactlibrary/RNEasymerchantsdkModule$2.dex +0 -0
  11. package/android/build/.transforms/ffabb40f9809b32eb9546ce76fc51764/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/reactlibrary/RNEasymerchantsdkModule.dex +0 -0
  12. package/android/build/intermediates/aar_main_jar/release/syncReleaseLibJars/classes.jar +0 -0
  13. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  14. package/android/build/intermediates/compile_library_classes_jar/release/bundleLibCompileToJarRelease/classes.jar +0 -0
  15. package/android/build/intermediates/full_jar/release/createFullJarRelease/full.jar +0 -0
  16. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
  17. package/android/build/intermediates/incremental/lintVitalAnalyzeRelease/release-artifact-dependencies.xml +4 -4
  18. package/android/build/intermediates/incremental/lintVitalAnalyzeRelease/release-artifact-libraries.xml +4 -4
  19. package/android/build/intermediates/incremental/release/mergeReleaseResources/compile-file-map.properties +838 -838
  20. package/android/build/intermediates/incremental/release/mergeReleaseResources/merger.xml +3 -3
  21. package/android/build/intermediates/incremental/release/packageReleaseResources/compile-file-map.properties +1 -1
  22. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
  23. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  24. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  25. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  26. package/android/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
  27. package/android/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  28. package/android/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  29. package/android/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  30. package/android/build/intermediates/lint-cache/lintVitalAnalyzeRelease/maven.google/androidx/core/group-index.xml +4 -3
  31. package/android/build/intermediates/lint-cache/lintVitalAnalyzeRelease/maven.google/androidx/lifecycle/group-index.xml +165 -165
  32. package/android/build/intermediates/lint-cache/lintVitalAnalyzeRelease/maven.google/com/android/tools/build/group-index.xml +15 -15
  33. package/android/build/intermediates/lint-cache/lintVitalAnalyzeRelease/sdk_index/snapshot.gz +0 -0
  34. package/android/build/intermediates/lint_model/release/generateReleaseLintModel/release-artifact-dependencies.xml +4 -4
  35. package/android/build/intermediates/lint_model/release/generateReleaseLintModel/release-artifact-libraries.xml +4 -4
  36. package/android/build/intermediates/lint_vital_lint_model/release/generateReleaseLintVitalModel/release-artifact-dependencies.xml +4 -4
  37. package/android/build/intermediates/lint_vital_lint_model/release/generateReleaseLintVitalModel/release-artifact-libraries.xml +4 -4
  38. package/android/build/intermediates/local_aar_for_lint/release/out.aar +0 -0
  39. package/android/build/intermediates/merged_res/release/mergeReleaseResources/layout/activity_card_addition_ifo.xml +1 -0
  40. package/android/build/intermediates/merged_res/release/mergeReleaseResources/layout/activity_card_billing_info.xml +1 -0
  41. package/android/build/intermediates/merged_res/release/mergeReleaseResources/layout/activity_card_scan.xml +1 -0
  42. package/android/build/intermediates/merged_res/release/mergeReleaseResources/layout/activity_payment_done.xml +1 -0
  43. package/android/build/intermediates/merged_res/release/mergeReleaseResources/layout/activity_payment_done_url.xml +1 -0
  44. package/android/build/intermediates/merged_res/release/mergeReleaseResources/layout/activity_payment_error.xml +1 -0
  45. package/android/build/intermediates/merged_res/release/mergeReleaseResources/layout/activity_select_payment_method.xml +1 -0
  46. package/android/build/intermediates/merged_res/release/mergeReleaseResources/layout/activity_verify_email.xml +1 -0
  47. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/multi-v2/values-night-v8.json +19 -19
  48. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/multi-v2/values.json +38 -38
  49. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/anim-v21.json +9 -9
  50. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/anim.json +24 -24
  51. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/animator-v21.json +1 -1
  52. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/animator.json +34 -34
  53. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/color-night-v8.json +3 -3
  54. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/color-v31.json +10 -10
  55. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/color.json +153 -153
  56. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-hdpi-v4.json +1 -1
  57. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-mdpi-v4.json +1 -1
  58. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-v21.json +3 -3
  59. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-v23.json +7 -7
  60. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-xhdpi-v4.json +1 -1
  61. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-xxhdpi-v4.json +1 -1
  62. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable-xxxhdpi-v4.json +1 -1
  63. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/drawable.json +391 -391
  64. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/font.json +9 -9
  65. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/interpolator-v21.json +10 -10
  66. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/interpolator.json +11 -11
  67. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/layout-land.json +3 -3
  68. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/layout-sw600dp-v13.json +2 -2
  69. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/layout-v26.json +1 -1
  70. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/layout.json +98 -98
  71. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-anydpi-v26.json +2 -2
  72. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-hdpi-v4.json +2 -2
  73. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-mdpi-v4.json +2 -2
  74. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-xhdpi-v4.json +2 -2
  75. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-xxhdpi-v4.json +2 -2
  76. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/mipmap-xxxhdpi-v4.json +2 -2
  77. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/raw.json +46 -46
  78. package/android/build/intermediates/merged_res_blame_folder/release/mergeReleaseResources/out/single/xml.json +3 -3
  79. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
  80. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  81. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  82. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  83. package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
  84. package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  85. package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  86. package/android/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  87. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  88. package/android/build/intermediates/runtime_library_classes_jar/release/bundleLibRuntimeToJarRelease/classes.jar +0 -0
  89. package/android/build/intermediates/source_set_path_map/release/mapReleaseSourceSetPaths/file-map.txt +24 -24
  90. package/android/build/intermediates/verified_library_resources/release/verifyReleaseResources/compiled/layout_activity_card_addition_ifo.xml.flat +0 -0
  91. package/android/build/intermediates/verified_library_resources/release/verifyReleaseResources/compiled/layout_activity_card_billing_info.xml.flat +0 -0
  92. package/android/build/intermediates/verified_library_resources/release/verifyReleaseResources/compiled/layout_activity_card_scan.xml.flat +0 -0
  93. package/android/build/intermediates/verified_library_resources/release/verifyReleaseResources/compiled/layout_activity_payment_done.xml.flat +0 -0
  94. package/android/build/intermediates/verified_library_resources/release/verifyReleaseResources/compiled/layout_activity_payment_done_url.xml.flat +0 -0
  95. package/android/build/intermediates/verified_library_resources/release/verifyReleaseResources/compiled/layout_activity_payment_error.xml.flat +0 -0
  96. package/android/build/intermediates/verified_library_resources/release/verifyReleaseResources/compiled/layout_activity_select_payment_method.xml.flat +0 -0
  97. package/android/build/intermediates/verified_library_resources/release/verifyReleaseResources/compiled/layout_activity_verify_email.xml.flat +0 -0
  98. package/android/build/outputs/aar/jimrising_easymerchantsdk-react-native-release.aar +0 -0
  99. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkModule$1$1.class.uniqueId1 +0 -0
  100. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkModule$1.class.uniqueId0 +0 -0
  101. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkModule$2.class.uniqueId3 +0 -0
  102. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkModule.class.uniqueId2 +0 -0
  103. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkPackage.class.uniqueId4 +0 -0
  104. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  105. package/android/build/tmp/compileReleaseJavaWithJavac/previous-compilation-data.bin +0 -0
  106. package/android/build.gradle +1 -1
  107. package/android/src/main/java/com/reactlibrary/RNEasymerchantsdkModule.java +93 -26
  108. package/ios/Classes/EasyMerchantSdk.m +6 -6
  109. package/ios/Classes/EasyMerchantSdk.swift +116 -60
  110. package/ios/Classes/EasyPayViewController.swift +3 -5
  111. package/ios/CustomComponents/CheckboxButton.swift +66 -0
  112. package/ios/Helper/GrailPayHelper.swift +1 -0
  113. package/ios/Models/Request.swift +8 -8
  114. package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +233 -97
  115. package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +376 -104
  116. package/ios/Pods/ViewControllers/OTPVerificationVC.swift +28 -27
  117. package/ios/Pods/ViewControllers/PaymentErrorVC.swift +22 -25
  118. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +575 -190
  119. package/ios/easymerchantsdk.podspec +1 -1
  120. package/ios/easymerchantsdk.storyboard +15 -15
  121. package/package.json +1 -1
package/README.md CHANGED
@@ -1,41 +1,62 @@
1
1
  # EasyMerchantSdk React Native Implementation
2
2
 
3
- To implement the EasyMerchantSdk in your React Native App project. Follow the below steps:
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
- ## Add the sdk path in your project.
6
- To add the path of sdk in your project. Open your `package.json` file and inside the `dependencies` section, add the below code and set the path of the sdk where you store on your disk.
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.7"
11
- },
21
+ "@jimrising/easymerchantsdk-react-native": "^2.1.0"
22
+ }
12
23
  ```
13
24
 
14
- or using command
15
- ```cmd
16
- npm i @jimrising/easymerchantsdk-react-native
25
+ Alternatively, install it using the following command:
26
+
27
+ ```bash
28
+ npm install @jimrising/easymerchantsdk-react-native
17
29
  ```
18
30
 
19
- ## Changes in android side.
20
- Now open your `android` folder and there is a `build.gradle` file. Open it and add the below code in it.
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
- ## Changes in IOS side.
32
- Add below content inside the AppDelegate.swift File :-
53
+ 3. Sync your project with Gradle to apply the changes.
33
54
 
34
- ## Requirements
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", // replace it with your app name
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
- if let easyMerchantSdkPlugin = bridge?.module(for: EasyMerchantSdkPlugin.self) as? EasyMerchantSdkPlugin {
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
- inside the PodFile add below
93
- ```pod
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
- require_relative '../node_modules/react-native/scripts/react_native_pods'
96
- require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
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
- platform :ios, 16.0
99
-
100
- pod 'easymerchantsdk', :path => '../node_modules/easymerchantsdk-react-native/ios'
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
- ## How to call the sdk in `App.js`.
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
- // Dropdown Component
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
- // Custom Filled Button Component
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: 'San Fran, Punjab',
254
- country: 'USA',
255
- state: 'California',
256
- city: 'Paris',
257
- postal_code: '234234',
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
- themeConfiguration: {
280
- bodyBackgroundColor: '#1E3A8A',
281
- containerBackgroundColor: '#1E40AF',
282
- primaryFontColor: '#FFFFFF',
283
- secondaryFontColor: '#BFDBFE',
284
- primaryButtonBackgroundColor: '#3B82F6',
285
- primaryButtonHoverColor: '#2563EB',
286
- primaryButtonFontColor: '#FFFFFF',
287
- secondaryButtonBackgroundColor: '#1D4ED8',
288
- secondaryButtonHoverColor: '#1E40AF',
289
- secondaryButtonFontColor: '#FFFFFF',
290
- borderRadius: '8',
291
- fontSize: '14',
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
- recurringStartType: Platform.OS === 'android' ? ['Custom', 'Fixed'] : ['custom', 'fixed'],
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
- androidConfig: {
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
- const App = () => {
354
- const [amount, setAmount] = useState(externalConfig.amount);
355
- const [email, setEmail] = useState(externalConfig.email);
356
- const [name, setName] = useState('');
357
- const [environment, setEnvironment] = useState('sandbox');
358
- const [isRecurring, setIsRecurring] = useState(externalConfig.isRecurring);
359
- const [isAuthenticatedACH, setAuthenticatedACH] = useState(externalConfig.isAuthenticatedACH);
360
- const [isSecureAuthentication, setSecureAuthentication] = useState(externalConfig.isSecureAuthentication);
361
- const [isBillingVisible, setBillingVisible] = useState(externalConfig.isBillingVisible);
362
- const [isAdditionalVisible, setAdditionalVisible] = useState(externalConfig.isAdditionalVisible);
363
- const [emailEditable, setEmailEditable] = useState(externalConfig.emailEditable); // Android
364
- const [isEmail, setIsEmail] = useState(externalConfig.isEmail); // iOS
365
- const [billingInfo, setBillingInfo] = useState(externalConfig.billingInfo);
366
- const [themeConfiguration, setThemeConfiguration] = useState(externalConfig.themeConfiguration);
367
- const [grailPayParams, setGrailPayParams] = useState(externalConfig.grailPayParams);
368
- const [recurringData, setRecurringData] = useState(externalConfig.recurringData);
369
- const [androidConfig, setAndroidConfig] = useState(externalConfig.androidConfig);
370
- const [result, setResult] = useState('');
371
- const [referenceToken, setReferenceToken] = useState('');
372
- const [loading, setLoading] = useState(false);
373
- const [showSecretKey, setShowSecretKey] = useState(false);
374
- const metadata = {
375
- metaKey: 'metaValue',
376
- metaKLey1: 'metaValue1',
377
- metaKLey2: 'metaValue2',
378
- metaKLey3: 'metaValue3',
379
- metaKLey4: 'metaValue4',
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
- const [apiKeys, setApiKeys] = useState({
383
- sandbox: {
384
- apiKey: 'sandboxApiKey',
385
- secretKey: 'sandboxSecretKey',
386
- },
387
- staging: {
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
- updateEnvironment();
431
- }, [environment, apiKeys]);
334
+ #### iOS: `handleIosBilling`
432
335
 
433
- useEffect(() => {
434
- if (Platform.OS !== 'android') {
435
- console.log('Event listeners skipped: not Android');
436
- return;
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
- if (!RNEasymerchantsdk) {
440
- console.warn('RNEasymerchantsdk native module is not available.');
441
- return;
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
- const easyMerchantEvents = new NativeEventEmitter(RNEasymerchantsdk);
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
- const successSub = easyMerchantEvents.addListener('PaymentSuccess', (data) => {
447
- console.log('Payment success event:', data);
393
+ setLoading(true);
394
+ const timeoutId = setTimeout(() => setLoading(false), 3000);
448
395
 
449
- try {
450
- const parsed = JSON.parse(data.response);
451
- console.log('Parsed PaymentSuccess:', parsed);
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
- const billing = safeParseMaybeJSON(parsed.billingInfo);
454
- const additional = safeParseMaybeJSON(parsed.additional_info);
409
+ ### 4. Event Listeners for Android
455
410
 
456
- console.log('Billing Info:', billing);
457
- console.log('Additional Info:', additional);
411
+ Set up event listeners for payment success, status, and errors using `NativeEventEmitter` (Android only):
458
412
 
459
- // Replace raw with parsed versions
460
- parsed.billingInfo = billing;
461
- parsed.additional_info = additional;
413
+ ```javascript
414
+ useEffect(() => {
415
+ if (Platform.OS !== 'android') return;
462
416
 
463
- setResult(JSON.stringify(parsed, null, 2));
464
- } catch (err) {
465
- console.error('Error parsing PaymentSuccess response:', err);
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
- console.log('Raw Payment status event:', data);
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
- console.log('Payment status error event:', data);
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
- const safeParseMaybeJSON = (value) => {
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 billing info not available.
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`.