@jimrising/easymerchantsdk-react-native 2.0.4 → 2.0.6

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 (51) hide show
  1. package/README.md +1003 -406
  2. package/android/.gradle/8.10/checksums/checksums.lock +0 -0
  3. package/android/.gradle/8.10/dependencies-accessors/gc.properties +0 -0
  4. package/android/.gradle/8.10/fileChanges/last-build.bin +0 -0
  5. package/android/.gradle/8.10/fileHashes/fileHashes.bin +0 -0
  6. package/android/.gradle/8.10/fileHashes/fileHashes.lock +0 -0
  7. package/android/.gradle/8.10/gc.properties +0 -0
  8. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  9. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  10. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  11. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  12. package/android/.gradle/8.9/gc.properties +0 -0
  13. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  14. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  15. package/android/.gradle/nb-cache/trust/0B5D6BE682AD6AEE9815EC13516BF075752CAE5AD5BECDCC00315C37622C2FD3 +1 -0
  16. package/android/.gradle/vcs-1/gc.properties +0 -0
  17. package/android/build/.transforms/664c2535aec304a21f4de38c6fe405c3/results.bin +1 -0
  18. package/android/build/.transforms/664c2535aec304a21f4de38c6fe405c3/transformed/classes/classes_dex/classes.dex +0 -0
  19. package/android/build/.transforms/8b9a31b6d3889f12f1bd8cedf6c6f513/results.bin +1 -0
  20. package/android/build/.transforms/8b9a31b6d3889f12f1bd8cedf6c6f513/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/BuildConfig.dex +0 -0
  21. package/android/build/.transforms/8b9a31b6d3889f12f1bd8cedf6c6f513/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$1$1.dex +0 -0
  22. package/android/build/.transforms/8b9a31b6d3889f12f1bd8cedf6c6f513/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$1.dex +0 -0
  23. package/android/build/.transforms/8b9a31b6d3889f12f1bd8cedf6c6f513/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$2.dex +0 -0
  24. package/android/build/.transforms/8b9a31b6d3889f12f1bd8cedf6c6f513/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule.dex +0 -0
  25. package/android/build/.transforms/8b9a31b6d3889f12f1bd8cedf6c6f513/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkPackage.dex +0 -0
  26. package/android/build/.transforms/8b9a31b6d3889f12f1bd8cedf6c6f513/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +0 -0
  27. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  28. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
  29. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +1 -1
  30. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
  31. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  32. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  33. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  34. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
  35. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  36. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  37. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  38. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  39. package/android/build/outputs/logs/manifest-merger-debug-report.txt +10 -10
  40. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  41. package/android/build.gradle +1 -1
  42. package/android/src/main/java/com/reactlibrary/RNEasymerchantsdkModule.java +9 -0
  43. package/ios/Classes/EasyMerchantSdk.m +2 -0
  44. package/ios/Classes/EasyMerchantSdk.swift +3 -1
  45. package/ios/Models/Request.swift +50 -3
  46. package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +74 -6
  47. package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +92 -20
  48. package/ios/Pods/ViewControllers/OTPVerificationVC.swift +28 -2
  49. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +73 -564
  50. package/ios/easymerchantsdk.podspec +1 -1
  51. package/package.json +1 -1
package/README.md CHANGED
@@ -7,7 +7,7 @@ To add the path of sdk in your project. Open your `package.json` file and inside
7
7
 
8
8
  ```json
9
9
  "dependencies": {
10
- "@jimrising/easymerchantsdk-react-native": "^2.0.4"
10
+ "@jimrising/easymerchantsdk-react-native": "^2.0.6"
11
11
  },
12
12
  ```
13
13
 
@@ -129,10 +129,121 @@ import {
129
129
  Switch,
130
130
  TouchableOpacity,
131
131
  NativeEventEmitter,
132
+ KeyboardAvoidingView,
133
+ Keyboard,
134
+ TouchableWithoutFeedback,
135
+ Modal,
132
136
  } from 'react-native';
133
137
 
134
138
  const { RNEasymerchantsdk, EasyMerchantSdk } = NativeModules;
135
139
 
140
+ // Dropdown Component
141
+ const Dropdown = ({ value, onValueChange, options, placeholder }) => {
142
+ const [isOpen, setIsOpen] = useState(false);
143
+
144
+ // Handler to close modal when tapping outside
145
+ const handleClose = () => setIsOpen(false);
146
+
147
+ return (
148
+ <View style={styles.dropdownContainer} pointerEvents="box-none">
149
+ <TouchableOpacity
150
+ style={styles.dropdownButton}
151
+ onPress={() => setIsOpen(!isOpen)}
152
+ >
153
+ <Text style={styles.dropdownButtonText}>
154
+ {value || placeholder}
155
+ </Text>
156
+ <Text style={styles.dropdownArrow}>
157
+ {isOpen ? '▲' : '▼'}
158
+ </Text>
159
+ </TouchableOpacity>
160
+ {Platform.OS === 'android' ? (
161
+ <Modal
162
+ visible={isOpen}
163
+ transparent={true}
164
+ animationType="fade"
165
+ onRequestClose={handleClose}
166
+ >
167
+ <TouchableWithoutFeedback onPress={handleClose}>
168
+ <View style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.2)' }}>
169
+ <View style={[styles.dropdownOptions, { position: 'absolute', top: '30%', left: 20, right: 20, maxHeight: 300, elevation: 20 }]}>
170
+ <ScrollView
171
+ style={[styles.dropdownScrollView, { maxHeight: 280 }]}
172
+ showsVerticalScrollIndicator={true}
173
+ persistentScrollbar={true}
174
+ nestedScrollEnabled={true}
175
+ keyboardShouldPersistTaps="handled"
176
+ >
177
+ {options.map((option, index) => (
178
+ <TouchableOpacity
179
+ key={index}
180
+ style={styles.dropdownOption}
181
+ onPress={() => {
182
+ onValueChange(option);
183
+ setIsOpen(false);
184
+ }}
185
+ >
186
+ <Text style={styles.dropdownOptionText}>{option}</Text>
187
+ </TouchableOpacity>
188
+ ))}
189
+ </ScrollView>
190
+ </View>
191
+ </View>
192
+ </TouchableWithoutFeedback>
193
+ </Modal>
194
+ ) : (
195
+ isOpen && (
196
+ <View style={styles.dropdownOptions}>
197
+ <ScrollView
198
+ style={styles.dropdownScrollView}
199
+ showsVerticalScrollIndicator={true}
200
+ persistentScrollbar={true}
201
+ nestedScrollEnabled={true}
202
+ keyboardShouldPersistTaps="handled"
203
+ >
204
+ {options.map((option, index) => (
205
+ <TouchableOpacity
206
+ key={index}
207
+ style={styles.dropdownOption}
208
+ onPress={() => {
209
+ onValueChange(option);
210
+ setIsOpen(false);
211
+ }}
212
+ >
213
+ <Text style={styles.dropdownOptionText}>{option}</Text>
214
+ </TouchableOpacity>
215
+ ))}
216
+ </ScrollView>
217
+ </View>
218
+ )
219
+ )}
220
+ </View>
221
+ );
222
+ };
223
+
224
+ // Custom Filled Button Component
225
+ const FilledButton = ({ title, onPress, disabled = false, style = {}, textStyle = {} }) => {
226
+ return (
227
+ <TouchableOpacity
228
+ style={[
229
+ styles.filledButton,
230
+ disabled && styles.filledButtonDisabled,
231
+ style,
232
+ ]}
233
+ onPress={onPress}
234
+ disabled={disabled}
235
+ >
236
+ <Text style={[
237
+ styles.filledButtonText,
238
+ disabled && styles.filledButtonTextDisabled,
239
+ textStyle,
240
+ ]}>
241
+ {title}
242
+ </Text>
243
+ </TouchableOpacity>
244
+ );
245
+ };
246
+
136
247
  const externalConfig = {
137
248
  amount: '',
138
249
  email: '',
@@ -141,8 +252,8 @@ const externalConfig = {
141
252
  isSecureAuthentication: false,
142
253
  isBillingVisible: false,
143
254
  isAdditionalVisible: false,
144
- emailEditable: true,
145
- isEmail: true,
255
+ emailEditable: true,
256
+ isEmail: true,
146
257
  billingInfo: {
147
258
  visibility: { billing: false, additional: false },
148
259
  billing: {
@@ -160,34 +271,30 @@ const externalConfig = {
160
271
  postal_code: true,
161
272
  },
162
273
  additional: {
163
- name: 'Test User',
164
- email_address: 'test@gmail.com',
165
- phone_number: '21408713290',
274
+
275
+ phone_number: '2140871329',
166
276
  description: 'Test',
167
277
  },
168
278
  additionalRequired: {
169
- name: true,
170
- email_address: true,
279
+
171
280
  phone_number: true,
172
281
  description: false,
173
282
  },
174
283
  },
175
- themeConfiguration: {
176
- bodyBackgroundColor: '#0f1715',
177
- containerBackgroundColor: '#152321',
178
- primaryFontColor: '#FFFFFF',
179
- secondaryFontColor: '#A0B5A4',
180
- primaryButtonBackgroundColor: '#10B981',
181
- primaryButtonHoverColor: '#059669',
182
- primaryButtonFontColor: '#FFFFFF',
183
- secondaryButtonBackgroundColor: '#374151',
184
- secondaryButtonHoverColor: '#4B5563',
185
- secondaryButtonFontColor: '#E5E7EB',
186
- borderRadius: '8',
187
- fontSize: '14',
188
- fontWeight: 500,
189
- fontFamily: '"Inter", sans-serif',
190
- },
284
+ themeConfiguration: {
285
+ bodyBackgroundColor: '#1E3A8A',
286
+ containerBackgroundColor: '#1E40AF',
287
+ primaryFontColor: '#FFFFFF',
288
+ secondaryFontColor: '#BFDBFE',
289
+ primaryButtonBackgroundColor: '#3B82F6',
290
+ primaryButtonHoverColor: '#2563EB',
291
+ primaryButtonFontColor: '#FFFFFF',
292
+ secondaryButtonBackgroundColor: '#1D4ED8',
293
+ secondaryButtonHoverColor: '#1E40AF',
294
+ secondaryButtonFontColor: '#FFFFFF',
295
+ borderRadius: '8',
296
+ fontSize: '14',
297
+ },
191
298
  grailPayParams: {
192
299
  role: 'business',
193
300
  timeout: 10,
@@ -198,9 +305,9 @@ const externalConfig = {
198
305
  },
199
306
  recurringData: {
200
307
  allowCycles: 2,
201
- intervals: ['daily','weekly', 'monthly'],
308
+ intervals: ['daily', 'weekly', 'monthly'],
202
309
  recurringStartType: 'custom',
203
- recurringStartDate: '07/08/2030',
310
+ recurringStartDate: new Date().toLocaleDateString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' }),
204
311
  },
205
312
  androidConfig: {
206
313
  currency: 'usd',
@@ -211,7 +318,7 @@ const externalConfig = {
211
318
  showTotal: true,
212
319
  showSubmitButton: true,
213
320
  paymentMethod: ['card', 'ach'],
214
- name: 'Pavan',
321
+ name: '',
215
322
  fields: {
216
323
  visibility: { billing: false, additional: false },
217
324
  billing: [
@@ -222,28 +329,27 @@ const externalConfig = {
222
329
  { name: 'postal_code', required: true, value: '1432456' },
223
330
  ],
224
331
  additional: [
225
- { name: 'name', required: true, value: 'Test User 7' },
226
- { name: 'email_address', required: true, value: 'usertest@gmail.com' },
332
+
227
333
  { name: 'phone_number', required: true, value: '8978967895' },
228
334
  { name: 'description', required: true, value: 'Hi This is description' },
229
335
  ],
230
336
  },
231
337
  appearanceSettings: {
232
338
  theme: 'dark',
233
- bodyBackgroundColor: '#121212',
234
- containerBackgroundColor: '#1E1E1E',
339
+ bodyBackgroundColor: '#0F5132',
340
+ containerBackgroundColor: '#166534',
235
341
  primaryFontColor: '#FFFFFF',
236
- secondaryFontColor: '#B0B0B0',
237
- primaryButtonBackgroundColor: '#2563EB',
238
- primaryButtonHoverColor: '#1D4ED8',
342
+ secondaryFontColor: '#D1FAE5',
343
+ primaryButtonBackgroundColor: '#059669',
344
+ primaryButtonHoverColor: '#047857',
239
345
  primaryButtonFontColor: '#FFFFFF',
240
- secondaryButtonBackgroundColor: '#374151',
241
- secondaryButtonHoverColor: '#4B5563',
242
- secondaryButtonFontColor: '#E5E7EB',
346
+ secondaryButtonBackgroundColor: '#065F46',
347
+ secondaryButtonHoverColor: '#064E3B',
348
+ secondaryButtonFontColor: '#FFFFFF',
243
349
  borderRadius: '8',
244
350
  fontSize: '14',
245
- fontWeight: '500',
246
- fontFamily: 'Inter, sans-serif',
351
+ fontWeight: 500,
352
+ fontFamily: '"Inter", sans-serif',
247
353
  },
248
354
  },
249
355
  };
@@ -251,6 +357,7 @@ const externalConfig = {
251
357
  const App = () => {
252
358
  const [amount, setAmount] = useState(externalConfig.amount);
253
359
  const [email, setEmail] = useState(externalConfig.email);
360
+ const [name, setName] = useState('');
254
361
  const [environment, setEnvironment] = useState('sandbox');
255
362
  const [isRecurring, setIsRecurring] = useState(externalConfig.isRecurring);
256
363
  const [isAuthenticatedACH, setAuthenticatedACH] = useState(externalConfig.isAuthenticatedACH);
@@ -268,6 +375,13 @@ const App = () => {
268
375
  const [referenceToken, setReferenceToken] = useState('');
269
376
  const [loading, setLoading] = useState(false);
270
377
  const [showSecretKey, setShowSecretKey] = useState(false);
378
+ const metadata = {
379
+ metaKey: 'metaValue',
380
+ metaKLey1: 'metaValue1',
381
+ metaKLey2: 'metaValue2',
382
+ metaKLey3: 'metaValue3',
383
+ metaKLey4: 'metaValue4',
384
+ };
271
385
 
272
386
  const [apiKeys, setApiKeys] = useState({
273
387
  sandbox: {
@@ -282,6 +396,21 @@ const [apiKeys, setApiKeys] = useState({
282
396
  const [isEnvironmentLoading, setIsEnvironmentLoading] = useState(false);
283
397
  const [showConfig, setShowConfig] = useState(true);
284
398
 
399
+ // Debug useEffect to track payment method changes
400
+ useEffect(() => {
401
+ console.log('Payment methods state changed:', androidConfig.paymentMethod);
402
+ }, [androidConfig.paymentMethod]);
403
+
404
+ // Debug useEffect to track theme configuration changes
405
+ useEffect(() => {
406
+ console.log('Theme configuration changed:', themeConfiguration);
407
+ }, [themeConfiguration]);
408
+
409
+ // Debug useEffect to track Android appearance settings changes
410
+ useEffect(() => {
411
+ console.log('Android appearance settings changed:', androidConfig.appearanceSettings);
412
+ }, [androidConfig.appearanceSettings]);
413
+
285
414
 
286
415
  useEffect(() => {
287
416
  const updateEnvironment = async () => {
@@ -450,12 +579,36 @@ const safeParseMaybeJSON = (value) => {
450
579
  };
451
580
 
452
581
  const togglePaymentMethod = (method) => {
453
- setAndroidConfig(prev => ({
454
- ...prev,
455
- paymentMethod: prev.paymentMethod.includes(method)
456
- ? prev.paymentMethod.filter(m => m !== method)
457
- : [...prev.paymentMethod, method],
458
- }));
582
+ console.log('Toggling payment method:', method);
583
+ setAndroidConfig(prev => {
584
+ const currentMethods = prev.paymentMethod;
585
+ console.log('Current payment methods before toggle:', currentMethods);
586
+ const isCurrentlySelected = currentMethods.includes(method);
587
+ console.log('Is currently selected:', isCurrentlySelected);
588
+
589
+ let newMethods;
590
+ if (isCurrentlySelected) {
591
+ // If deselecting and it's the only method, don't allow deselection
592
+ if (currentMethods.length === 1) {
593
+ console.log('Cannot deselect last payment method, keeping current state');
594
+ return prev; // Keep the current state
595
+ }
596
+ // Remove the method
597
+ newMethods = currentMethods.filter(m => m !== method);
598
+ console.log('Removing method, new methods:', newMethods);
599
+ } else {
600
+ // Add the method
601
+ newMethods = [...currentMethods, method];
602
+ console.log('Adding method, new methods:', newMethods);
603
+ }
604
+
605
+ const updatedConfig = {
606
+ ...prev,
607
+ paymentMethod: newMethods,
608
+ };
609
+ console.log('Updated android config payment methods:', updatedConfig.paymentMethod);
610
+ return updatedConfig;
611
+ });
459
612
  };
460
613
 
461
614
  const toggleInterval = (interval) => {
@@ -471,58 +624,126 @@ const safeParseMaybeJSON = (value) => {
471
624
  if (!amount || isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) {
472
625
  return Alert.alert('Error', 'Please enter a valid amount');
473
626
  }
474
- if (!email) {
475
- return Alert.alert('Error', 'Please enter an email address');
627
+
628
+ // Validate that at least one interval is selected when recurring payment is enabled
629
+ if (isRecurring && (!recurringData.intervals || recurringData.intervals.length === 0)) {
630
+ return Alert.alert('Error', 'Please select at least one interval for recurring payment');
476
631
  }
477
632
 
478
633
  setLoading(true);
479
- if (Platform.OS === 'android') {
480
- await handleAndroidBilling();
481
- } else {
482
- await handleIosBilling();
634
+
635
+ // Add a timeout to ensure loading state is reset even if SDK blocks execution
636
+ const timeoutId = setTimeout(() => {
637
+ setLoading(false);
638
+ }, 3000); // 3 second timeout
639
+
640
+ try {
641
+ if (Platform.OS === 'android') {
642
+ await handleAndroidBilling();
643
+ } else {
644
+ await handleIosBilling();
645
+ }
646
+ } finally {
647
+ clearTimeout(timeoutId);
648
+ setLoading(false);
483
649
  }
484
650
  };
485
651
 
486
652
  const handleAndroidBilling = async () => {
653
+
487
654
  const { apiKey, secretKey } = apiKeys[environment];
488
- const config = {
489
- amount,
490
- apiKey: apiKey,
491
- secretKey: apiSecret,
492
- jsonConfig: {
493
- environment,
494
- amount,
495
- tokenOnly: false,
496
- currency: androidConfig.currency,
497
- saveCard: androidConfig.saveCard,
498
- saveAccount: androidConfig.saveAccount,
499
- authenticatedACH: isAuthenticatedACH,
500
- secureAuthentication: isSecureAuthentication,
501
- showReceipt: androidConfig.showReceipt,
502
- showDonate: androidConfig.showDonate,
503
- showTotal: androidConfig.showTotal,
504
- showSubmitButton: androidConfig.showSubmitButton,
505
- paymentMethod: androidConfig.paymentMethod,
506
- emailEditable,
507
- email,
508
- name: androidConfig.name,
509
- fields: {
510
- ...androidConfig.fields,
511
- visibility: {
512
- billing: isBillingVisible,
513
- additional: isAdditionalVisible,
514
- },
515
- },
516
- ...(isRecurring && {
517
- recurring: {
518
- enableRecurring: true,
519
- recurringData,
520
- },
521
- }),
522
- grailPayParams,
523
- appearanceSettings: androidConfig.appearanceSettings,
524
- },
525
- };
655
+
656
+ console.log('Selected payment methods for Android:', androidConfig.paymentMethod);
657
+ console.log('Full androidConfig.paymentMethod:', JSON.stringify(androidConfig.paymentMethod));
658
+
659
+ // Create a fresh copy of payment methods to ensure no reference issues
660
+ const selectedPaymentMethods = [...androidConfig.paymentMethod];
661
+ console.log('Fresh copy of payment methods:', selectedPaymentMethods);
662
+ console.log('Payment method contains "ach":', selectedPaymentMethods.includes('ach'));
663
+ console.log('Payment method contains "card":', selectedPaymentMethods.includes('card'));
664
+
665
+ // Log ACH-specific configurations when ACH is selected
666
+ if (selectedPaymentMethods.includes('ach')) {
667
+ console.log('=== ACH CONFIGURATION DEBUG ===');
668
+ console.log('authenticatedACH setting:', isAuthenticatedACH);
669
+ console.log('secureAuthentication setting:', isSecureAuthentication);
670
+ console.log('saveAccount setting:', androidConfig.saveAccount);
671
+ }
672
+
673
+ // Try different payment method format for ACH-only scenario
674
+ let finalPaymentMethods = selectedPaymentMethods;
675
+ if (selectedPaymentMethods.length === 1 && selectedPaymentMethods.includes('ach')) {
676
+ // Try using a different format for ACH-only
677
+ finalPaymentMethods = ['ach'];
678
+ console.log('Using ACH-only format:', finalPaymentMethods);
679
+ }
680
+
681
+ const config = {
682
+ amount,
683
+ apiKey: apiKey,
684
+ secretKey: secretKey,
685
+ jsonConfig: {
686
+ environment,
687
+ amount,
688
+ tokenOnly: false,
689
+ currency: androidConfig.currency,
690
+ saveCard: androidConfig.saveCard,
691
+ saveAccount: androidConfig.saveAccount,
692
+ authenticatedACH: isAuthenticatedACH,
693
+ secureAuthentication: isSecureAuthentication,
694
+
695
+ showReceipt: androidConfig.showReceipt,
696
+ showDonate: androidConfig.showDonate,
697
+ showTotal: androidConfig.showTotal,
698
+ showSubmitButton: androidConfig.showSubmitButton,
699
+ paymentMethod: finalPaymentMethods,
700
+ // Try different configuration approaches for ACH-only
701
+ ...(finalPaymentMethods.length === 1 && finalPaymentMethods.includes('ach') && {
702
+ paymentMethods: ['ach'],
703
+ allowedPaymentMethods: ['ach'],
704
+ disableCard: true,
705
+ }),
706
+
707
+ emailEditable,
708
+ email,
709
+ name: name,
710
+ fields: {
711
+ ...androidConfig.fields,
712
+ visibility: {
713
+ billing: isBillingVisible,
714
+ additional: isAdditionalVisible,
715
+ },
716
+ },
717
+
718
+ metadata,
719
+
720
+
721
+ ...(isRecurring && {
722
+ recurring: {
723
+ enableRecurring: true,
724
+ recurringData,
725
+ },
726
+ }),
727
+ grailPayParams,
728
+ appearanceSettings: androidConfig.appearanceSettings,
729
+
730
+
731
+ },
732
+ };
733
+
734
+ console.log('ENV API Keys =>', {
735
+ env: environment,
736
+ apiKeyFromEnv: apiKey,
737
+ secretKeyFromEnv: secretKey,
738
+ });
739
+
740
+ console.log('Config API Key =>', config.apiKey);
741
+ console.log('Config Secret Key =>', config.secretKey);
742
+ console.log('Full config being sent to Android SDK:', JSON.stringify(config, null, 2));
743
+ console.log('=== THEME CONFIGURATION DEBUG ===');
744
+ console.log('Android appearanceSettings:', JSON.stringify(androidConfig.appearanceSettings, null, 2));
745
+
746
+
526
747
 
527
748
  try {
528
749
  const response = await RNEasymerchantsdk.makePayment(config);
@@ -546,12 +767,33 @@ setResult(JSON.stringify(parsedResponse, null, 2));
546
767
  };
547
768
 
548
769
  const handleIosBilling = async () => {
770
+
771
+ console.log('Selected payment methods for iOS:', androidConfig.paymentMethod);
772
+ console.log('Full androidConfig.paymentMethod for iOS:', JSON.stringify(androidConfig.paymentMethod));
773
+
774
+ // Create a fresh copy of payment methods for iOS and convert to iOS format
775
+ const selectedPaymentMethodsIOS = [...androidConfig.paymentMethod];
776
+ console.log('Fresh copy of payment methods for iOS:', selectedPaymentMethodsIOS);
777
+ console.log('Payment method contains "ach" (iOS):', selectedPaymentMethodsIOS.includes('ach'));
778
+ console.log('Payment method contains "card" (iOS):', selectedPaymentMethodsIOS.includes('card'));
779
+
780
+ // Convert Android payment method names to iOS format
781
+ let finalPaymentMethodsIOS = selectedPaymentMethodsIOS.map(method => {
782
+ if (method === 'card') return 'Card';
783
+ if (method === 'ach') return 'Bank';
784
+ return method;
785
+ });
786
+
787
+ console.log('Converted payment methods for iOS:', finalPaymentMethodsIOS);
788
+ console.log('=== iOS THEME CONFIGURATION DEBUG ===');
789
+ console.log('iOS themeConfiguration:', JSON.stringify(themeConfiguration, null, 2));
790
+
549
791
  try {
550
792
  const result = await EasyMerchantSdk.billing(
551
793
  amount,
552
794
  androidConfig.currency || 'usd',
553
795
  billingInfo,
554
- androidConfig.paymentMethod,
796
+ finalPaymentMethodsIOS,
555
797
  themeConfiguration,
556
798
  false, // tokenOnly
557
799
  androidConfig.saveCard,
@@ -570,7 +812,8 @@ setResult(JSON.stringify(parsedResponse, null, 2));
570
812
  androidConfig.showSubmitButton,
571
813
  isEmail,
572
814
  email,
573
- androidConfig.name
815
+ name,
816
+ metadata
574
817
  );
575
818
 
576
819
  const refToken = result?.additionalInfo?.threeDSecureStatus?.data?.ref_token;
@@ -603,10 +846,6 @@ setResult(JSON.stringify(parsedResponse, null, 2));
603
846
  setResult('Payment Reference not supported on Android');
604
847
  return;
605
848
  }
606
- if (!referenceToken) {
607
- setResult('No reference token available');
608
- return;
609
- }
610
849
  try {
611
850
  const response = await EasyMerchantSdk.paymentReference(referenceToken);
612
851
  setResult(`Payment Reference:\n${JSON.stringify(response, null, 2)}`);
@@ -618,8 +857,16 @@ setResult(JSON.stringify(parsedResponse, null, 2));
618
857
  };
619
858
 
620
859
  return (
621
- <View style={styles.container}>
622
- <ScrollView contentContainerStyle={styles.scrollContent}>
860
+ <KeyboardAvoidingView
861
+ style={styles.container}
862
+ behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
863
+ keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 20}
864
+ >
865
+ <ScrollView
866
+ contentContainerStyle={styles.scrollContent}
867
+ keyboardShouldPersistTaps="handled"
868
+ showsVerticalScrollIndicator={false}
869
+ >
623
870
  <Text style={styles.title}>EasyMerchant SDK</Text>
624
871
 
625
872
 
@@ -629,6 +876,7 @@ setResult(JSON.stringify(parsedResponse, null, 2));
629
876
  <TextInput
630
877
  style={styles.input}
631
878
  placeholder="Enter amount (e.g., 10.00)"
879
+ placeholderTextColor="#999999"
632
880
  keyboardType="decimal-pad"
633
881
  value={amount}
634
882
  onChangeText={setAmount}
@@ -637,18 +885,39 @@ setResult(JSON.stringify(parsedResponse, null, 2));
637
885
  <TextInput
638
886
  style={styles.input}
639
887
  placeholder="Enter email"
888
+ placeholderTextColor="#999999"
640
889
  keyboardType="email-address"
641
890
  value={email}
642
891
  onChangeText={setEmail}
643
892
  />
893
+ <Text style={styles.label}>Name</Text>
894
+ <TextInput
895
+ style={styles.input}
896
+ placeholder="Enter name"
897
+ placeholderTextColor="#999999"
898
+ value={name}
899
+ onChangeText={setName}
900
+ />
644
901
 
645
- <View style={styles.buttonGroup}>
646
- <Button title="Pay" onPress={handlePayment} />
902
+ <View style={styles.buttonGroup}>
903
+ <FilledButton
904
+ title="Pay"
905
+ onPress={handlePayment}
906
+ disabled={loading}
907
+ />
647
908
  {Platform.OS === 'ios' && (
648
- <Button title="Payment Ref" onPress={handlePaymentReference} disabled={loading} />
909
+ <FilledButton
910
+ title="Payment Ref"
911
+ onPress={handlePaymentReference}
912
+ disabled={loading}
913
+ />
649
914
  )}
650
915
  </View>
651
916
 
917
+
918
+
919
+
920
+
652
921
  <View style={styles.toggleContainer}>
653
922
  <Text style={styles.label}>Show Configurations</Text>
654
923
  <Switch
@@ -706,6 +975,7 @@ setResult(JSON.stringify(parsedResponse, null, 2));
706
975
  }))
707
976
  }
708
977
  placeholder={`Enter ${environment === 'sandbox' ? 'Sandbox' : 'Staging'} API Key`}
978
+ placeholderTextColor="#999999"
709
979
  />
710
980
  <Text style={styles.label}>Secret Key</Text>
711
981
  <View style={styles.inputContainer}>
@@ -719,6 +989,7 @@ setResult(JSON.stringify(parsedResponse, null, 2));
719
989
  }))
720
990
  }
721
991
  placeholder={`Enter ${environment === 'sandbox' ? 'Sandbox' : 'Staging'} Secret Key`}
992
+ placeholderTextColor="#999999"
722
993
  secureTextEntry={!showSecretKey}
723
994
  />
724
995
  <TouchableOpacity
@@ -779,22 +1050,45 @@ setResult(JSON.stringify(parsedResponse, null, 2));
779
1050
  thumbColor={isAdditionalVisible ? '#fff' : '#f4f3f4'}
780
1051
  />
781
1052
  </View>
782
- <Text style={styles.label}>Payment Methods (Shared)</Text>
1053
+ <Text style={styles.label}>Payment Methods (Shared)</Text>
1054
+ <Text style={styles.debugText}>
1055
+ Selected: {androidConfig.paymentMethod.join(', ') || 'None'}
1056
+ </Text>
783
1057
  <View style={styles.buttonGroup}>
784
- <Button
1058
+ <FilledButton
785
1059
  title="Card"
786
1060
  onPress={() => togglePaymentMethod('card')}
787
- color={androidConfig.paymentMethod.includes('card') ? '#ccc' :'#2563EB'}
1061
+ disabled={loading}
1062
+ style={[
1063
+ { flex: 1 },
1064
+ androidConfig.paymentMethod.includes('card')
1065
+ ? { backgroundColor: '#2563EB' }
1066
+ : { backgroundColor: '#E5E7EB' }
1067
+ ]}
1068
+ textStyle={androidConfig.paymentMethod.includes('card')
1069
+ ? { color: '#fff' }
1070
+ : { color: '#374151' }
1071
+ }
788
1072
  />
789
- <Button
1073
+ <FilledButton
790
1074
  title="ACH"
791
1075
  onPress={() => togglePaymentMethod('ach')}
792
- color={androidConfig.paymentMethod.includes('ach') ? '#ccc' :'#2563EB' }
1076
+ disabled={loading}
1077
+ style={[
1078
+ { flex: 1 },
1079
+ androidConfig.paymentMethod.includes('ach')
1080
+ ? { backgroundColor: '#2563EB' }
1081
+ : { backgroundColor: '#E5E7EB' }
1082
+ ]}
1083
+ textStyle={androidConfig.paymentMethod.includes('ach')
1084
+ ? { color: '#fff' }
1085
+ : { color: '#374151' }
1086
+ }
793
1087
  />
794
1088
  </View>
795
1089
  {Platform.OS === 'android' && (
796
1090
  <View style={styles.toggleContainer}>
797
- <Text style={styles.label}>Email Editable (Android)</Text>
1091
+ <Text style={styles.label}>Email Editable</Text>
798
1092
  <Switch
799
1093
  value={emailEditable}
800
1094
  onValueChange={setEmailEditable}
@@ -805,7 +1099,7 @@ setResult(JSON.stringify(parsedResponse, null, 2));
805
1099
  )}
806
1100
  {Platform.OS === 'ios' && (
807
1101
  <View style={styles.toggleContainer}>
808
- <Text style={styles.label}>Allow Email (iOS)</Text>
1102
+ <Text style={styles.label}>Allow Email Editable</Text>
809
1103
  <Switch
810
1104
  value={isEmail}
811
1105
  onValueChange={setIsEmail}
@@ -816,36 +1110,9 @@ setResult(JSON.stringify(parsedResponse, null, 2));
816
1110
  )}
817
1111
 
818
1112
  <Text style={styles.sectionTitle}>Billing Info</Text>
819
- <Text style={styles.label}>Billing Address</Text>
820
- <TextInput
821
- style={styles.input}
822
- value={billingInfo.billing.address}
823
- onChangeText={value => updateBillingInfo('billing', 'address', value)}
824
- />
825
- <Text style={styles.label}>Billing Country</Text>
826
- <TextInput
827
- style={styles.input}
828
- value={billingInfo.billing.country}
829
- onChangeText={value => updateBillingInfo('billing', 'country', value)}
830
- />
831
- <Text style={styles.label}>Billing State</Text>
832
- <TextInput
833
- style={styles.input}
834
- value={billingInfo.billing.state}
835
- onChangeText={value => updateBillingInfo('billing', 'state', value)}
836
- />
837
- <Text style={styles.label}>Billing City</Text>
838
- <TextInput
839
- style={styles.input}
840
- value={billingInfo.billing.city}
841
- onChangeText={value => updateBillingInfo('billing', 'city', value)}
842
- />
843
- <Text style={styles.label}>Billing Postal Code</Text>
844
- <TextInput
845
- style={styles.input}
846
- value={billingInfo.billing.postal_code}
847
- onChangeText={value => updateBillingInfo('billing', 'postal_code', value)}
848
- />
1113
+
1114
+ {/* Billing Required Switches */}
1115
+ <Text style={styles.subsectionTitle}>Billing Required Fields</Text>
849
1116
  <Text style={styles.label}>Billing Required: Address</Text>
850
1117
  <Switch
851
1118
  value={billingInfo.billingRequired.address}
@@ -863,7 +1130,7 @@ setResult(JSON.stringify(parsedResponse, null, 2));
863
1130
  <Text style={styles.label}>Billing Required: State</Text>
864
1131
  <Switch
865
1132
  value={billingInfo.billingRequired.state}
866
- onChangeText={value => updateBillingInfo('billingRequired', 'state', value)}
1133
+ onValueChange={value => updateBillingInfo('billingRequired', 'state', value)}
867
1134
  trackColor={{ false: '#ccc', true: '#2563EB' }}
868
1135
  thumbColor={billingInfo.billingRequired.state ? '#fff' : '#f4f3f4'}
869
1136
  />
@@ -881,44 +1148,125 @@ setResult(JSON.stringify(parsedResponse, null, 2));
881
1148
  trackColor={{ false: '#ccc', true: '#2563EB' }}
882
1149
  thumbColor={billingInfo.billingRequired.postal_code ? '#fff' : '#f4f3f4'}
883
1150
  />
884
- <Text style={styles.label}>Additional: Name</Text>
885
- <TextInput
886
- style={styles.input}
887
- value={billingInfo.additional.name}
888
- onChangeText={value => updateBillingInfo('additional', 'name', value)}
889
- />
890
- <Text style={styles.label}>Additional: Email Address</Text>
891
- <TextInput
892
- style={styles.input}
893
- value={billingInfo.additional.email_address}
894
- onChangeText={value => updateBillingInfo('additional', 'email_address', value)}
895
- />
896
- <Text style={styles.label}>Additional: Phone Number</Text>
897
- <TextInput
898
- style={styles.input}
899
- value={billingInfo.additional.phone_number}
900
- onChangeText={value => updateBillingInfo('additional', 'phone_number', value)}
901
- />
902
- <Text style={styles.label}>Additional: Description</Text>
903
- <TextInput
904
- style={styles.input}
905
- value={billingInfo.additional.description}
906
- onChangeText={value => updateBillingInfo('additional', 'description', value)}
907
- />
908
- <Text style={styles.label}>Additional Required: Name</Text>
909
- <Switch
910
- value={billingInfo.additionalRequired.name}
911
- onValueChange={value => updateBillingInfo('additionalRequired', 'name', value)}
912
- trackColor={{ false: '#ccc', true: '#2563EB' }}
913
- thumbColor={billingInfo.additionalRequired.name ? '#fff' : '#f4f3f4'}
914
- />
915
- <Text style={styles.label}>Additional Required: Email Address</Text>
916
- <Switch
917
- value={billingInfo.additionalRequired.email_address}
918
- onValueChange={value => updateBillingInfo('additionalRequired', 'email_address', value)}
919
- trackColor={{ false: '#ccc', true: '#2563EB' }}
920
- thumbColor={billingInfo.additionalRequired.email_address ? '#fff' : '#f4f3f4'}
921
- />
1151
+
1152
+ {/* Billing Fields Section */}
1153
+ <Text style={styles.subsectionTitle}>Billing Fields</Text>
1154
+ {billingInfo.billingRequired.address && (
1155
+ <>
1156
+ <Text style={styles.label}>Billing Address</Text>
1157
+ <TextInput
1158
+ style={styles.input}
1159
+ value={billingInfo.billing.address}
1160
+ onChangeText={value => updateBillingInfo('billing', 'address', value)}
1161
+ placeholder="Enter billing address"
1162
+ placeholderTextColor="#999999"
1163
+ />
1164
+ </>
1165
+ )}
1166
+ {billingInfo.billingRequired.country && (
1167
+ <>
1168
+ <Text style={styles.label}>Billing Country</Text>
1169
+ <Dropdown
1170
+ value={billingInfo.billing.country}
1171
+ onValueChange={value => updateBillingInfo('billing', 'country', value)}
1172
+ options={[
1173
+ "Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra",
1174
+ "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina",
1175
+ "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan",
1176
+ "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus",
1177
+ "Belgium", "Belize", "Benin", "Bermuda", "Bhutan",
1178
+ "Bolivia", "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil",
1179
+ "British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi",
1180
+ "Cabo Verde", "Cambodia", "Cameroon", "Canada", "Cayman Islands",
1181
+ "Central African Republic", "Chad", "Chile", "China", "Christmas Island",
1182
+ "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo", "Congo, Democratic Republic of the",
1183
+ "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Curaçao",
1184
+ "Cyprus", "Czech Republic", "Côte d'Ivoire", "Denmark", "Djibouti",
1185
+ "Dominica", "Dominican Republic", "Ecuador", "Egypt", "El Salvador",
1186
+ "Equatorial Guinea", "Eritrea", "Estonia", "Eswatini", "Ethiopia",
1187
+ "Falkland Islands (Malvinas)", "Faroe Islands", "Fiji", "Finland", "France",
1188
+ "French Guiana", "French Polynesia", "French Southern Territories", "Gabon", "Gambia",
1189
+ "Georgia", "Germany", "Ghana", "Gibraltar", "Greece",
1190
+ "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala",
1191
+ "Guernsey", "Guinea", "Guinea-Bissau", "Guyana", "Haiti",
1192
+ "Heard Island and McDonald Islands", "Holy See", "Honduras", "Hong Kong", "Hungary",
1193
+ "Iceland", "India", "Indonesia", "Iran", "Iraq",
1194
+ "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica",
1195
+ "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
1196
+ "Kiribati", "Korea (Democratic People's Republic of)", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
1197
+ "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia",
1198
+ "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macao",
1199
+ "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali",
1200
+ "Malta", "Marshall Islands", "Martinique", "Mauritania", "Mauritius",
1201
+ "Mayotte", "Mexico", "Micronesia (Federated States of)", "Moldova", "Monaco",
1202
+ "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique",
1203
+ "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands",
1204
+ "New Caledonia", "New Zealand", "Nicaragua", "Niger", "Nigeria",
1205
+ "Niue", "Norfolk Island", "North Macedonia", "Northern Mariana Islands", "Norway",
1206
+ "Oman", "Pakistan", "Palau", "Palestine, State of", "Panama",
1207
+ "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Pitcairn",
1208
+ "Poland", "Portugal", "Puerto Rico", "Qatar", "Romania",
1209
+ "Russian Federation", "Rwanda", "Réunion", "Saint Barthélemy", "Saint Helena, Ascension and Tristan da Cunha",
1210
+ "Saint Kitts and Nevis", "Saint Lucia", "Saint Martin (French part)", "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines",
1211
+ "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal",
1212
+ "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Sint Maarten (Dutch part)",
1213
+ "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
1214
+ "South Georgia and the South Sandwich Islands", "South Sudan", "Spain", "Sri Lanka", "Sudan",
1215
+ "Suriname", "Svalbard and Jan Mayen", "Sweden", "Switzerland", "Syrian Arab Republic",
1216
+ "Taiwan, Province of China", "Tajikistan", "Tanzania", "Thailand", "Timor-Leste",
1217
+ "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia",
1218
+ "Turkey", "Turkmenistan", "Tuvalu", "Uganda", "Ukraine",
1219
+ "United Arab Emirates", "United Kingdom", "United States", "Uruguay", "Uzbekistan",
1220
+ "Vanuatu", "Venezuela", "Viet Nam", "Western Sahara", "Yemen",
1221
+ "Zambia", "Zimbabwe", "Åland Islands"
1222
+ ]}
1223
+ placeholder="Select country"
1224
+ />
1225
+ </>
1226
+ )}
1227
+ {billingInfo.billingRequired.state && (
1228
+ <>
1229
+ <Text style={styles.label}>Billing State</Text>
1230
+ <TextInput
1231
+ style={styles.input}
1232
+ value={billingInfo.billing.state}
1233
+ onChangeText={value => updateBillingInfo('billing', 'state', value)}
1234
+ placeholder="Enter state"
1235
+ placeholderTextColor="#999999"
1236
+ />
1237
+ </>
1238
+ )}
1239
+ {billingInfo.billingRequired.city && (
1240
+ <>
1241
+ <Text style={styles.label}>Billing City</Text>
1242
+ <TextInput
1243
+ style={styles.input}
1244
+ value={billingInfo.billing.city}
1245
+ onChangeText={value => updateBillingInfo('billing', 'city', value)}
1246
+ placeholder="Enter city"
1247
+ placeholderTextColor="#999999"
1248
+ />
1249
+ </>
1250
+ )}
1251
+ {billingInfo.billingRequired.postal_code && (
1252
+ <>
1253
+ <Text style={styles.label}>Billing Postal Code</Text>
1254
+ <TextInput
1255
+ style={styles.input}
1256
+ value={billingInfo.billing.postal_code}
1257
+ onChangeText={value => updateBillingInfo('billing', 'postal_code', value)}
1258
+ placeholder="Enter postal code"
1259
+ placeholderTextColor="#999999"
1260
+ />
1261
+ </>
1262
+ )}
1263
+
1264
+ <Text style={styles.sectionTitle}>Additional Info</Text>
1265
+
1266
+ {/* Additional Required Switches */}
1267
+ <Text style={styles.subsectionTitle}>Additional Required Fields</Text>
1268
+
1269
+
922
1270
  <Text style={styles.label}>Additional Required: Phone Number</Text>
923
1271
  <Switch
924
1272
  value={billingInfo.additionalRequired.phone_number}
@@ -934,190 +1282,194 @@ setResult(JSON.stringify(parsedResponse, null, 2));
934
1282
  thumbColor={billingInfo.additionalRequired.description ? '#fff' : '#f4f3f4'}
935
1283
  />
936
1284
 
937
- {/* Android Configuration */}
938
- {Platform.OS === 'android' && (
1285
+ {/* Additional Fields Section */}
1286
+ <Text style={styles.subsectionTitle}>Additional Fields</Text>
1287
+
1288
+ {billingInfo.additionalRequired.phone_number && (
939
1289
  <>
940
- <Text style={styles.sectionTitle}>Android Configuration</Text>
941
- <Text style={styles.label}>Currency</Text>
942
- <TextInput
943
- style={styles.input}
944
- value={androidConfig.currency}
945
- onChangeText={value => updateAndroidConfig('currency', value)}
946
- />
947
- <Text style={styles.label}>Save Card</Text>
948
- <Switch
949
- value={androidConfig.saveCard}
950
- onValueChange={value => updateAndroidConfig('saveCard', value)}
951
- trackColor={{ false: '#ccc', true: '#2563EB' }}
952
- thumbColor={androidConfig.saveCard ? '#fff' : '#f4f3f4'}
953
- />
954
- <Text style={styles.label}>Save Account</Text>
955
- <Switch
956
- value={androidConfig.saveAccount}
957
- onValueChange={value => updateAndroidConfig('saveAccount', value)}
958
- trackColor={{ false: '#ccc', true: '#2563EB' }}
959
- thumbColor={androidConfig.saveAccount ? '#fff' : '#f4f3f4'}
960
- />
961
- <Text style={styles.label}>Show Receipt</Text>
962
- <Switch
963
- value={androidConfig.showReceipt}
964
- onValueChange={value => updateAndroidConfig('showReceipt', value)}
965
- trackColor={{ false: '#ccc', true: '#2563EB' }}
966
- thumbColor={androidConfig.showReceipt ? '#fff' : '#f4f3f4'}
967
- />
968
- <Text style={styles.label}>Show Donate</Text>
969
- <Switch
970
- value={androidConfig.showDonate}
971
- onValueChange={value => updateAndroidConfig('showDonate', value)}
972
- trackColor={{ false: '#ccc', true: '#2563EB' }}
973
- thumbColor={androidConfig.showDonate ? '#fff' : '#f4f3f4'}
974
- />
975
- <Text style={styles.label}>Show Total</Text>
976
- <Switch
977
- value={androidConfig.showTotal}
978
- onValueChange={value => updateAndroidConfig('showTotal', value)}
979
- trackColor={{ false: '#ccc', true: '#2563EB' }}
980
- thumbColor={androidConfig.showTotal ? '#fff' : '#f4f3f4'}
981
- />
982
- <Text style={styles.label}>Show Submit Button</Text>
983
- <Switch
984
- value={androidConfig.showSubmitButton}
985
- onValueChange={value => updateAndroidConfig('showSubmitButton', value)}
986
- trackColor={{ false: '#ccc', true: '#2563EB' }}
987
- thumbColor={androidConfig.showSubmitButton ? '#fff' : '#f4f3f4'}
988
- />
989
- <Text style={styles.label}>Name</Text>
1290
+ <Text style={styles.label}>Additional: Phone Number</Text>
1291
+ <View style={styles.phoneRow}>
1292
+ <TextInput
1293
+ style={styles.countryCodeInput}
1294
+ value={billingInfo.additional.country_code || '+1'}
1295
+ onChangeText={value => {
1296
+ // Only allow a '+' at the start, followed by up to 4 digits
1297
+ let filtered = value.replace(/[^+0-9]/g, '');
1298
+ if (!filtered.startsWith('+')) filtered = '+' + filtered.replace(/\+/g, '');
1299
+ filtered = filtered.slice(0, 5);
1300
+ // Update country code and combined phone number
1301
+ const phone = billingInfo.additional.phone_number || '';
1302
+ const phoneOnly = phone.slice((billingInfo.additional.country_code || '+1').replace('+', '').length);
1303
+ const combined = filtered.replace('+', '') + phoneOnly;
1304
+ updateBillingInfo('additional', 'country_code', filtered);
1305
+ updateBillingInfo('additional', 'phone_number', combined);
1306
+ }}
1307
+ placeholder="+1"
1308
+ placeholderTextColor="#999999"
1309
+ keyboardType="phone-pad"
1310
+ maxLength={5}
1311
+ />
1312
+ <TextInput
1313
+ style={[styles.input, { flex: 1, marginBottom: 0 }]}
1314
+ value={(billingInfo.additional.phone_number || '').slice((billingInfo.additional.country_code || '+1').replace('+', '').length)}
1315
+ onChangeText={value => {
1316
+ // Only allow digits and limit to 10 characters
1317
+ const numericValue = value.replace(/[^0-9]/g, '').slice(0, 10);
1318
+ const code = (billingInfo.additional.country_code || '+1').replace('+', '');
1319
+ updateBillingInfo('additional', 'phone_number', code + numericValue);
1320
+ }}
1321
+ placeholder="Enter phone number"
1322
+ placeholderTextColor="#999999"
1323
+ keyboardType="phone-pad"
1324
+ maxLength={10}
1325
+ />
1326
+ </View>
1327
+ </>
1328
+ )}
1329
+ {billingInfo.additionalRequired.description && (
1330
+ <>
1331
+ <Text style={styles.label}>Additional: Description</Text>
990
1332
  <TextInput
991
1333
  style={styles.input}
992
- value={androidConfig.name}
993
- onChangeText={value => updateAndroidConfig('name', value)}
1334
+ value={billingInfo.additional.description}
1335
+ onChangeText={value => updateBillingInfo('additional', 'description', value)}
1336
+ placeholder="Enter description"
1337
+ placeholderTextColor="#999999"
994
1338
  />
995
- <Text style={styles.sectionTitle}>Android Fields</Text>
996
- {androidConfig.fields.billing.map((field, index) => (
997
- <View key={`billing-${index}`}>
998
- <Text style={styles.label}>{`Billing ${field.name}`}</Text>
999
- <TextInput
1000
- style={styles.input}
1001
- value={field.value}
1002
- onChangeText={value => updateAndroidConfigFields('billing', index, 'value', value)}
1003
- />
1004
- <Text style={styles.label}>{`Billing ${field.name} Required`}</Text>
1005
- <Switch
1006
- value={field.required}
1007
- onValueChange={value => updateAndroidConfigFields('billing', index, 'required', value)}
1008
- trackColor={{ false: '#ccc', true: '#2563EB' }}
1009
- thumbColor={field.required ? '#fff' : '#f4f3f4'}
1010
- />
1011
- </View>
1012
- ))}
1013
- {androidConfig.fields.additional.map((field, index) => (
1014
- <View key={`additional-${index}`}>
1015
- <Text style={styles.label}>{`Additional ${field.name}`}</Text>
1016
- <TextInput
1017
- style={styles.input}
1018
- value={field.value}
1019
- onChangeText={value => updateAndroidConfigFields('additional', index, 'value', value)}
1020
- />
1021
- <Text style={styles.label}>{`Additional ${field.name} Required`}</Text>
1022
- <Switch
1023
- value={field.required}
1024
- onValueChange={value => updateAndroidConfigFields('additional', index, 'required', value)}
1025
- trackColor={{ false: '#ccc', true: '#2563EB' }}
1026
- thumbColor={field.required ? '#fff' : '#f4f3f4'}
1027
- />
1028
- </View>
1029
- ))}
1339
+ </>
1340
+ )}
1341
+
1342
+ {/* Android Appearance Settings */}
1343
+ {Platform.OS === 'android' && (
1344
+ <>
1030
1345
  <Text style={styles.sectionTitle}>Android Appearance Settings</Text>
1031
- <Text style={styles.label}>Theme</Text>
1032
- <TextInput
1033
- style={styles.input}
1034
- value={androidConfig.appearanceSettings.theme}
1035
- onChangeText={value => updateAndroidConfigAppearanceSettings('theme', value)}
1036
- />
1037
1346
  <Text style={styles.label}>Body Background Color</Text>
1038
- <TextInput
1039
- style={styles.input}
1040
- value={androidConfig.appearanceSettings.bodyBackgroundColor}
1041
- onChangeText={value => updateAndroidConfigAppearanceSettings('bodyBackgroundColor', value)}
1042
- />
1347
+ <View style={styles.colorInputContainer}>
1348
+ <TextInput
1349
+ style={[styles.input, { flex: 1 }]}
1350
+ value={androidConfig.appearanceSettings.bodyBackgroundColor}
1351
+ onChangeText={value => updateAndroidConfigAppearanceSettings('bodyBackgroundColor', value)}
1352
+ placeholder="Enter body background color"
1353
+ placeholderTextColor="#999999"
1354
+ />
1355
+ <View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.bodyBackgroundColor }]} />
1356
+ </View>
1043
1357
  <Text style={styles.label}>Container Background Color</Text>
1044
- <TextInput
1045
- style={styles.input}
1046
- value={androidConfig.appearanceSettings.containerBackgroundColor}
1047
- onChangeText={value => updateAndroidConfigAppearanceSettings('containerBackgroundColor', value)}
1048
- />
1358
+ <View style={styles.colorInputContainer}>
1359
+ <TextInput
1360
+ style={[styles.input, { flex: 1 }]}
1361
+ value={androidConfig.appearanceSettings.containerBackgroundColor}
1362
+ onChangeText={value => updateAndroidConfigAppearanceSettings('containerBackgroundColor', value)}
1363
+ placeholder="Enter container background color"
1364
+ placeholderTextColor="#999999"
1365
+ />
1366
+ <View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.containerBackgroundColor }]} />
1367
+ </View>
1049
1368
  <Text style={styles.label}>Primary Font Color</Text>
1050
- <TextInput
1051
- style={styles.input}
1052
- value={androidConfig.appearanceSettings.primaryFontColor}
1053
- onChangeText={value => updateAndroidConfigAppearanceSettings('primaryFontColor', value)}
1054
- />
1369
+ <View style={styles.colorInputContainer}>
1370
+ <TextInput
1371
+ style={[styles.input, { flex: 1 }]}
1372
+ value={androidConfig.appearanceSettings.primaryFontColor}
1373
+ onChangeText={value => updateAndroidConfigAppearanceSettings('primaryFontColor', value)}
1374
+ placeholder="Enter primary font color"
1375
+ placeholderTextColor="#999999"
1376
+ />
1377
+ <View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.primaryFontColor }]} />
1378
+ </View>
1055
1379
  <Text style={styles.label}>Secondary Font Color</Text>
1056
- <TextInput
1057
- style={styles.input}
1058
- value={androidConfig.appearanceSettings.secondaryFontColor}
1059
- onChangeText={value => updateAndroidConfigAppearanceSettings('secondaryFontColor', value)}
1060
- />
1380
+ <View style={styles.colorInputContainer}>
1381
+ <TextInput
1382
+ style={[styles.input, { flex: 1 }]}
1383
+ value={androidConfig.appearanceSettings.secondaryFontColor}
1384
+ onChangeText={value => updateAndroidConfigAppearanceSettings('secondaryFontColor', value)}
1385
+ placeholder="Enter secondary font color"
1386
+ placeholderTextColor="#999999"
1387
+ />
1388
+ <View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.secondaryFontColor }]} />
1389
+ </View>
1061
1390
  <Text style={styles.label}>Primary Button Background Color</Text>
1062
- <TextInput
1063
- style={styles.input}
1064
- value={androidConfig.appearanceSettings.primaryButtonBackgroundColor}
1065
- onChangeText={value => updateAndroidConfigAppearanceSettings('primaryButtonBackgroundColor', value)}
1066
- />
1391
+ <View style={styles.colorInputContainer}>
1392
+ <TextInput
1393
+ style={[styles.input, { flex: 1 }]}
1394
+ value={androidConfig.appearanceSettings.primaryButtonBackgroundColor}
1395
+ onChangeText={value => updateAndroidConfigAppearanceSettings('primaryButtonBackgroundColor', value)}
1396
+ placeholder="Enter primary button background color"
1397
+ placeholderTextColor="#999999"
1398
+ />
1399
+ <View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.primaryButtonBackgroundColor }]} />
1400
+ </View>
1067
1401
  <Text style={styles.label}>Primary Button Hover Color</Text>
1068
- <TextInput
1069
- style={styles.input}
1070
- value={androidConfig.appearanceSettings.primaryButtonHoverColor}
1071
- onChangeText={value => updateAndroidConfigAppearanceSettings('primaryButtonHoverColor', value)}
1072
- />
1402
+ <View style={styles.colorInputContainer}>
1403
+ <TextInput
1404
+ style={[styles.input, { flex: 1 }]}
1405
+ value={androidConfig.appearanceSettings.primaryButtonHoverColor}
1406
+ onChangeText={value => updateAndroidConfigAppearanceSettings('primaryButtonHoverColor', value)}
1407
+ placeholder="Enter primary button hover color"
1408
+ placeholderTextColor="#999999"
1409
+ />
1410
+ <View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.primaryButtonHoverColor }]} />
1411
+ </View>
1073
1412
  <Text style={styles.label}>Primary Button Font Color</Text>
1074
- <TextInput
1075
- style={styles.input}
1076
- value={androidConfig.appearanceSettings.primaryButtonFontColor}
1077
- onChangeText={value => updateAndroidConfigAppearanceSettings('primaryButtonFontColor', value)}
1078
- />
1413
+ <View style={styles.colorInputContainer}>
1414
+ <TextInput
1415
+ style={[styles.input, { flex: 1 }]}
1416
+ value={androidConfig.appearanceSettings.primaryButtonFontColor}
1417
+ onChangeText={value => updateAndroidConfigAppearanceSettings('primaryButtonFontColor', value)}
1418
+ placeholder="Enter primary button font color"
1419
+ placeholderTextColor="#999999"
1420
+ />
1421
+ <View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.primaryButtonFontColor }]} />
1422
+ </View>
1079
1423
  <Text style={styles.label}>Secondary Button Background Color</Text>
1080
- <TextInput
1081
- style={styles.input}
1082
- value={androidConfig.appearanceSettings.secondaryButtonBackgroundColor}
1083
- onChangeText={value => updateAndroidConfigAppearanceSettings('secondaryButtonBackgroundColor', value)}
1084
- />
1424
+ <View style={styles.colorInputContainer}>
1425
+ <TextInput
1426
+ style={[styles.input, { flex: 1 }]}
1427
+ value={androidConfig.appearanceSettings.secondaryButtonBackgroundColor}
1428
+ onChangeText={value => updateAndroidConfigAppearanceSettings('secondaryButtonBackgroundColor', value)}
1429
+ placeholder="Enter secondary button background color"
1430
+ placeholderTextColor="#999999"
1431
+ />
1432
+ <View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.secondaryButtonBackgroundColor }]} />
1433
+ </View>
1085
1434
  <Text style={styles.label}>Secondary Button Hover Color</Text>
1086
- <TextInput
1087
- style={styles.input}
1088
- value={androidConfig.appearanceSettings.secondaryButtonHoverColor}
1089
- onChangeText={value => updateAndroidConfigAppearanceSettings('secondaryButtonHoverColor', value)}
1090
- />
1435
+ <View style={styles.colorInputContainer}>
1436
+ <TextInput
1437
+ style={[styles.input, { flex: 1 }]}
1438
+ value={androidConfig.appearanceSettings.secondaryButtonHoverColor}
1439
+ onChangeText={value => updateAndroidConfigAppearanceSettings('secondaryButtonHoverColor', value)}
1440
+ placeholder="Enter secondary button hover color"
1441
+ placeholderTextColor="#999999"
1442
+ />
1443
+ <View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.secondaryButtonHoverColor }]} />
1444
+ </View>
1091
1445
  <Text style={styles.label}>Secondary Button Font Color</Text>
1092
- <TextInput
1093
- style={styles.input}
1094
- value={androidConfig.appearanceSettings.secondaryButtonFontColor}
1095
- onChangeText={value => updateAndroidConfigAppearanceSettings('secondaryButtonFontColor', value)}
1096
- />
1446
+ <View style={styles.colorInputContainer}>
1447
+ <TextInput
1448
+ style={[styles.input, { flex: 1 }]}
1449
+ value={androidConfig.appearanceSettings.secondaryButtonFontColor}
1450
+ onChangeText={value => updateAndroidConfigAppearanceSettings('secondaryButtonFontColor', value)}
1451
+ placeholder="Enter secondary button font color"
1452
+ placeholderTextColor="#999999"
1453
+ />
1454
+ <View style={[styles.colorPreview, { backgroundColor: androidConfig.appearanceSettings.secondaryButtonFontColor }]} />
1455
+ </View>
1097
1456
  <Text style={styles.label}>Border Radius</Text>
1098
1457
  <TextInput
1099
1458
  style={styles.input}
1100
1459
  value={androidConfig.appearanceSettings.borderRadius}
1101
1460
  onChangeText={value => updateAndroidConfigAppearanceSettings('borderRadius', value)}
1461
+ placeholder="Enter border radius"
1462
+ placeholderTextColor="#999999"
1102
1463
  />
1103
1464
  <Text style={styles.label}>Font Size</Text>
1104
1465
  <TextInput
1105
1466
  style={styles.input}
1106
1467
  value={androidConfig.appearanceSettings.fontSize}
1107
1468
  onChangeText={value => updateAndroidConfigAppearanceSettings('fontSize', value)}
1469
+ placeholder="Enter font size"
1470
+ placeholderTextColor="#999999"
1108
1471
  />
1109
- <Text style={styles.label}>Font Weight</Text>
1110
- <TextInput
1111
- style={styles.input}
1112
- value={String(androidConfig.appearanceSettings.fontWeight)}
1113
- onChangeText={value => updateAndroidConfigAppearanceSettings('fontWeight', parseInt(value) || '500')}
1114
- />
1115
- <Text style={styles.label}>Font Family</Text>
1116
- <TextInput
1117
- style={styles.input}
1118
- value={androidConfig.appearanceSettings.fontFamily}
1119
- onChangeText={value => updateAndroidConfigAppearanceSettings('fontFamily', value)}
1120
- />
1472
+
1121
1473
  </>
1122
1474
  )}
1123
1475
 
@@ -1126,89 +1478,132 @@ setResult(JSON.stringify(parsedResponse, null, 2));
1126
1478
  <>
1127
1479
  <Text style={styles.sectionTitle}>Theme Configuration (iOS)</Text>
1128
1480
  <Text style={styles.label}>Body Background Color</Text>
1129
- <TextInput
1130
- style={styles.input}
1131
- value={themeConfiguration.bodyBackgroundColor}
1132
- onChangeText={value => updateThemeConfiguration('bodyBackgroundColor', value)}
1133
- />
1481
+ <View style={styles.colorInputContainer}>
1482
+ <TextInput
1483
+ style={[styles.input, { flex: 1 }]}
1484
+ value={themeConfiguration.bodyBackgroundColor}
1485
+ onChangeText={value => updateThemeConfiguration('bodyBackgroundColor', value)}
1486
+ placeholder="Enter body background color"
1487
+ placeholderTextColor="#999999"
1488
+ />
1489
+ <View style={[styles.colorPreview, { backgroundColor: themeConfiguration.bodyBackgroundColor }]} />
1490
+ </View>
1134
1491
  <Text style={styles.label}>Container Background Color</Text>
1135
- <TextInput
1136
- style={styles.input}
1137
- value={themeConfiguration.containerBackgroundColor}
1138
- onChangeText={value => updateThemeConfiguration('containerBackgroundColor', value)}
1139
- />
1492
+ <View style={styles.colorInputContainer}>
1493
+ <TextInput
1494
+ style={[styles.input, { flex: 1 }]}
1495
+ value={themeConfiguration.containerBackgroundColor}
1496
+ onChangeText={value => updateThemeConfiguration('containerBackgroundColor', value)}
1497
+ placeholder="Enter container background color"
1498
+ placeholderTextColor="#999999"
1499
+ />
1500
+ <View style={[styles.colorPreview, { backgroundColor: themeConfiguration.containerBackgroundColor }]} />
1501
+ </View>
1140
1502
  <Text style={styles.label}>Primary Font Color</Text>
1141
- <TextInput
1142
- style={styles.input}
1143
- value={themeConfiguration.primaryFontColor}
1144
- onChangeText={value => updateThemeConfiguration('primaryFontColor', value)}
1145
- />
1503
+ <View style={styles.colorInputContainer}>
1504
+ <TextInput
1505
+ style={[styles.input, { flex: 1 }]}
1506
+ value={themeConfiguration.primaryFontColor}
1507
+ onChangeText={value => updateThemeConfiguration('primaryFontColor', value)}
1508
+ placeholder="Enter primary font color"
1509
+ placeholderTextColor="#999999"
1510
+ />
1511
+ <View style={[styles.colorPreview, { backgroundColor: themeConfiguration.primaryFontColor }]} />
1512
+ </View>
1146
1513
  <Text style={styles.label}>Secondary Font Color</Text>
1147
- <TextInput
1148
- style={styles.input}
1149
- value={themeConfiguration.secondaryFontColor}
1150
- onChangeText={value => updateThemeConfiguration('secondaryFontColor', value)}
1151
- />
1514
+ <View style={styles.colorInputContainer}>
1515
+ <TextInput
1516
+ style={[styles.input, { flex: 1 }]}
1517
+ value={themeConfiguration.secondaryFontColor}
1518
+ onChangeText={value => updateThemeConfiguration('secondaryFontColor', value)}
1519
+ placeholder="Enter secondary font color"
1520
+ placeholderTextColor="#999999"
1521
+ />
1522
+ <View style={[styles.colorPreview, { backgroundColor: themeConfiguration.secondaryFontColor }]} />
1523
+ </View>
1152
1524
  <Text style={styles.label}>Primary Button Background Color</Text>
1153
- <TextInput
1154
- style={styles.input}
1155
- value={themeConfiguration.primaryButtonBackgroundColor}
1156
- onChangeText={value => updateThemeConfiguration('primaryButtonBackgroundColor', value)}
1157
- />
1525
+ <View style={styles.colorInputContainer}>
1526
+ <TextInput
1527
+ style={[styles.input, { flex: 1 }]}
1528
+ value={themeConfiguration.primaryButtonBackgroundColor}
1529
+ onChangeText={value => updateThemeConfiguration('primaryButtonBackgroundColor', value)}
1530
+ placeholder="Enter primary button background color"
1531
+ placeholderTextColor="#999999"
1532
+ />
1533
+ <View style={[styles.colorPreview, { backgroundColor: themeConfiguration.primaryButtonBackgroundColor }]} />
1534
+ </View>
1158
1535
  <Text style={styles.label}>Primary Button Hover Color</Text>
1159
- <TextInput
1160
- style={styles.input}
1161
- value={themeConfiguration.primaryButtonHoverColor}
1162
- onChangeText={value => updateThemeConfiguration('primaryButtonHoverColor', value)}
1163
- />
1536
+ <View style={styles.colorInputContainer}>
1537
+ <TextInput
1538
+ style={[styles.input, { flex: 1 }]}
1539
+ value={themeConfiguration.primaryButtonHoverColor}
1540
+ onChangeText={value => updateThemeConfiguration('primaryButtonHoverColor', value)}
1541
+ placeholder="Enter primary button hover color"
1542
+ placeholderTextColor="#999999"
1543
+ />
1544
+ <View style={[styles.colorPreview, { backgroundColor: themeConfiguration.primaryButtonHoverColor }]} />
1545
+ </View>
1164
1546
  <Text style={styles.label}>Primary Button Font Color</Text>
1165
- <TextInput
1166
- style={styles.input}
1167
- value={themeConfiguration.primaryButtonFontColor}
1168
- onChangeText={value => updateThemeConfiguration('primaryButtonFontColor', value)}
1169
- />
1547
+ <View style={styles.colorInputContainer}>
1548
+ <TextInput
1549
+ style={[styles.input, { flex: 1 }]}
1550
+ value={themeConfiguration.primaryButtonFontColor}
1551
+ onChangeText={value => updateThemeConfiguration('primaryButtonFontColor', value)}
1552
+ placeholder="Enter primary button font color"
1553
+ placeholderTextColor="#999999"
1554
+ />
1555
+ <View style={[styles.colorPreview, { backgroundColor: themeConfiguration.primaryButtonFontColor }]} />
1556
+ </View>
1170
1557
  <Text style={styles.label}>Secondary Button Background Color</Text>
1171
- <TextInput
1172
- style={styles.input}
1173
- value={themeConfiguration.secondaryButtonBackgroundColor}
1174
- onChangeText={value => updateThemeConfiguration('secondaryButtonBackgroundColor', value)}
1175
- />
1558
+ <View style={styles.colorInputContainer}>
1559
+ <TextInput
1560
+ style={[styles.input, { flex: 1 }]}
1561
+ value={themeConfiguration.secondaryButtonBackgroundColor}
1562
+ onChangeText={value => updateThemeConfiguration('secondaryButtonBackgroundColor', value)}
1563
+ placeholder="Enter secondary button background color"
1564
+ placeholderTextColor="#999999"
1565
+ />
1566
+ <View style={[styles.colorPreview, { backgroundColor: themeConfiguration.secondaryButtonBackgroundColor }]} />
1567
+ </View>
1176
1568
  <Text style={styles.label}>Secondary Button Hover Color</Text>
1177
- <TextInput
1178
- style={styles.input}
1179
- value={themeConfiguration.secondaryButtonHoverColor}
1180
- onChangeText={value => updateThemeConfiguration('secondaryButtonHoverColor', value)}
1181
- />
1569
+ <View style={styles.colorInputContainer}>
1570
+ <TextInput
1571
+ style={[styles.input, { flex: 1 }]}
1572
+ value={themeConfiguration.secondaryButtonHoverColor}
1573
+ onChangeText={value => updateThemeConfiguration('secondaryButtonHoverColor', value)}
1574
+ placeholder="Enter secondary button hover color"
1575
+ placeholderTextColor="#999999"
1576
+ />
1577
+ <View style={[styles.colorPreview, { backgroundColor: themeConfiguration.secondaryButtonHoverColor }]} />
1578
+ </View>
1182
1579
  <Text style={styles.label}>Secondary Button Font Color</Text>
1183
- <TextInput
1184
- style={styles.input}
1185
- value={themeConfiguration.secondaryButtonFontColor}
1186
- onChangeText={value => updateThemeConfiguration('secondaryButtonFontColor', value)}
1187
- />
1580
+ <View style={styles.colorInputContainer}>
1581
+ <TextInput
1582
+ style={[styles.input, { flex: 1 }]}
1583
+ value={themeConfiguration.secondaryButtonFontColor}
1584
+ onChangeText={value => updateThemeConfiguration('secondaryButtonFontColor', value)}
1585
+ placeholder="Enter secondary button font color"
1586
+ placeholderTextColor="#999999"
1587
+ />
1588
+ <View style={[styles.colorPreview, { backgroundColor: themeConfiguration.secondaryButtonFontColor }]} />
1589
+ </View>
1188
1590
  <Text style={styles.label}>Border Radius</Text>
1189
1591
  <TextInput
1190
1592
  style={styles.input}
1191
1593
  value={themeConfiguration.borderRadius}
1192
1594
  onChangeText={value => updateThemeConfiguration('borderRadius', value)}
1595
+ placeholder="Enter border radius"
1596
+ placeholderTextColor="#999999"
1193
1597
  />
1194
1598
  <Text style={styles.label}>Font Size</Text>
1195
1599
  <TextInput
1196
1600
  style={styles.input}
1197
1601
  value={themeConfiguration.fontSize}
1198
1602
  onChangeText={value => updateThemeConfiguration('fontSize', value)}
1603
+ placeholder="Enter font size"
1604
+ placeholderTextColor="#999999"
1199
1605
  />
1200
- <Text style={styles.label}>Font Weight</Text>
1201
- <TextInput
1202
- style={styles.input}
1203
- value={String(themeConfiguration.fontWeight)}
1204
- onChangeText={value => updateThemeConfiguration('fontWeight', parseInt(value) || 500)}
1205
- />
1206
- <Text style={styles.label}>Font Family</Text>
1207
- <TextInput
1208
- style={styles.input}
1209
- value={themeConfiguration.fontFamily}
1210
- onChangeText={value => updateThemeConfiguration('fontFamily', value)}
1211
- />
1606
+
1212
1607
  </>
1213
1608
  )}
1214
1609
 
@@ -1219,12 +1614,16 @@ setResult(JSON.stringify(parsedResponse, null, 2));
1219
1614
  style={styles.input}
1220
1615
  value={grailPayParams.role}
1221
1616
  onChangeText={value => updateGrailPayParams('role', value)}
1617
+ placeholder="Enter role"
1618
+ placeholderTextColor="#999999"
1222
1619
  />
1223
1620
  <Text style={styles.label}>Timeout</Text>
1224
1621
  <TextInput
1225
1622
  style={styles.input}
1226
1623
  value={String(grailPayParams.timeout)}
1227
1624
  onChangeText={value => updateGrailPayParams('timeout', parseInt(value) || 10)}
1625
+ placeholder="Enter timeout"
1626
+ placeholderTextColor="#999999"
1228
1627
  />
1229
1628
  <Text style={styles.label}>Is Sandbox</Text>
1230
1629
  <Switch
@@ -1238,52 +1637,106 @@ setResult(JSON.stringify(parsedResponse, null, 2));
1238
1637
  style={styles.input}
1239
1638
  value={grailPayParams.brandingName}
1240
1639
  onChangeText={value => updateGrailPayParams('brandingName', value)}
1640
+ placeholder="Enter branding name"
1641
+ placeholderTextColor="#999999"
1241
1642
  />
1242
1643
  <Text style={styles.label}>Finder Subtitle</Text>
1243
1644
  <TextInput
1244
1645
  style={styles.input}
1245
1646
  value={grailPayParams.finderSubtitle}
1246
1647
  onChangeText={value => updateGrailPayParams('finderSubtitle', value)}
1648
+ placeholder="Enter finder subtitle"
1649
+ placeholderTextColor="#999999"
1247
1650
  />
1248
1651
  <Text style={styles.label}>Search Placeholder</Text>
1249
1652
  <TextInput
1250
1653
  style={styles.input}
1251
1654
  value={grailPayParams.searchPlaceholder}
1252
1655
  onChangeText={value => updateGrailPayParams('searchPlaceholder', value)}
1656
+ placeholder="Enter search placeholder"
1657
+ placeholderTextColor="#999999"
1253
1658
  />
1254
1659
 
1255
1660
  {/* Recurring Data */}
1256
1661
  <Text style={styles.sectionTitle}>Recurring Data</Text>
1257
- <Text style={styles.label}>Allow Cycles</Text>
1662
+ <Text style={styles.label}>Allow Cycles (Minimum 2)</Text>
1258
1663
  <TextInput
1259
1664
  style={styles.input}
1260
1665
  value={String(recurringData.allowCycles)}
1261
1666
  onChangeText={value => updateRecurringData('allowCycles', parseInt(value) || 2)}
1667
+ placeholder="Enter allowed cycles"
1668
+ placeholderTextColor="#999999"
1262
1669
  />
1263
- <Text style={styles.label}>Intervals</Text>
1670
+ <Text style={styles.label}>Intervals</Text>
1264
1671
  <View style={styles.buttonGroup}>
1265
- <Button
1672
+ <FilledButton
1673
+ title="Daily"
1674
+ onPress={() => toggleInterval('daily')}
1675
+ disabled={loading}
1676
+ style={[
1677
+ { flex: 1 },
1678
+ recurringData.intervals.includes('daily')
1679
+ ? { backgroundColor: '#2563EB' }
1680
+ : { backgroundColor: '#E5E7EB' }
1681
+ ]}
1682
+ textStyle={[
1683
+ { fontSize: 14 },
1684
+ recurringData.intervals.includes('daily')
1685
+ ? { color: '#fff' }
1686
+ : { color: '#374151' }
1687
+ ]}
1688
+ />
1689
+ <FilledButton
1266
1690
  title="Weekly"
1267
1691
  onPress={() => toggleInterval('weekly')}
1268
- color={recurringData.intervals.includes('weekly') ? '#2563EB' : '#ccc'}
1692
+ disabled={loading}
1693
+ style={[
1694
+ { flex: 1 },
1695
+ recurringData.intervals.includes('weekly')
1696
+ ? { backgroundColor: '#2563EB' }
1697
+ : { backgroundColor: '#E5E7EB' }
1698
+ ]}
1699
+ textStyle={[
1700
+ { fontSize: 14 },
1701
+ recurringData.intervals.includes('weekly')
1702
+ ? { color: '#fff' }
1703
+ : { color: '#374151' }
1704
+ ]}
1269
1705
  />
1270
- <Button
1706
+ <FilledButton
1271
1707
  title="Monthly"
1272
1708
  onPress={() => toggleInterval('monthly')}
1273
- color={recurringData.intervals.includes('monthly') ? '#2563EB' : '#ccc'}
1709
+ disabled={loading}
1710
+ style={[
1711
+ { flex: 1 },
1712
+ recurringData.intervals.includes('monthly')
1713
+ ? { backgroundColor: '#2563EB' }
1714
+ : { backgroundColor: '#E5E7EB' }
1715
+ ]}
1716
+ textStyle={[
1717
+ { fontSize: 14 },
1718
+ recurringData.intervals.includes('monthly')
1719
+ ? { color: '#fff' }
1720
+ : { color: '#374151' }
1721
+ ]}
1274
1722
  />
1275
1723
  </View>
1276
1724
  <Text style={styles.label}>Recurring Start Type</Text>
1277
- <TextInput
1278
- style={styles.input}
1725
+ <Dropdown
1279
1726
  value={recurringData.recurringStartType}
1280
- onChangeText={value => updateRecurringData('recurringStartType', value)}
1727
+ onValueChange={value => updateRecurringData('recurringStartType', value)}
1728
+ options={Platform.OS === 'android' ? ['Custom', 'Fixed'] : ['custom', 'fixed']}
1729
+ placeholder="Select start type"
1281
1730
  />
1282
1731
  <Text style={styles.label}>Recurring Start Date</Text>
1283
1732
  <TextInput
1284
- style={styles.input}
1733
+ style={[styles.input, styles.dateInput]}
1285
1734
  value={recurringData.recurringStartDate}
1286
1735
  onChangeText={value => updateRecurringData('recurringStartDate', value)}
1736
+ placeholder="MM/DD/YYYY"
1737
+ placeholderTextColor="#999999"
1738
+ returnKeyType="done"
1739
+ onSubmitEditing={() => Keyboard.dismiss()}
1287
1740
  />
1288
1741
  </>
1289
1742
  )}
@@ -1291,7 +1744,7 @@ setResult(JSON.stringify(parsedResponse, null, 2));
1291
1744
  <Text style={styles.sectionTitle}>SDK Response</Text>
1292
1745
  <Text selectable style={styles.result}>{result || 'No response yet'}</Text>
1293
1746
  </ScrollView>
1294
- </View>
1747
+ </KeyboardAvoidingView>
1295
1748
  );
1296
1749
 
1297
1750
  };
@@ -1314,6 +1767,13 @@ const styles = StyleSheet.create({
1314
1767
  marginTop: 20,
1315
1768
  marginBottom: 10,
1316
1769
  },
1770
+ subsectionTitle: {
1771
+ fontSize: 16,
1772
+ fontWeight: '600',
1773
+ marginTop: 15,
1774
+ marginBottom: 8,
1775
+ color: '#374151',
1776
+ },
1317
1777
  input: {
1318
1778
  height: 40,
1319
1779
  borderColor: '#ccc',
@@ -1321,6 +1781,19 @@ const styles = StyleSheet.create({
1321
1781
  borderRadius: 5,
1322
1782
  paddingHorizontal: 10,
1323
1783
  marginBottom: 10,
1784
+ backgroundColor: '#FFFFFF',
1785
+ color: '#000000',
1786
+ },
1787
+ dateInput: {
1788
+ backgroundColor: '#fff',
1789
+ shadowColor: '#000',
1790
+ shadowOffset: {
1791
+ width: 0,
1792
+ height: 1,
1793
+ },
1794
+ shadowOpacity: 0.1,
1795
+ shadowRadius: 2,
1796
+ elevation: 2,
1324
1797
  },
1325
1798
  pickerContainer: {
1326
1799
  marginBottom: 20,
@@ -1379,8 +1852,132 @@ const styles = StyleSheet.create({
1379
1852
  fontSize: 20,
1380
1853
  color: '#333',
1381
1854
  },
1855
+ // New styles for FilledButton
1856
+ filledButton: {
1857
+ flex: 1,
1858
+ paddingVertical: 10,
1859
+ paddingHorizontal: 20,
1860
+ borderRadius: 5,
1861
+ marginHorizontal: 5,
1862
+ alignItems: 'center',
1863
+ justifyContent: 'center',
1864
+ backgroundColor: '#2563EB', // Primary color for filled buttons
1865
+ },
1866
+ filledButtonDisabled: {
1867
+ backgroundColor: '#ccc',
1868
+ opacity: 0.7,
1869
+ },
1870
+ filledButtonText: {
1871
+ color: '#fff',
1872
+ fontSize: 16,
1873
+ fontWeight: '500',
1874
+ },
1875
+ filledButtonTextDisabled: {
1876
+ color: '#888',
1877
+ },
1878
+ debugText: {
1879
+ fontSize: 12,
1880
+ color: '#666',
1881
+ fontStyle: 'italic',
1882
+ marginBottom: 5,
1883
+ },
1884
+ // Dropdown Styles
1885
+ dropdownContainer: {
1886
+ position: 'relative',
1887
+ marginBottom: 20,
1888
+ zIndex: 9998,
1889
+ },
1890
+ dropdownButton: {
1891
+ height: 40,
1892
+ borderColor: '#ccc',
1893
+ borderWidth: 1,
1894
+ borderRadius: 5,
1895
+ paddingHorizontal: 10,
1896
+ flexDirection: 'row',
1897
+ justifyContent: 'space-between',
1898
+ alignItems: 'center',
1899
+ backgroundColor: '#fff',
1900
+ },
1901
+ dropdownButtonText: {
1902
+ fontSize: 16,
1903
+ color: '#333',
1904
+ },
1905
+ dropdownArrow: {
1906
+ fontSize: 12,
1907
+ color: '#666',
1908
+ },
1909
+ dropdownOptions: {
1910
+ position: 'absolute',
1911
+ top: 45,
1912
+ left: 0,
1913
+ right: 0,
1914
+ backgroundColor: '#fff',
1915
+ borderColor: '#ccc',
1916
+ borderWidth: 1,
1917
+ borderRadius: 5,
1918
+ zIndex: 9999,
1919
+ maxHeight: 200,
1920
+ elevation: 5,
1921
+ shadowColor: '#000',
1922
+ shadowOffset: {
1923
+ width: 0,
1924
+ height: 2,
1925
+ },
1926
+ shadowOpacity: 0.25,
1927
+ shadowRadius: 3.84,
1928
+ },
1929
+ dropdownScrollView: {
1930
+ maxHeight: 180,
1931
+ },
1932
+ dropdownOption: {
1933
+ paddingVertical: 12,
1934
+ paddingHorizontal: 15,
1935
+ borderBottomWidth: 1,
1936
+ borderBottomColor: '#f0f0f0',
1937
+ backgroundColor: '#fff',
1938
+ },
1939
+ dropdownOptionText: {
1940
+ fontSize: 16,
1941
+ color: '#333',
1942
+ },
1943
+ // Color preview styles
1944
+ colorInputContainer: {
1945
+ flexDirection: 'row',
1946
+ alignItems: 'center',
1947
+ marginBottom: 10,
1948
+ gap: 10,
1949
+ },
1950
+ colorPreview: {
1951
+ width: 30,
1952
+ height: 30,
1953
+ borderRadius: 4,
1954
+ borderWidth: 1,
1955
+ borderColor: '#ccc',
1956
+ },
1957
+ phoneRow: {
1958
+ flexDirection: 'row',
1959
+ alignItems: 'center',
1960
+ marginBottom: 10,
1961
+ gap: 8,
1962
+ },
1963
+ countryCodeInput: {
1964
+ width: 70,
1965
+ height: 40,
1966
+ borderColor: '#ccc',
1967
+ borderWidth: 1,
1968
+ borderRadius: 5,
1969
+ paddingHorizontal: 10,
1970
+ backgroundColor: '#FFFFFF',
1971
+ color: '#000000',
1972
+ textAlign: 'center',
1973
+ alignSelf: 'stretch',
1974
+ marginBottom: 0,
1975
+ },
1382
1976
  });
1383
1977
 
1978
+
1979
+
1980
+
1384
1981
  ```
1385
1982
 
1386
1983
  You can send `null` if billing info not available.