@jimrising/easymerchantsdk-react-native 1.0.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 (188) hide show
  1. package/.idea/caches/deviceStreaming.xml +340 -0
  2. package/.idea/em-MobileCheckoutSDK-ReactNative.iml +9 -0
  3. package/.idea/misc.xml +5 -0
  4. package/.idea/modules.xml +8 -0
  5. package/.idea/vcs.xml +6 -0
  6. package/README.md +230 -0
  7. package/android/build/.transforms/27d3a0c22098810ca42038b1b8102417/results.bin +1 -0
  8. package/android/build/.transforms/27d3a0c22098810ca42038b1b8102417/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/BuildConfig.dex +0 -0
  9. package/android/build/.transforms/27d3a0c22098810ca42038b1b8102417/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$1.dex +0 -0
  10. package/android/build/.transforms/27d3a0c22098810ca42038b1b8102417/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule.dex +0 -0
  11. package/android/build/.transforms/27d3a0c22098810ca42038b1b8102417/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkPackage.dex +0 -0
  12. package/android/build/.transforms/27d3a0c22098810ca42038b1b8102417/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +0 -0
  13. package/android/build/.transforms/41cd2635778c1bb5c18af2c46eb9196e/results.bin +1 -0
  14. package/android/build/.transforms/41cd2635778c1bb5c18af2c46eb9196e/transformed/classes/classes_dex/classes.dex +0 -0
  15. package/android/build/.transforms/6b36622787f586c4b6e3df98c5efa11c/results.bin +1 -0
  16. package/android/build/.transforms/6b36622787f586c4b6e3df98c5efa11c/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/BuildConfig.dex +0 -0
  17. package/android/build/.transforms/6b36622787f586c4b6e3df98c5efa11c/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$1.dex +0 -0
  18. package/android/build/.transforms/6b36622787f586c4b6e3df98c5efa11c/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule.dex +0 -0
  19. package/android/build/.transforms/6b36622787f586c4b6e3df98c5efa11c/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkPackage.dex +0 -0
  20. package/android/build/.transforms/6b36622787f586c4b6e3df98c5efa11c/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +0 -0
  21. package/android/build/.transforms/7eb7ceaa43c9e8323ac65b4f785987b6/results.bin +1 -0
  22. package/android/build/.transforms/7eb7ceaa43c9e8323ac65b4f785987b6/transformed/classes/classes_dex/classes.dex +0 -0
  23. package/android/build/.transforms/a8b93af4be449a0e2cca4bd6ddb6685e/results.bin +1 -0
  24. package/android/build/.transforms/a8b93af4be449a0e2cca4bd6ddb6685e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/BuildConfig.dex +0 -0
  25. package/android/build/.transforms/a8b93af4be449a0e2cca4bd6ddb6685e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$1.dex +0 -0
  26. package/android/build/.transforms/a8b93af4be449a0e2cca4bd6ddb6685e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule.dex +0 -0
  27. package/android/build/.transforms/a8b93af4be449a0e2cca4bd6ddb6685e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkPackage.dex +0 -0
  28. package/android/build/.transforms/a8b93af4be449a0e2cca4bd6ddb6685e/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +0 -0
  29. package/android/build/.transforms/f1a160720415bc76d8d6c6734c5acf3e/results.bin +1 -0
  30. package/android/build/.transforms/f1a160720415bc76d8d6c6734c5acf3e/transformed/classes/classes_dex/classes.dex +0 -0
  31. package/android/build/generated/source/buildConfig/debug/com/reactlibrary/BuildConfig.java +10 -0
  32. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +7 -0
  33. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +18 -0
  34. package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +6 -0
  35. package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +1 -0
  36. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  37. package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
  38. package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
  39. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -0
  40. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
  41. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
  42. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
  43. package/android/build/intermediates/incremental/packageDebugAssets/merger.xml +2 -0
  44. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/BuildConfig.class +0 -0
  45. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  46. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  47. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
  48. package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +2 -0
  49. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +7 -0
  50. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +7 -0
  51. package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +1 -0
  52. package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +1 -0
  53. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/BuildConfig.class +0 -0
  54. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  55. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  56. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
  57. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  58. package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +1 -0
  59. package/android/build/outputs/logs/manifest-merger-debug-report.txt +17 -0
  60. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkModule$1.class.uniqueId2 +0 -0
  61. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkModule.class.uniqueId1 +0 -0
  62. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkPackage.class.uniqueId0 +0 -0
  63. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  64. package/android/build.gradle +55 -0
  65. package/android/src/main/AndroidManifest.xml +5 -0
  66. package/android/src/main/java/com/reactlibrary/RNEasymerchantsdkModule.java +72 -0
  67. package/android/src/main/java/com/reactlibrary/RNEasymerchantsdkPackage.java +28 -0
  68. package/index.js +5 -0
  69. package/ios/Bundle/EasyPayBundle.swift +22 -0
  70. package/ios/Classes/EasyMerchantSdk.h +4 -0
  71. package/ios/Classes/EasyMerchantSdk.m +43 -0
  72. package/ios/Classes/EasyMerchantSdk.swift +124 -0
  73. package/ios/Classes/EasyPayViewController.swift +90 -0
  74. package/ios/CustomComponents/CheckboxButton.swift +20 -0
  75. package/ios/CustomComponents/FilledButton.swift +17 -0
  76. package/ios/CustomComponents/OutlineButton.swift +26 -0
  77. package/ios/CustomComponents/TextFieldStackView.swift +96 -0
  78. package/ios/EnvironmentConfig.swift +63 -0
  79. package/ios/Example/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
  80. package/ios/Example/Assets.xcassets/AppIcon.appiconset/Contents.json +13 -0
  81. package/ios/Example/Assets.xcassets/Contents.json +6 -0
  82. package/ios/Example/Base.lproj/LaunchScreen.storyboard +25 -0
  83. package/ios/Example/Base.lproj/Main.storyboard +65 -0
  84. package/ios/Example/SceneDelegate.swift +52 -0
  85. package/ios/Example/ViewController.swift +84 -0
  86. package/ios/Extensions/UIColor.swift +22 -0
  87. package/ios/Extensions/UIFont.swift +80 -0
  88. package/ios/Extensions/UIViewController+Extension.swift +42 -0
  89. package/ios/Models/AdditionalInfo.swift +16 -0
  90. package/ios/Models/BankAccountModel.swift +23 -0
  91. package/ios/Models/BillingInfo.swift +16 -0
  92. package/ios/Models/CardModel.swift +19 -0
  93. package/ios/Models/Country.swift +19 -0
  94. package/ios/Models/PaymentInfo.swift +46 -0
  95. package/ios/Models/Request.swift +245 -0
  96. package/ios/Models/Result.swift +69 -0
  97. package/ios/Pods/UserDefaults/UserStoreSingleton.swift +200 -0
  98. package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +1073 -0
  99. package/ios/Pods/ViewControllers/BaseVC.swift +117 -0
  100. package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +616 -0
  101. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CityListTVC.swift +19 -0
  102. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CountryListTVC.swift +19 -0
  103. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/StateListTVC.swift +19 -0
  104. package/ios/Pods/ViewControllers/CountryListVC.swift +367 -0
  105. package/ios/Pods/ViewControllers/EmailVerificationVC.swift +192 -0
  106. package/ios/Pods/ViewControllers/OTPVerificationVC.swift +1009 -0
  107. package/ios/Pods/ViewControllers/PaymentDoneVC.swift +94 -0
  108. package/ios/Pods/ViewControllers/PaymentErrorVC.swift +43 -0
  109. package/ios/Pods/ViewControllers/PaymentInformation/AccountTypeTVC.swift +19 -0
  110. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +4269 -0
  111. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInformationCVC.swift +20 -0
  112. package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.swift +37 -0
  113. package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.xib +163 -0
  114. package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.swift +40 -0
  115. package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.xib +184 -0
  116. package/ios/Pods/ViewControllers/TermAndConditionsVC.swift +21 -0
  117. package/ios/Resources/Assets.xcassets/Card/Amex.imageset/206682_american_express_method_card_payment_icon.svg +1 -0
  118. package/ios/Resources/Assets.xcassets/Card/Amex.imageset/Contents.json +12 -0
  119. package/ios/Resources/Assets.xcassets/Card/Contents.json +6 -0
  120. package/ios/Resources/Assets.xcassets/Card/DinersClub.imageset/472318_card_club_diners_dinner_payment_icon.svg +1 -0
  121. package/ios/Resources/Assets.xcassets/Card/DinersClub.imageset/Contents.json +12 -0
  122. package/ios/Resources/Assets.xcassets/Card/Discover.imageset/206686_network_payment_discover_card_method_icon.svg +1 -0
  123. package/ios/Resources/Assets.xcassets/Card/Discover.imageset/Contents.json +12 -0
  124. package/ios/Resources/Assets.xcassets/Card/JCB.imageset/358102_card_jcb_payment_icon.svg +1 -0
  125. package/ios/Resources/Assets.xcassets/Card/JCB.imageset/Contents.json +12 -0
  126. package/ios/Resources/Assets.xcassets/Card/MasterCard.imageset/206680_master_method_card_payment_icon.svg +1 -0
  127. package/ios/Resources/Assets.xcassets/Card/MasterCard.imageset/Contents.json +12 -0
  128. package/ios/Resources/Assets.xcassets/Card/UnionPay.imageset/1468976_card_payment_unionpay_icon.svg +1 -0
  129. package/ios/Resources/Assets.xcassets/Card/UnionPay.imageset/Contents.json +12 -0
  130. package/ios/Resources/Assets.xcassets/Card/Visa.imageset/206684_visa_method_card_payment_icon.svg +1 -0
  131. package/ios/Resources/Assets.xcassets/Card/Visa.imageset/Contents.json +12 -0
  132. package/ios/Resources/Assets.xcassets/Card/maestro.imageset/Contents.json +12 -0
  133. package/ios/Resources/Assets.xcassets/Card/maestro.imageset/maestro.svg +1 -0
  134. package/ios/Resources/Assets.xcassets/Card/paypal.imageset/206675_paypal_method_payment_icon.svg +1 -0
  135. package/ios/Resources/Assets.xcassets/Card/paypal.imageset/Contents.json +12 -0
  136. package/ios/Resources/Assets.xcassets/Card/rupay.imageset/Contents.json +12 -0
  137. package/ios/Resources/Assets.xcassets/Card/rupay.imageset/rupay.svg +1 -0
  138. package/ios/Resources/Assets.xcassets/Card/unknown_card.imageset/4635000_card_credit_digital_money_icon.svg +1 -0
  139. package/ios/Resources/Assets.xcassets/Card/unknown_card.imageset/Contents.json +12 -0
  140. package/ios/Resources/Assets.xcassets/Card/worldpay.imageset/Contents.json +12 -0
  141. package/ios/Resources/Assets.xcassets/Card/worldpay.imageset/worldpay.svg +1 -0
  142. package/ios/Resources/Assets.xcassets/Contents.json +6 -0
  143. package/ios/Resources/Assets.xcassets/Ellipsis.imageset/Contents.json +23 -0
  144. package/ios/Resources/Assets.xcassets/Ellipsis.imageset/icons8-menu-30.png +0 -0
  145. package/ios/Resources/Assets.xcassets/Ellipsis.imageset/icons8-menu-60.png +0 -0
  146. package/ios/Resources/Assets.xcassets/Ellipsis.imageset/icons8-menu-90.png +0 -0
  147. package/ios/Resources/Assets.xcassets/Rotate.imageset/Contents.json +23 -0
  148. package/ios/Resources/Assets.xcassets/Rotate.imageset/refresh-cw-2.png +0 -0
  149. package/ios/Resources/Assets.xcassets/Rotate.imageset/refresh-cw-3.png +0 -0
  150. package/ios/Resources/Assets.xcassets/Rotate.imageset/refresh-cw.png +0 -0
  151. package/ios/Resources/Assets.xcassets/amexCvc.imageset/Contents.json +22 -0
  152. package/ios/Resources/Assets.xcassets/amexCvc.imageset/Group-1.png +0 -0
  153. package/ios/Resources/Assets.xcassets/amexCvc.imageset/Group.png +0 -0
  154. package/ios/Resources/Assets.xcassets/check.imageset/Contents.json +23 -0
  155. package/ios/Resources/Assets.xcassets/check.imageset/check-circle-4.png +0 -0
  156. package/ios/Resources/Assets.xcassets/check.imageset/check-circle-5.png +0 -0
  157. package/ios/Resources/Assets.xcassets/check.imageset/check-circle-6.png +0 -0
  158. package/ios/Resources/Assets.xcassets/chip.imageset/Contents.json +21 -0
  159. package/ios/Resources/Assets.xcassets/chip.imageset/chip.png +0 -0
  160. package/ios/Resources/Assets.xcassets/loaderImage.imageset/Contents.json +23 -0
  161. package/ios/Resources/Assets.xcassets/loaderImage.imageset/check-circle-2.png +0 -0
  162. package/ios/Resources/Assets.xcassets/loaderImage.imageset/check-circle-3.png +0 -0
  163. package/ios/Resources/Assets.xcassets/loaderImage.imageset/check-circle.png +0 -0
  164. package/ios/Resources/Assets.xcassets/payment_done_icon.imageset/Contents.json +23 -0
  165. package/ios/Resources/Assets.xcassets/payment_done_icon.imageset/payment_done_icon 1.png +0 -0
  166. package/ios/Resources/Assets.xcassets/payment_done_icon.imageset/payment_done_icon 2.png +0 -0
  167. package/ios/Resources/Assets.xcassets/payment_done_icon.imageset/payment_done_icon.png +0 -0
  168. package/ios/Resources/Assets.xcassets/payment_error_icon.imageset/Contents.json +23 -0
  169. package/ios/Resources/Assets.xcassets/payment_error_icon.imageset/payment_error_icon 1.png +0 -0
  170. package/ios/Resources/Assets.xcassets/payment_error_icon.imageset/payment_error_icon 2.png +0 -0
  171. package/ios/Resources/Assets.xcassets/payment_error_icon.imageset/payment_error_icon.png +0 -0
  172. package/ios/Resources/Assets.xcassets/wallet.imageset/Contents.json +23 -0
  173. package/ios/Resources/Assets.xcassets/wallet.imageset/icons8-wallet-24(@1/303/227).png +0 -0
  174. package/ios/Resources/Assets.xcassets/wallet.imageset/icons8-wallet-48(@2/303/227).png +0 -0
  175. package/ios/Resources/Assets.xcassets/wallet.imageset/icons8-wallet-72(@3/303/227).png +0 -0
  176. package/ios/Resources/Colors.xcassets/00000008.colorset/Contents.json +20 -0
  177. package/ios/Resources/Colors.xcassets/00000016.colorset/Contents.json +20 -0
  178. package/ios/Resources/Colors.xcassets/00000038.colorset/Contents.json +20 -0
  179. package/ios/Resources/Colors.xcassets/00000060.colorset/Contents.json +20 -0
  180. package/ios/Resources/Colors.xcassets/1757D9.colorset/Contents.json +20 -0
  181. package/ios/Resources/Colors.xcassets/Contents.json +6 -0
  182. package/ios/Resources/Colors.xcassets/E93939.colorset/Contents.json +20 -0
  183. package/ios/Resources/PrivacyInfo.xcprivacy +10 -0
  184. package/ios/ThirdParty/Keyboad Handling/KeyboardObserver.swift +74 -0
  185. package/ios/easymerchantsdk.podspec +80 -0
  186. package/ios/easymerchantsdk.storyboard +5885 -0
  187. package/package.json +27 -0
  188. package/src/index.js +44 -0
@@ -0,0 +1,4269 @@
1
+ //
2
+ // PaymentInfoVC.swift
3
+ // EasyPay
4
+ //
5
+ // Created by Mony's Mac on 06/09/24.
6
+ //
7
+
8
+ import UIKit
9
+
10
+ struct PaymentsData {
11
+ var name = String()
12
+ var image = String()
13
+ }
14
+
15
+ let EasyPaySdk = UIStoryboard(name: "EasyPaySdk", bundle: Bundle.easyPayBundle)
16
+
17
+ class PaymentInfoVC: BaseVC, BillingInfoVCDelegate {
18
+
19
+ @IBOutlet weak var viewBtnShowSavedCards: UIView!
20
+ @IBOutlet weak var viewBtnShowSavedCardHeight: NSLayoutConstraint!
21
+ @IBOutlet weak var viewBtnShowSavedCardTopCon: NSLayoutConstraint!
22
+ @IBOutlet weak var lblBtnShowSaveCard: UILabel!
23
+
24
+ @IBOutlet weak var collVwPayment: UICollectionView!
25
+
26
+ @IBOutlet weak var viewCardFields: UIView!
27
+
28
+ @IBOutlet weak var btnNext: FilledButton!
29
+ @IBOutlet weak var btnNextHeight: NSLayoutConstraint!
30
+ @IBOutlet weak var btnNextTopCon: NSLayoutConstraint!
31
+
32
+ @IBOutlet weak var btnCheckBoxSavedCard: UIButton!
33
+ @IBOutlet weak var lblEasyMerchantOne: UILabel!
34
+ @IBOutlet weak var btnSettings: UIButton!
35
+
36
+ // Card
37
+ @IBOutlet private var cardNumberTextField: TextFieldStackView!
38
+ @IBOutlet private var cardExpiryTextField: TextFieldStackView!
39
+ @IBOutlet private var cardCvvTextField: TextFieldStackView!
40
+ @IBOutlet private var cardNameTextField: TextFieldStackView!
41
+ @IBOutlet weak var viewTextFieldCardNumber: UIView!
42
+ @IBOutlet weak var viewTxtFieldExpiryDate: UIView!
43
+ @IBOutlet weak var viewTxtFieldCVV: UIView!
44
+ @IBOutlet weak var viewTxtFieldNameOnCard: UIView!
45
+
46
+ @IBOutlet weak var emailView: UIView!
47
+ @IBOutlet weak var txtFieldEmail: UITextField!
48
+ @IBOutlet weak var viewTxtFieldEmail: UIView!
49
+ @IBOutlet weak var OTPView: UIView!
50
+
51
+ //OTP View
52
+ @IBOutlet weak var viewTextOTP1: UIView!
53
+ @IBOutlet weak var txtFieldOTPText1: UITextField!
54
+ @IBOutlet weak var viewTextOTP2: UIView!
55
+ @IBOutlet weak var txtFieldOTPText2: UITextField!
56
+ @IBOutlet weak var viewTextOTP3: UIView!
57
+ @IBOutlet weak var txtFieldOTPText3: UITextField!
58
+ @IBOutlet weak var viewTextOTP4: UIView!
59
+ @IBOutlet weak var txtFieldOTPText4: UITextField!
60
+ @IBOutlet weak var viewTextOTP5: UIView!
61
+ @IBOutlet weak var txtFieldOTPText5: UITextField!
62
+ @IBOutlet weak var viewTextOTP6: UIView!
63
+ @IBOutlet weak var txtFieldOTPText6: UITextField!
64
+ @IBOutlet weak var imgEsclamationMark: UIImageView!
65
+ @IBOutlet weak var lblOtpTimer: UILabel!
66
+ @IBOutlet weak var lblUntillResendOtp: UILabel!
67
+ @IBOutlet weak var btnResendOTP: UIButton!
68
+
69
+ @IBOutlet private var buttonBottomConstraint: NSLayoutConstraint!
70
+
71
+ //Single Saved Card
72
+ @IBOutlet weak var viewSingleSavedCard: UIView!
73
+ @IBOutlet weak var viewSingleSavedCardHeight: NSLayoutConstraint!
74
+ @IBOutlet weak var viewTxtFieldCVVSingleCard: TextFieldStackView!
75
+ @IBOutlet weak var txtFieldCVVSingleSavedCard: UITextField!
76
+ @IBOutlet weak var viewTxtFieldCVVSingleSavedCard: UIView!
77
+ @IBOutlet weak var btnPayNowSingleCard: FilledButton!
78
+ @IBOutlet weak var imgViewSigleSavedCard: UIImageView!
79
+ @IBOutlet weak var lblCardNumberSigleSavedCard: UILabel!
80
+ @IBOutlet weak var lblExpireDateSingelSavedCard: UILabel!
81
+
82
+ @IBOutlet weak var viewChangeCard: UIView!
83
+ @IBOutlet weak var viewUpdateCard: UIView!
84
+ @IBOutlet weak var viewAddNewCard: UIView!
85
+
86
+ @IBOutlet weak var tblViewSavedCardsList: UITableView!
87
+ @IBOutlet weak var tblViewSavedCardListHeight: NSLayoutConstraint!
88
+
89
+ //Update Card
90
+ @IBOutlet weak var btnSelectUpdateCardView: UIButton!
91
+ @IBOutlet weak var imgViewCardUpdateCardView: UIImageView!
92
+ @IBOutlet weak var lblCardNumberUpdateCardView: UILabel!
93
+ @IBOutlet weak var lblExpireDateUpdateCardView: UILabel!
94
+ @IBOutlet weak var txtFieldExpireDateUpdateCardView: UITextField!
95
+ @IBOutlet weak var viewTxtFieldExpireDateUpdateCardView: UIView!
96
+ @IBOutlet weak var txtFieldCVVUpdateCardView: UITextField!
97
+ @IBOutlet weak var viewtxtFieldCVVUpdateCardView: UIView!
98
+ @IBOutlet weak var txtFieldNameOnCardUpdateCardView: UITextField!
99
+ @IBOutlet weak var viewTxtFieldNameOnCardUpdateCardView: UIView!
100
+
101
+ //New Card
102
+ @IBOutlet weak var txtFieldCardNumberNewCardView: UITextField!
103
+ @IBOutlet weak var viewtxtFieldCardNumberNewCardView: UIView!
104
+ @IBOutlet weak var txtFieldExpiryDateNewCardView: UITextField!
105
+ @IBOutlet weak var viewtxtFieldExpiryDateNewCardView: UIView!
106
+ @IBOutlet weak var txtFieldCVVNewCardView: UITextField!
107
+ @IBOutlet weak var viewtxtFieldCVVNewCardView: UIView!
108
+ @IBOutlet weak var txtFieldNameOnCardNewCardView: UITextField!
109
+ @IBOutlet weak var viewtxtFieldNameOnCardNewCardView: UIView!
110
+ @IBOutlet weak var btnPayNowNewCardView: FilledButton!
111
+ @IBOutlet weak var btnSavedCardForFutureNewCardView: CheckboxButton!
112
+
113
+ //Bank
114
+ @IBOutlet weak var viewBankFields: UIView!
115
+ @IBOutlet weak var txtFieldAccountName: UITextField!
116
+ @IBOutlet weak var viewtxtFieldAccountName: UIView!
117
+ @IBOutlet weak var txtFieldRoutingNumber: UITextField!
118
+ @IBOutlet weak var viewtxtFieldRoutingNumber: UIView!
119
+ @IBOutlet weak var txtFieldAccountType: UITextField!
120
+ @IBOutlet weak var viewtxtFieldAccountType: UIView!
121
+ @IBOutlet weak var txtFieldAccountNumber: UITextField!
122
+ @IBOutlet weak var viewtxtFieldAccountNumber: UIView!
123
+ @IBOutlet weak var viewTermsAndConditions: UIStackView!
124
+ @IBOutlet weak var btnAgreeTermsConditions: UIButton!
125
+ @IBOutlet weak var lblTermsAndConditions: UILabel!
126
+ @IBOutlet weak var viewAccountType: UIView!
127
+ @IBOutlet weak var tblViewAccountTypes: UITableView!
128
+ @IBOutlet weak var btnCheckSavedAccountForFuture: UIButton!
129
+
130
+ //SavedBank
131
+ @IBOutlet weak var viewSingleSavedAccount: UIView!
132
+ @IBOutlet weak var viewTermAndConditionsSingleAccountView: UIStackView!
133
+ @IBOutlet weak var btnCheckAgreeTermsAndConditionsSingleAccount: UIButton!
134
+ @IBOutlet weak var lblTermsAndCondtionsSingleAccount: UILabel!
135
+ @IBOutlet weak var lblAgreetoTheSingleSavedAccountView: UILabel!
136
+ @IBOutlet weak var btnSelectSingleAccountView: UIButton!
137
+ @IBOutlet weak var lblAccountNumberSingleAccountView: UILabel!
138
+ @IBOutlet weak var lblAccountTypeSingleAccountView: UILabel!
139
+ @IBOutlet weak var btnPayNowSingleAccountView: FilledButton!
140
+ @IBOutlet weak var viewSingleAccountViewHeight: NSLayoutConstraint!
141
+ @IBOutlet weak var viewChangedAccount: UIView!
142
+ @IBOutlet weak var tblViewSavedBankAccounts: UITableView!
143
+ @IBOutlet weak var tblViewSavedBankAccountsHeight: NSLayoutConstraint!
144
+
145
+ //New Bank
146
+ @IBOutlet weak var viewNewBankAccount: UIView!
147
+ @IBOutlet weak var txtFieldAccountNameNewAccountView: UITextField!
148
+ @IBOutlet weak var viewtxtFieldAccountNameNewAccountView: UIView!
149
+ @IBOutlet weak var txtFieldRoutingNumberNewAccountView: UITextField!
150
+ @IBOutlet weak var viewtxtFieldRoutingNumberNewAccountView: UIView!
151
+ @IBOutlet weak var txtFieldAccountTypeNewAccountView: UITextField!
152
+ @IBOutlet weak var viewtxtFieldAccountTypeNewAccountView: UIView!
153
+ @IBOutlet weak var txtFieldAccountNumberNewAccountView: UITextField!
154
+ @IBOutlet weak var viewtxtFieldAccountNumberNewAccountView: UIView!
155
+ @IBOutlet weak var btnSavedNewAccountForFuture: UIButton!
156
+ @IBOutlet weak var viewAccountTypeNewAccountView: UIView!
157
+ @IBOutlet weak var tblViewAccountTypeNewAccountView: UITableView!
158
+ @IBOutlet weak var btnPayNowNewAccountView: FilledButton!
159
+
160
+ //Crypto
161
+ @IBOutlet weak var viewCrypto: UIView!
162
+ @IBOutlet weak var viewCryptoTryAgain: UIView!
163
+ @IBOutlet weak var viewCryptoQRCode: UIView!
164
+ @IBOutlet weak var qrImageView: UIImageView!
165
+ @IBOutlet weak var lblAmmoutCrypto: UILabel!
166
+ @IBOutlet weak var lblCryptoAmmountViewTryAgain: UILabel!
167
+ @IBOutlet weak var lblTimerCrypto: UILabel!
168
+ @IBOutlet weak var btnOnChain: UIButton!
169
+ @IBOutlet weak var btnLightning: UIButton!
170
+ @IBOutlet weak var lblBTCAddress: UILabel!
171
+ @IBOutlet weak var viewQrTryAgainHeight: NSLayoutConstraint!
172
+ @IBOutlet weak var viewQrCodeHeight: NSLayoutConstraint!
173
+ @IBOutlet weak var viewCryptoHeight: NSLayoutConstraint!
174
+
175
+ //Setting View
176
+ @IBOutlet weak var settingsView: UIView!
177
+
178
+ var chainInvoiceAddress: String?
179
+ var lightningURI: String?
180
+
181
+ var cryptoTimer: DispatchSourceTimer?
182
+ var totalTimeInSeconds = 300 // 5 minutes (300 seconds)
183
+ var isTimerRunning = false
184
+
185
+ var chargeId: String?
186
+ var cryptoInfoTimer: Timer?
187
+
188
+ var savedCards: [CardModel] = []
189
+ var isSelectForPay: Bool = false
190
+
191
+ var selectedCardIndex: Int? = nil
192
+
193
+ private var request: Request!
194
+ private weak var delegate: EasyPayViewControllerDelegate?
195
+
196
+ public var amount: Int?
197
+
198
+ let arrPaymentMethods = [
199
+ PaymentsData(name: "Card",image: "creditcard"),
200
+ PaymentsData(name: "Bank",image: "building.columns.fill"),
201
+ PaymentsData(name: "Crypto",image: "bitcoinsign.circle.fill"),
202
+ PaymentsData(name: "Wallet",image: "bag.fill")
203
+ ]
204
+
205
+ private var selectedIndex: IndexPath?
206
+ var selectedPaymentMethod: String?
207
+
208
+ private var timer: Timer?
209
+ private var timeRemaining = 180 // 3 minutes
210
+
211
+ var isSavedForFuture: Bool = false
212
+
213
+ var selectedCard: CardModel?
214
+
215
+ var isSavedNewCardForFuture: Bool = false
216
+
217
+ var selectedCardID: String?
218
+
219
+ //Bank
220
+ var bankAccounts: [Accounts] = []
221
+ var selectedbankAccounts: Accounts?
222
+ var selectedBank: BankAccountModel?
223
+ var isSelectForPayBank: Bool = false
224
+ var selectedBankIndex: Int? = nil
225
+ var arrAccountType = ["Saving","Current"]
226
+
227
+ var agreeTermsAndCondtition: Bool = false
228
+
229
+ var isSavedNewAccount: Bool = false
230
+ var selectedBankID: String?
231
+
232
+ public weak var easyPayDelegate: EasyPayViewControllerDelegate?
233
+
234
+ private let keyboardObserver = KeyboardObserver()
235
+
236
+ var isFrom = String()
237
+
238
+ //MARK: - View Did Load
239
+ override func viewDidLoad() {
240
+ super.viewDidLoad()
241
+
242
+ viewCryptoTryAgain.layer.borderColor = UIColor.systemGray.cgColor
243
+ viewCryptoTryAgain.layer.borderWidth = 1
244
+ viewCryptoTryAgain.layer.cornerRadius = 8
245
+
246
+ viewCryptoQRCode.layer.borderColor = UIColor.systemGray.cgColor
247
+ viewCryptoQRCode.layer.borderWidth = 1
248
+ viewCryptoQRCode.layer.cornerRadius = 8
249
+
250
+ setUpTextFieldsDelegates()
251
+
252
+ collVwPayment.delegate = self
253
+ collVwPayment.dataSource = self
254
+ // Set the first cell as selected by default
255
+ selectedIndex = IndexPath(row: 0, section: 0)
256
+ selectedPaymentMethod = arrPaymentMethods[selectedIndex!.row].name
257
+ collVwPayment.reloadData()
258
+
259
+ // Add tap gesture to hide the keyboard when tapping outside of text fields
260
+ let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
261
+ tapGesture.cancelsTouchesInView = false
262
+ self.view.addGestureRecognizer(tapGesture)
263
+
264
+ // Check if billingInfoData is available
265
+ if let billingInfoData = request.billingInfoData,
266
+ let json = try? JSONSerialization.jsonObject(with: billingInfoData, options: []),
267
+ let jsonDict = json as? [String: Any], !jsonDict.isEmpty {
268
+ print("Billing Info Data: \(jsonDict)")
269
+ // Set the button title to the default "Next"
270
+ btnNext.setTitle("Next (Billing Info)", for: .normal)
271
+ btnPayNowNewCardView.setTitle("Next (Billing Info)", for: .normal)
272
+ btnPayNowNewAccountView.setTitle("Next (Billing Info)", for: .normal)
273
+ btnPayNowSingleCard.setTitle("Next (Billing Info)", for: .normal)
274
+ btnPayNowSingleAccountView.setTitle("Next (Billing Info)", for: .normal)
275
+ } else {
276
+ // If billingInfoData is nil or empty, set the button title to "Pay Now"
277
+ btnNext.setTitle("Pay Now ($\(amount ?? 0))", for: .normal)
278
+ btnPayNowNewCardView.setTitle("Pay Now ($\(amount ?? 0))", for: .normal)
279
+ btnPayNowNewAccountView.setTitle("Pay Now ($\(amount ?? 0))", for: .normal)
280
+ btnPayNowSingleCard.setTitle("Pay Now ($\(amount ?? 0))", for: .normal)
281
+ btnPayNowSingleAccountView.setTitle("Pay Now ($\(amount ?? 0))", for: .normal)
282
+ }
283
+
284
+ emailView.isHidden = true
285
+ OTPView.isHidden = true
286
+
287
+ //OTP View
288
+ btnResendOTP.isHidden = true
289
+
290
+ // Set delegate for txtFieldEmail to handle return key press
291
+ txtFieldEmail.delegate = self
292
+
293
+ setupTextFields()
294
+
295
+ tblViewSavedCardsList.delegate = self
296
+ tblViewSavedCardsList.dataSource = self
297
+ let nib = UINib(nibName: "SavedCardsTVC", bundle: .easyPayBundle)
298
+ tblViewSavedCardsList.register(nib, forCellReuseIdentifier: "SavedCardsTVC")
299
+ tblViewSavedCardsList.showsVerticalScrollIndicator = false
300
+ tblViewSavedCardsList.showsHorizontalScrollIndicator = false
301
+ tblViewSavedCardsList.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
302
+
303
+ viewChangeCard.isHidden = true
304
+
305
+ tblViewSavedBankAccounts.delegate = self
306
+ tblViewSavedBankAccounts.dataSource = self
307
+ let nib2 = UINib(nibName: "SavedAccountTVC", bundle: .easyPayBundle)
308
+ tblViewSavedBankAccounts.register(nib2, forCellReuseIdentifier: "SavedAccountTVC")
309
+ tblViewSavedBankAccounts.showsVerticalScrollIndicator = false
310
+ tblViewSavedBankAccounts.showsHorizontalScrollIndicator = false
311
+ tblViewSavedBankAccounts.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
312
+
313
+ viewChangedAccount.isHidden = true
314
+
315
+ tblViewAccountTypes.delegate = self
316
+ tblViewAccountTypes.dataSource = self
317
+ tblViewAccountTypes.showsVerticalScrollIndicator = false
318
+ tblViewAccountTypes.showsHorizontalScrollIndicator = false
319
+
320
+ viewAccountType.clipsToBounds = false
321
+ viewAccountType.layer.shadowColor = UIColor.black.cgColor
322
+ viewAccountType.layer.shadowOpacity = 0.3
323
+ viewAccountType.layer.shadowOffset = CGSize(width: 0, height: 2)
324
+ viewAccountType.layer.shadowRadius = 4
325
+ viewAccountType.layer.cornerRadius = 8
326
+ viewAccountType.layer.cornerRadius = 8
327
+
328
+ viewAccountType.isHidden = true
329
+
330
+ tblViewAccountTypeNewAccountView.delegate = self
331
+ tblViewAccountTypeNewAccountView.dataSource = self
332
+ tblViewAccountTypeNewAccountView.showsVerticalScrollIndicator = false
333
+ tblViewAccountTypeNewAccountView.showsHorizontalScrollIndicator = false
334
+
335
+ viewAccountTypeNewAccountView.clipsToBounds = false
336
+ viewAccountTypeNewAccountView.layer.shadowColor = UIColor.black.cgColor
337
+ viewAccountTypeNewAccountView.layer.shadowOpacity = 0.3
338
+ viewAccountTypeNewAccountView.layer.shadowOffset = CGSize(width: 0, height: 2)
339
+ viewAccountTypeNewAccountView.layer.shadowRadius = 4
340
+ viewAccountTypeNewAccountView.layer.cornerRadius = 8
341
+ viewAccountTypeNewAccountView.layer.cornerRadius = 8
342
+
343
+ viewAccountTypeNewAccountView.isHidden = true
344
+
345
+ // Generate and set the QR code image
346
+ qrImageView.image = generateQRCode(from: "CryptoQR")
347
+
348
+ setUpTermAndConditionsButton()
349
+ setUpTermAndConditionsForSingleSavedAccountButton()
350
+
351
+ settingsView.clipsToBounds = false
352
+ settingsView.layer.shadowColor = UIColor.black.cgColor
353
+ settingsView.layer.shadowOpacity = 0.3
354
+ settingsView.layer.shadowOffset = CGSize(width: 0, height: 2)
355
+ settingsView.layer.shadowRadius = 4
356
+ settingsView.layer.cornerRadius = 8
357
+ settingsView.layer.cornerRadius = 8
358
+
359
+ settingsView.isHidden = true
360
+
361
+ if UserStoreSingleton.shared.isLoggedIn == true {
362
+ btnSettings.isHidden = false
363
+ }
364
+ else {
365
+ btnSettings.isHidden = true
366
+ }
367
+ }
368
+
369
+ //MARK: - View Wiil Appear
370
+ override func viewWillAppear(_ animated: Bool) {
371
+ keyboardObserver.animateChanges({ [self] height in
372
+ let newConstant = CGFloat.maximum(height - self.view.safeAreaInsets.bottom + 8, 8)
373
+ buttonBottomConstraint.constant = newConstant
374
+ self.view.setNeedsLayout()
375
+ self.view.layoutIfNeeded()
376
+ })
377
+
378
+ viewTermAndConditionsSingleAccountView.isHidden = true
379
+ btnPayNowSingleAccountView.isHidden = true
380
+ viewSingleAccountViewHeight.constant = 68
381
+
382
+ viewNewBankAccount.isHidden = true
383
+
384
+ viewTxtFieldCVVSingleCard.isHidden = true
385
+ btnPayNowSingleCard.isHidden = true
386
+ viewSingleSavedCardHeight.constant = 60
387
+
388
+ self.viewCrypto.isHidden = true
389
+
390
+ if selectedPaymentMethod == "Card" {
391
+ if UserStoreSingleton.shared.isLoggedIn == true {
392
+ if UserStoreSingleton.shared.customerId == nil {
393
+ self.OTPView.isHidden = true
394
+ self.viewCardFields.isHidden = false
395
+ self.viewSingleSavedCard.isHidden = true
396
+ self.viewBtnShowSavedCards.isHidden = true
397
+ self.viewBtnShowSavedCardHeight.constant = 0
398
+ self.viewBtnShowSavedCardTopCon.constant = 0
399
+ self.viewChangeCard.isHidden = true
400
+ self.viewUpdateCard.isHidden = true
401
+ self.viewAddNewCard.isHidden = true
402
+ }
403
+ else {
404
+ self.getShowCardsApi()
405
+ self.viewCardFields.isHidden = true
406
+ self.viewSingleSavedCard.isHidden = false
407
+ self.viewBtnShowSavedCards.isHidden = true
408
+ self.viewBtnShowSavedCardHeight.constant = 0
409
+ self.viewBtnShowSavedCardTopCon.constant = 0
410
+ self.viewChangeCard.isHidden = true
411
+ self.viewUpdateCard.isHidden = true
412
+ self.viewAddNewCard.isHidden = true
413
+
414
+ self.viewTxtFieldCVVSingleCard.isHidden = true
415
+ self.btnPayNowSingleCard.isHidden = true
416
+ self.viewSingleSavedCardHeight.constant = 60
417
+ self.btnNext.isHidden = true
418
+ self.btnNextHeight.constant = 0
419
+ self.btnNextTopCon.constant = 8
420
+
421
+ self.viewSingleSavedAccount.isHidden = true
422
+
423
+ self.viewBankFields.isHidden = true
424
+ self.viewTermsAndConditions.isHidden = true
425
+
426
+ if isSelectForPay {
427
+ viewTxtFieldCVVSingleCard.isHidden = false
428
+ btnPayNowSingleCard.isHidden = false
429
+ viewSingleSavedCardHeight.constant = 220
430
+ btnNext.isHidden = true
431
+ btnNextHeight.constant = 0
432
+ btnNextTopCon.constant = 0
433
+ } else {
434
+ viewTxtFieldCVVSingleCard.isHidden = true
435
+ btnPayNowSingleCard.isHidden = true
436
+ viewSingleSavedCardHeight.constant = 60
437
+ btnNext.isHidden = true
438
+ btnNextHeight.constant = 0
439
+ btnNextTopCon.constant = 8
440
+ }
441
+ }
442
+ }
443
+ else {
444
+ self.viewSingleSavedAccount.isHidden = true
445
+ self.viewBankFields.isHidden = true
446
+ self.viewBtnShowSavedCards.isHidden = false
447
+ self.lblBtnShowSaveCard.text = "Show Saved Cards"
448
+ self.viewBtnShowSavedCardHeight.constant = 50
449
+ self.viewBtnShowSavedCardTopCon.constant = 20
450
+ self.OTPView.isHidden = true
451
+ self.emailView.isHidden = true
452
+ self.viewCardFields.isHidden = false
453
+ self.viewSingleSavedCard.isHidden = true
454
+ self.viewChangeCard.isHidden = true
455
+ self.viewUpdateCard.isHidden = true
456
+ self.viewAddNewCard.isHidden = true
457
+ self.btnNext.isHidden = false
458
+ self.btnNextHeight.constant = 50
459
+ self.btnNextTopCon.constant = 20
460
+ self.viewTermsAndConditions.isHidden = true
461
+ self.viewCrypto.isHidden = true
462
+ }
463
+ }
464
+ else if selectedPaymentMethod == "Bank" {
465
+ if UserStoreSingleton.shared.isLoggedIn == true {
466
+
467
+ if isFrom == "NewAccount" {
468
+ viewNewBankAccount.isHidden = false
469
+ } else {
470
+ viewSingleSavedAccount.isHidden = false
471
+ }
472
+
473
+ self.viewSingleSavedAccount.isHidden = false
474
+ self.viewBankFields.isHidden = true
475
+ self.viewBtnShowSavedCards.isHidden = true
476
+ self.viewBtnShowSavedCardHeight.constant = 0
477
+ self.viewBtnShowSavedCardTopCon.constant = 0
478
+ self.OTPView.isHidden = true
479
+ self.emailView.isHidden = true
480
+ self.viewCardFields.isHidden = true
481
+ self.viewSingleSavedCard.isHidden = true
482
+ self.viewChangeCard.isHidden = true
483
+ self.viewUpdateCard.isHidden = true
484
+ self.viewAddNewCard.isHidden = true
485
+ self.btnNext.isHidden = true
486
+ self.btnNextHeight.constant = 0
487
+ self.btnNextTopCon.constant = 8
488
+
489
+ self.viewNewBankAccount.isHidden = true
490
+ self.viewCrypto.isHidden = true
491
+
492
+ self.viewTermsAndConditions.isHidden = true
493
+
494
+ self.viewChangedAccount.isHidden = true
495
+
496
+ if self.isSelectForPayBank {
497
+ self.viewTermAndConditionsSingleAccountView.isHidden = false
498
+ self.btnPayNowSingleAccountView.isHidden = false
499
+ self.viewSingleAccountViewHeight.constant = 170
500
+ } else {
501
+ self.viewTermAndConditionsSingleAccountView.isHidden = true
502
+ self.btnPayNowSingleAccountView.isHidden = true
503
+ self.viewSingleAccountViewHeight.constant = 68
504
+ self.btnNext.isHidden = true
505
+ self.btnNextHeight.constant = 0
506
+ self.btnNextTopCon.constant = 0
507
+ }
508
+
509
+ }
510
+ else {
511
+ self.viewBankFields.isHidden = false
512
+ self.viewBtnShowSavedCards.isHidden = false
513
+ self.lblBtnShowSaveCard.text = "Use Saved Accounts"
514
+ self.viewBtnShowSavedCardHeight.constant = 50
515
+ self.viewBtnShowSavedCardTopCon.constant = 20
516
+ self.OTPView.isHidden = true
517
+ self.emailView.isHidden = true
518
+ self.viewCardFields.isHidden = true
519
+ self.viewSingleSavedCard.isHidden = true
520
+ self.viewChangeCard.isHidden = true
521
+ self.viewUpdateCard.isHidden = true
522
+ self.viewAddNewCard.isHidden = true
523
+ self.btnNext.isHidden = false
524
+ self.btnNextHeight.constant = 50
525
+ self.btnNextTopCon.constant = 20
526
+ self.viewTermsAndConditions.isHidden = false
527
+ self.viewCrypto.isHidden = true
528
+ self.viewSingleSavedAccount.isHidden = true
529
+ self.viewChangedAccount.isHidden = true
530
+ }
531
+ }
532
+
533
+ //CRYPTO
534
+ setOnChainSelected()
535
+ }
536
+
537
+ override func viewWillDisappear(_ animated: Bool) {
538
+ super.viewWillDisappear(animated)
539
+ keyboardObserver.invalidate()
540
+ resetTimer()
541
+ }
542
+
543
+ func setUpTextFieldsDelegates() {
544
+ cardNumberTextField.textField.delegate = self
545
+ cardExpiryTextField.textField.delegate = self
546
+ cardCvvTextField.textField.delegate = self
547
+ cardNameTextField.textField.delegate = self
548
+
549
+ txtFieldEmail.delegate = self
550
+
551
+ txtFieldCVVSingleSavedCard.delegate = self
552
+ txtFieldExpireDateUpdateCardView.delegate = self
553
+ txtFieldCVVUpdateCardView.delegate = self
554
+ txtFieldNameOnCardUpdateCardView.delegate = self
555
+
556
+ txtFieldCardNumberNewCardView.delegate = self
557
+ txtFieldExpiryDateNewCardView.delegate = self
558
+ txtFieldCVVNewCardView.delegate = self
559
+ txtFieldNameOnCardNewCardView.delegate = self
560
+
561
+ txtFieldAccountName.delegate = self
562
+ txtFieldRoutingNumber.delegate = self
563
+ txtFieldAccountType.delegate = self
564
+ txtFieldAccountNumber.delegate = self
565
+
566
+ txtFieldAccountNameNewAccountView.delegate = self
567
+ txtFieldRoutingNumberNewAccountView.delegate = self
568
+ txtFieldAccountTypeNewAccountView.delegate = self
569
+ txtFieldAccountNumber.delegate = self
570
+
571
+ viewTextFieldCardNumber.layer.borderColor = UIColor.systemGray.cgColor
572
+ viewTxtFieldExpiryDate.layer.borderColor = UIColor.systemGray.cgColor
573
+ viewTxtFieldCVV.layer.borderColor = UIColor.systemGray.cgColor
574
+ viewTxtFieldNameOnCard.layer.borderColor = UIColor.systemGray.cgColor
575
+ viewTxtFieldEmail.layer.borderColor = UIColor.systemGray.cgColor
576
+ viewTxtFieldCVVSingleSavedCard.layer.borderColor = UIColor.systemGray.cgColor
577
+ //Update Card View
578
+ viewTxtFieldExpireDateUpdateCardView.layer.borderColor = UIColor.systemGray.cgColor
579
+ viewtxtFieldCVVUpdateCardView.layer.borderColor = UIColor.systemGray.cgColor
580
+ viewTxtFieldNameOnCardUpdateCardView.layer.borderColor = UIColor.systemGray.cgColor
581
+ //NEW Card View
582
+ viewtxtFieldCardNumberNewCardView.layer.borderColor = UIColor.systemGray.cgColor
583
+ viewtxtFieldExpiryDateNewCardView.layer.borderColor = UIColor.systemGray.cgColor
584
+ viewtxtFieldCVVNewCardView.layer.borderColor = UIColor.systemGray.cgColor
585
+ viewtxtFieldNameOnCardNewCardView.layer.borderColor = UIColor.systemGray.cgColor
586
+ //Bank View
587
+ viewtxtFieldAccountName.layer.borderColor = UIColor.systemGray.cgColor
588
+ viewtxtFieldRoutingNumber.layer.borderColor = UIColor.systemGray.cgColor
589
+ viewtxtFieldAccountType.layer.borderColor = UIColor.systemGray.cgColor
590
+ viewtxtFieldAccountNumber.layer.borderColor = UIColor.systemGray.cgColor
591
+ //New Bank Account View
592
+ viewtxtFieldAccountNameNewAccountView.layer.borderColor = UIColor.systemGray.cgColor
593
+ viewtxtFieldRoutingNumberNewAccountView.layer.borderColor = UIColor.systemGray.cgColor
594
+ viewtxtFieldAccountTypeNewAccountView.layer.borderColor = UIColor.systemGray.cgColor
595
+ viewtxtFieldAccountNumberNewAccountView.layer.borderColor = UIColor.systemGray.cgColor
596
+ }
597
+
598
+ //MARK: - Term & Conditions setup for normal bank fields view
599
+ func setUpTermAndConditionsButton() {
600
+ let string = "TERMS & CONDITIONS"
601
+ let attributedString = NSMutableAttributedString(string: string)
602
+
603
+ attributedString.addAttribute(
604
+ .font,
605
+ value: UIFont.systemFont(ofSize: 12, weight: .medium),
606
+ range: NSRange(location: 0, length: string.count)
607
+ )
608
+
609
+ // Assign the attributed string to the label
610
+ lblTermsAndConditions.attributedText = attributedString
611
+ lblTermsAndConditions.isUserInteractionEnabled = true
612
+ // Add tap gesture recognizer
613
+ let tap = UITapGestureRecognizer(target: self, action: #selector(aboutTerms))
614
+ lblTermsAndConditions.addGestureRecognizer(tap)
615
+ }
616
+
617
+ @objc func aboutTerms(sender: UITapGestureRecognizer) {
618
+ guard let vc = UIStoryboard(name: "EasyPaySdk", bundle: Bundle.easyPayBundle).instantiateViewController(withIdentifier: "TermAndConditionsVC") as? TermAndConditionsVC else {
619
+ print("Error: Could not find TermAndConditionsVC in EasyPaySdk storyboard.")
620
+ return
621
+ }
622
+ vc.modalPresentationStyle = .overFullScreen
623
+ present(vc, animated: true, completion: nil)
624
+ }
625
+
626
+ //MARK: - Term & Conditions setup for Signle saved bank account view
627
+ func setUpTermAndConditionsForSingleSavedAccountButton() {
628
+ let string = "TERMS & CONDITIONS"
629
+ let attributedString = NSMutableAttributedString(string: string)
630
+
631
+ attributedString.addAttribute(
632
+ .font,
633
+ value: UIFont.systemFont(ofSize: 12, weight: .medium),
634
+ range: NSRange(location: 0, length: string.count)
635
+ )
636
+
637
+ // Assign the attributed string to the label
638
+ lblTermsAndCondtionsSingleAccount.attributedText = attributedString
639
+ lblTermsAndCondtionsSingleAccount.isUserInteractionEnabled = true
640
+ // Add tap gesture recognizer
641
+ let tap = UITapGestureRecognizer(target: self, action: #selector(aboutTermsAndConditions))
642
+ lblTermsAndCondtionsSingleAccount.addGestureRecognizer(tap)
643
+ }
644
+
645
+ @objc func aboutTermsAndConditions(sender: UITapGestureRecognizer) {
646
+ guard let vc = UIStoryboard(name: "EasyPaySdk", bundle: Bundle.easyPayBundle).instantiateViewController(withIdentifier: "TermAndConditionsVC") as? TermAndConditionsVC else {
647
+ print("Error: Could not find TermAndConditionsVC in EasyPaySdk storyboard.")
648
+ return
649
+ }
650
+ vc.modalPresentationStyle = .overFullScreen
651
+ present(vc, animated: true, completion: nil)
652
+ }
653
+
654
+ func didPassTextBack(_ text: String) {
655
+ print("Received text: \(text)")
656
+ isFrom = text
657
+ }
658
+
659
+ override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
660
+ guard keyPath == "contentSize" else {
661
+ super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
662
+ return
663
+ }
664
+ // Update height for tblViewSavedCards
665
+ if let tableView = object as? UITableView, tableView == tblViewSavedCardsList {
666
+ tblViewSavedCardListHeight.constant = tblViewSavedCardsList.contentSize.height
667
+ }
668
+ // Update height for tblViewSavedAccounts
669
+ else if let tableView = object as? UITableView, tableView == tblViewSavedBankAccounts {
670
+ tblViewSavedBankAccountsHeight.constant = tblViewSavedBankAccounts.contentSize.height
671
+ }
672
+
673
+ UIView.animate(withDuration: 0.5) {
674
+ self.updateViewConstraints()
675
+ }
676
+ }
677
+
678
+ private func setupTextFields() {
679
+ let textFields = [txtFieldOTPText1, txtFieldOTPText2, txtFieldOTPText3, txtFieldOTPText4, txtFieldOTPText5, txtFieldOTPText6]
680
+
681
+ for textField in textFields {
682
+ textField?.delegate = self
683
+ textField?.textContentType = .oneTimeCode
684
+ textField?.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
685
+ }
686
+ }
687
+
688
+ func configureWith(request: Request, delegate: EasyPayViewControllerDelegate?) {
689
+ self.request = request
690
+ self.delegate = delegate
691
+ }
692
+
693
+ @objc func dismissKeyboard() {
694
+ self.view.endEditing(true)
695
+ }
696
+
697
+ private func startTimer() {
698
+ timeRemaining = 180 // 3 minutes
699
+ lblOtpTimer.text = formatTime(timeRemaining)
700
+ timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
701
+ }
702
+
703
+ private func stopTimer() {
704
+ timer?.invalidate()
705
+ timer = nil
706
+ }
707
+
708
+ @objc private func updateTimer() {
709
+ if timeRemaining > 0 {
710
+ timeRemaining -= 1
711
+ lblOtpTimer.text = formatTime(timeRemaining)
712
+ } else {
713
+ stopTimer()
714
+ btnResendOTP.isHidden = false
715
+ imgEsclamationMark.isHidden = true
716
+ lblOtpTimer.isHidden = true
717
+ lblUntillResendOtp.isHidden = true
718
+ }
719
+ }
720
+
721
+ private func formatTime(_ seconds: Int) -> String {
722
+ let minutes = seconds / 60
723
+ let seconds = seconds % 60
724
+ return String(format: "%02d:%02d", minutes, seconds)
725
+ }
726
+
727
+ //MARK: - Crypto Functions
728
+
729
+ // Method to generate QR code from a string
730
+ private func generateQRCode(from string: String) -> UIImage? {
731
+ // Get data from the string
732
+ guard let data = string.data(using: .ascii) else { return nil }
733
+ // Get a QR CIFilter
734
+ guard let qrFilter = CIFilter(name: "CIQRCodeGenerator") else { return nil }
735
+ qrFilter.setValue(data, forKey: "inputMessage")
736
+ // Get the output image
737
+ guard let qrImage = qrFilter.outputImage else { return nil }
738
+ // Scale the image
739
+ let transform = CGAffineTransform(scaleX: 10, y: 10)
740
+ let scaledQrImage = qrImage.transformed(by: transform)
741
+ // Do some processing to get the UIImage
742
+ let context = CIContext()
743
+ guard let cgImage = context.createCGImage(scaledQrImage, from: scaledQrImage.extent) else { return nil }
744
+
745
+ return UIImage(cgImage: cgImage)
746
+ }
747
+
748
+ func startCryptoTimer() {
749
+ resetTimer()
750
+
751
+ lblTimerCrypto.text = formatTime(seconds: totalTimeInSeconds)
752
+
753
+ cryptoTimer = DispatchSource.makeTimerSource()
754
+ cryptoTimer?.schedule(deadline: .now(), repeating: 1.0)
755
+ cryptoTimer?.setEventHandler { [weak self] in
756
+ guard let self = self else { return }
757
+
758
+ DispatchQueue.main.async {
759
+ if self.totalTimeInSeconds > 0 {
760
+ self.totalTimeInSeconds -= 1
761
+ self.lblTimerCrypto.text = self.formatTime(seconds: self.totalTimeInSeconds)
762
+ } else {
763
+ self.resetTimer()
764
+ // Hide crypto view and show try again view when time is up
765
+ self.viewCryptoQRCode.isHidden = true
766
+ self.viewQrCodeHeight.constant = 225
767
+ self.viewCryptoHeight.constant = 225
768
+ self.viewCryptoTryAgain.isHidden = false
769
+ }
770
+ }
771
+ }
772
+
773
+ cryptoTimer?.resume()
774
+ isTimerRunning = true
775
+
776
+ // Start the 30-second interval timer
777
+ startCryptoInfoTimer()
778
+ }
779
+
780
+ func resetTimer() {
781
+ cryptoTimer?.cancel()
782
+ cryptoTimer = nil
783
+ totalTimeInSeconds = 300
784
+ isTimerRunning = false
785
+
786
+ // Stop the 30-second interval timer
787
+ stopCryptoInfoTimer()
788
+ }
789
+
790
+ func formatTime(seconds: Int) -> String {
791
+ let minutes = seconds / 60
792
+ let remainingSeconds = seconds % 60
793
+ return String(format: "%02d:%02d", minutes, remainingSeconds)
794
+ }
795
+
796
+ func startCryptoInfoTimer() {
797
+ // Invalidate any existing timer
798
+ cryptoInfoTimer?.invalidate()
799
+ // Schedule a new timer to call getCryptoPaymentInfoApi every 30 seconds
800
+ cryptoInfoTimer = Timer.scheduledTimer(withTimeInterval: 30.0, repeats: true) { [weak self] _ in
801
+ guard let self = self else { return }
802
+ if let chargeId = self.chargeId {
803
+ self.getCryptoPaymentInfoApi(chargeId: chargeId)
804
+ }
805
+ }
806
+ }
807
+
808
+ func stopCryptoInfoTimer() {
809
+ cryptoInfoTimer?.invalidate()
810
+ cryptoInfoTimer = nil
811
+ }
812
+
813
+ @IBAction func actionBtnClose(_ sender: UIButton) {
814
+ UserStoreSingleton.shared.isLoggedIn = false
815
+ self.dismiss(animated: true)
816
+ }
817
+
818
+ @IBAction func actionBtnShowSavedCards(_ sender: UIButton) {
819
+ emailView.isHidden = false
820
+ OTPView.isHidden = true
821
+ }
822
+
823
+ @IBAction func actionBtnCloseEmailView(_ sender: UIButton) {
824
+ emailView.isHidden = true
825
+ }
826
+
827
+ @IBAction func actionBtnCloseOTPView(_ sender: UIButton) {
828
+ stopTimer()
829
+ OTPView.isHidden = true
830
+ }
831
+
832
+ @IBAction func actionBtnResendOTP(_ sender: UIButton) {
833
+ startTimer()
834
+ btnResendOTP.isHidden = true
835
+ imgEsclamationMark.isHidden = false
836
+ lblOtpTimer.isHidden = false
837
+ lblUntillResendOtp.isHidden = false
838
+
839
+ //Call email verification APi
840
+ emailVerificationApi()
841
+ }
842
+
843
+ @IBAction func actionBtnSelectSingleSavedCard(_ sender: UIButton) {
844
+ // Toggle the boolean property
845
+ isSelectForPay.toggle()
846
+ // Update the button image based on the state
847
+ let imageName = isSelectForPay ? "circle.inset.filled" : "circle"
848
+ sender.setImage(UIImage(systemName: imageName), for: .normal)
849
+ // Show or hide the view based on the state
850
+ if isSelectForPay {
851
+ viewTxtFieldCVVSingleCard.isHidden = false
852
+ btnPayNowSingleCard.isHidden = false
853
+ viewSingleSavedCardHeight.constant = 220
854
+ btnNext.isHidden = true
855
+ btnNextHeight.constant = 0
856
+ btnNextTopCon.constant = 0
857
+ if let firstCard = savedCards.first {
858
+ // Update the selectedCard to the firstCard or the card that is currently selected
859
+ selectedCard = firstCard
860
+ }
861
+ } else {
862
+ viewTxtFieldCVVSingleCard.isHidden = true
863
+ btnPayNowSingleCard.isHidden = true
864
+ viewSingleSavedCardHeight.constant = 60
865
+ btnNext.isHidden = true
866
+ btnNextHeight.constant = 0
867
+ btnNextTopCon.constant = 8
868
+ }
869
+ }
870
+
871
+ @IBAction func actionBtnChangeSavedCard(_ sender: UIButton) {
872
+ self.viewChangeCard.isHidden = false
873
+ self.viewCardFields.isHidden = true
874
+ self.viewSingleSavedCard.isHidden = true
875
+ self.viewBtnShowSavedCards.isHidden = true
876
+ self.viewBtnShowSavedCardHeight.constant = 0
877
+ self.viewBtnShowSavedCardTopCon.constant = 0
878
+ self.viewUpdateCard.isHidden = true
879
+ self.viewAddNewCard.isHidden = true
880
+ }
881
+
882
+ @IBAction func actionBtnCloseChangedCardView(_ sender: UIButton) {
883
+ self.viewChangeCard.isHidden = true
884
+ self.viewSingleSavedCard.isHidden = false
885
+ self.viewCardFields.isHidden = true
886
+ self.viewBtnShowSavedCards.isHidden = true
887
+ self.viewBtnShowSavedCardHeight.constant = 0
888
+ self.viewBtnShowSavedCardTopCon.constant = 0
889
+ self.viewUpdateCard.isHidden = true
890
+ self.viewAddNewCard.isHidden = true
891
+ }
892
+
893
+ @IBAction func actionBtnPayNowSingleSavedCard(_ sender: UIButton) {
894
+ // Check if billingInfoData is not nil or empty
895
+ if let billingInfoData = request.billingInfoData,
896
+ let json = try? JSONSerialization.jsonObject(with: billingInfoData, options: []),
897
+ let jsonDict = json as? [String: Any], !jsonDict.isEmpty {
898
+ print("Billing Info Data: \(jsonDict)")
899
+
900
+ let cvvText = txtFieldCVVSingleSavedCard.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
901
+
902
+ // Show an alert if the CVV field is empty
903
+ if cvvText.isEmpty {
904
+ let alert = UIAlertController(title: "Missing CVV", message: "Please enter the CVV to proceed.", preferredStyle: .alert)
905
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
906
+ self.present(alert, animated: true, completion: nil)
907
+ return
908
+ }
909
+
910
+ // Instantiate BillingInfoVC and pass the selected card data and CVV text
911
+ let billingInfoVC = UIStoryboard(name: "EasyPaySdk", bundle: .easyPayBundle).instantiateViewController(withIdentifier: "BillingInfoVC") as! BillingInfoVC
912
+
913
+ billingInfoVC.isFrom = "SavedCards"
914
+ billingInfoVC.selectedCard = selectedCard // Passing the selected card data
915
+ billingInfoVC.cvvText = cvvText // Passing the CVV text
916
+ billingInfoVC.amount = Int(request.amount)
917
+ billingInfoVC.selectedPaymentMethod = selectedPaymentMethod
918
+
919
+ // Pass the billing info data and auth token
920
+ if let billingInfoData = request.billingInfoData,
921
+ let json = try? JSONSerialization.jsonObject(with: billingInfoData, options: []),
922
+ let jsonDict = json as? [String: Any] {
923
+ billingInfoVC.billingInfoData = jsonDict
924
+ }
925
+
926
+ // Navigate to BillingInfoVC
927
+ self.navigationController?.pushViewController(billingInfoVC, animated: true)
928
+ }
929
+ else {
930
+ // If billingInfoData is nil or empty, set the button title to "Pay Now"
931
+ let cvvText = txtFieldCVVSingleSavedCard.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
932
+
933
+ // Show an alert if the CVV field is empty
934
+ if cvvText.isEmpty {
935
+ let alert = UIAlertController(title: "Missing CVV", message: "Please enter the CVV to proceed.", preferredStyle: .alert)
936
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
937
+ self.present(alert, animated: true, completion: nil)
938
+ return
939
+ }
940
+ else {
941
+ paymentIntentFromShowSavedCardApi()
942
+ }
943
+ }
944
+ }
945
+
946
+ @IBAction func actionBtnSelectUpdateCardView(_ sender: UIButton) {
947
+
948
+ }
949
+
950
+ @IBAction func actionBtnUpdateCard(_ sender: UIButton) {
951
+ // Check if a card ID is selected
952
+ guard let cardID = selectedCardID else {
953
+ let alert = UIAlertController(title: "No Card Selected", message: "Please select a card to update.", preferredStyle: .alert)
954
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
955
+ self.present(alert, animated: true, completion: nil)
956
+ return
957
+ }
958
+
959
+ // Call the updateCardApi with the selected card's ID
960
+ updateCardApi(cardID: cardID)
961
+ }
962
+
963
+ @IBAction func actionBtnCloseUpdateCardView(_ sender: UIButton) {
964
+ self.viewChangeCard.isHidden = false
965
+ self.viewSingleSavedCard.isHidden = true
966
+ self.viewCardFields.isHidden = true
967
+ self.viewBtnShowSavedCards.isHidden = true
968
+ self.viewBtnShowSavedCardHeight.constant = 0
969
+ self.viewBtnShowSavedCardTopCon.constant = 0
970
+ self.viewUpdateCard.isHidden = true
971
+ self.viewAddNewCard.isHidden = true
972
+ }
973
+
974
+ @IBAction func actionBtnCloseNewCardView(_ sender: UIButton) {
975
+ self.viewChangeCard.isHidden = false
976
+ self.viewSingleSavedCard.isHidden = true
977
+ self.viewCardFields.isHidden = true
978
+ self.viewBtnShowSavedCards.isHidden = true
979
+ self.viewBtnShowSavedCardHeight.constant = 0
980
+ self.viewBtnShowSavedCardTopCon.constant = 0
981
+ self.viewUpdateCard.isHidden = true
982
+ self.viewAddNewCard.isHidden = true
983
+ }
984
+
985
+ @IBAction func actionBtnPayNowNewCardView(_ sender: UIButton) {
986
+ if UserStoreSingleton.shared.isLoggedIn == true {
987
+ //if billing info is availbale
988
+ if let billingInfoData = request.billingInfoData,
989
+ let json = try? JSONSerialization.jsonObject(with: billingInfoData, options: []),
990
+ let jsonDict = json as? [String: Any], !jsonDict.isEmpty {
991
+ print("Billing Info Data: \(jsonDict)")
992
+
993
+ // Retrieve and trim text field values (removing leading and trailing whitespace)
994
+ let cardNumber = txtFieldCardNumberNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
995
+ let expiryDate = txtFieldExpiryDateNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
996
+ let cvv = txtFieldCVVNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
997
+ let nameOnCard = txtFieldNameOnCardNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
998
+
999
+ // Check if any field is empty
1000
+ if cardNumber.isEmpty || expiryDate.isEmpty || cvv.isEmpty || nameOnCard.isEmpty {
1001
+ let alert = UIAlertController(title: "Missing Information", message: "Please fill in all card details.", preferredStyle: .alert)
1002
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
1003
+ self.present(alert, animated: true, completion: nil)
1004
+ return
1005
+ }
1006
+
1007
+ // Validate expiry date format
1008
+ let expiryDateFormat = "MM/yyyy"
1009
+ let dateFormatter = DateFormatter()
1010
+ dateFormatter.dateFormat = expiryDateFormat
1011
+ dateFormatter.locale = Locale(identifier: "en_US_POSIX")
1012
+
1013
+ // Split the expiry date and validate its format
1014
+ let exp = expiryDate.split(separator: "/")
1015
+ if exp.count != 2 || exp[0].count != 2 || exp[1].count != 4 {
1016
+ let alert = UIAlertController(title: "Invalid Expiry Date", message: "Please enter the expiry date in the format MM/yyyy.", preferredStyle: .alert)
1017
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
1018
+ self.present(alert, animated: true, completion: nil)
1019
+ return
1020
+ }
1021
+
1022
+ // Check if the expiry date is valid
1023
+ if let expiryDateObj = dateFormatter.date(from: expiryDate) {
1024
+ // Check if the expiry date is in the past
1025
+ let currentDate = Date()
1026
+ let calendar = Calendar.current
1027
+ let currentMonthYear = calendar.date(from: calendar.dateComponents([.year, .month], from: currentDate))
1028
+ if expiryDateObj < currentMonthYear! {
1029
+ let alert = UIAlertController(title: "Expired Card", message: "The expiry date cannot be in the past. Please enter a valid expiry date.", preferredStyle: .alert)
1030
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
1031
+ self.present(alert, animated: true, completion: nil)
1032
+ return
1033
+ }
1034
+ } else {
1035
+ let alert = UIAlertController(title: "Invalid Expiry Date", message: "Please enter the expiry date in the format MM/yyyy.", preferredStyle: .alert)
1036
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
1037
+ self.present(alert, animated: true, completion: nil)
1038
+ return
1039
+ }
1040
+
1041
+ // Instantiate BillingInfoVC and pass the card details
1042
+ let billingInfoVC = UIStoryboard(name: "EasyPaySdk", bundle: .easyPayBundle).instantiateViewController(withIdentifier: "BillingInfoVC") as! BillingInfoVC
1043
+
1044
+ // Pass the card details
1045
+ billingInfoVC.cardNumber = cardNumber
1046
+ billingInfoVC.expiryDate = expiryDate
1047
+ billingInfoVC.cvv = cvv
1048
+ billingInfoVC.nameOnCard = nameOnCard
1049
+ billingInfoVC.isSavedNewCard = isSavedNewCardForFuture
1050
+
1051
+ billingInfoVC.isFrom = "AddNewCard"
1052
+ billingInfoVC.amount = Int(request.amount)
1053
+ billingInfoVC.selectedPaymentMethod = selectedPaymentMethod
1054
+
1055
+ // Pass the billing info data and auth token
1056
+ if let billingInfoData = request.billingInfoData,
1057
+ let json = try? JSONSerialization.jsonObject(with: billingInfoData, options: []),
1058
+ let jsonDict = json as? [String: Any] {
1059
+ billingInfoVC.billingInfoData = jsonDict
1060
+ }
1061
+
1062
+ // Navigate to BillingInfoVC
1063
+ self.navigationController?.pushViewController(billingInfoVC, animated: true)
1064
+ }
1065
+ else {
1066
+ paymentIntentFromAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
1067
+ }
1068
+ }
1069
+ }
1070
+
1071
+ @IBAction func actionBtnSaveCardForFutureNewCardView(_ sender: UIButton) {
1072
+ isSavedNewCardForFuture.toggle()
1073
+ // Change the image based on the state
1074
+ let imageName = isSavedNewCardForFuture ? "checkmark.square.fill" : "square"
1075
+ btnSavedCardForFutureNewCardView.setImage(UIImage(systemName: imageName), for: .normal)
1076
+ }
1077
+
1078
+ @IBAction func actionBtnSaveCardForFuture(_ sender: UIButton) {
1079
+ isSavedForFuture.toggle()
1080
+ // Change the image based on the state
1081
+ let imageName = isSavedForFuture ? "checkmark.square.fill" : "square"
1082
+ btnCheckBoxSavedCard.setImage(UIImage(systemName: imageName), for: .normal)
1083
+ }
1084
+
1085
+ // MARK: - Action Button Next
1086
+ @IBAction func actionBtnNext(_ sender: UIButton) {
1087
+ // Check if billing info data exists and is valid JSON
1088
+ if let billingInfoData = request.billingInfoData {
1089
+ DispatchQueue.global(qos: .userInitiated).async { [weak self] in
1090
+ // Perform JSON parsing in a background thread
1091
+ guard let json = try? JSONSerialization.jsonObject(with: billingInfoData, options: []),
1092
+ let jsonDict = json as? [String: Any], !jsonDict.isEmpty else {
1093
+ return
1094
+ }
1095
+
1096
+ DispatchQueue.main.async {
1097
+ // Ensure UI updates happen on the main thread
1098
+ guard let self = self else { return }
1099
+
1100
+ if self.selectedPaymentMethod == "Card" {
1101
+ // Validate card fields
1102
+ if self.cardNumberTextField.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
1103
+ self.showAlert(title: "Missing Information", message: "Card Number is required.")
1104
+ } else if self.cardExpiryTextField.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
1105
+ self.showAlert(title: "Missing Information", message: "Card Expiry Date is required.")
1106
+ } else if self.cardCvvTextField.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
1107
+ self.showAlert(title: "Missing Information", message: "Card CVV Number is required.")
1108
+ } else if self.cardNameTextField.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
1109
+ self.showAlert(title: "Missing Information", message: "Card Name is required.")
1110
+ } else {
1111
+ // Proceed to BillingInfoVC
1112
+ let vc = EasyPaySdk.instantiateViewController(withIdentifier: "BillingInfoVC") as! BillingInfoVC
1113
+ vc.billingInfoData = jsonDict
1114
+ vc.cardNumber = self.cardNumberTextField.text
1115
+ vc.expiryDate = self.cardExpiryTextField.text
1116
+ vc.cvv = self.cardCvvTextField.text
1117
+ vc.nameOnCard = self.cardNameTextField.text
1118
+ vc.selectedPaymentMethod = self.selectedPaymentMethod
1119
+ vc.isSavedForFuture = self.isSavedForFuture
1120
+ self.navigationController?.pushViewController(vc, animated: true)
1121
+ }
1122
+ }
1123
+ else if self.selectedPaymentMethod == "Bank" {
1124
+ // Bank Case
1125
+ if self.txtFieldAccountName.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
1126
+ self.showAlert(title: "Missing Information", message: "Bank account name is required.")
1127
+ } else if self.txtFieldRoutingNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
1128
+ self.showAlert(title: "Missing Information", message: "Routing number is required.")
1129
+ } else if self.txtFieldAccountType.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
1130
+ self.showAlert(title: "Missing Information", message: "Bank account type is required.")
1131
+ } else if self.txtFieldAccountNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
1132
+ self.showAlert(title: "Missing Information", message: "Bank account number is required.")
1133
+ }
1134
+ else if !self.agreeTermsAndCondtition {
1135
+ self.showAlert(title: "Terms and Conditions", message: "Please agree to the terms and conditions")
1136
+ return
1137
+ }
1138
+ else {
1139
+ // Proceed to BillingInfoVC
1140
+ let vc = EasyPaySdk.instantiateViewController(withIdentifier: "BillingInfoVC") as! BillingInfoVC
1141
+ vc.accountName = self.txtFieldAccountName.text
1142
+ vc.routingNumber = self.txtFieldRoutingNumber.text
1143
+ vc.accountType = self.txtFieldAccountType.text
1144
+ vc.accountNumber = self.txtFieldAccountNumber.text
1145
+ vc.billingInfoData = jsonDict
1146
+ vc.selectedPaymentMethod = self.selectedPaymentMethod
1147
+ vc.isSavedForFuture = self.isSavedForFuture
1148
+ vc.isFrom = "NormalBankPayWithoutSave"
1149
+ self.navigationController?.pushViewController(vc, animated: true)
1150
+ }
1151
+ }
1152
+ }
1153
+ }
1154
+ } else {
1155
+ // Billing info is nil or empty
1156
+ if selectedPaymentMethod == "Card" {
1157
+ // Card Case
1158
+ if cardNumberTextField.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
1159
+ showAlert(title: "Missing Information", message: "Card Number is required.")
1160
+ } else if cardExpiryTextField.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
1161
+ showAlert(title: "Missing Information", message: "Card Expiry Date is required.")
1162
+ } else if cardCvvTextField.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
1163
+ showAlert(title: "Missing Information", message: "Card CVV Number is required.")
1164
+ } else if cardNameTextField.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
1165
+ showAlert(title: "Missing Information", message: "Card Name is required.")
1166
+ } else {
1167
+ // Navigate to EmailVerificationVC if isSavedForFuture is true, else call paymentIntentApi
1168
+ if isSavedForFuture {
1169
+ navigateCardDataToEmailVerificationVC()
1170
+ } else {
1171
+ paymentIntentApi()
1172
+ }
1173
+ }
1174
+ }
1175
+ else if selectedPaymentMethod == "Bank" {
1176
+ // Bank Case
1177
+ if txtFieldAccountName.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
1178
+ showAlert(title: "Missing Information", message: "Bank account name is required.")
1179
+ } else if txtFieldRoutingNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
1180
+ showAlert(title: "Missing Information", message: "Routing number is required.")
1181
+ } else if txtFieldAccountType.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
1182
+ showAlert(title: "Missing Information", message: "Bank account type is required.")
1183
+ } else if txtFieldAccountNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
1184
+ showAlert(title: "Missing Information", message: "Bank account number is required.")
1185
+ }
1186
+ else if !agreeTermsAndCondtition {
1187
+ showAlert(title: "Terms and Conditions", message: "Please agree to the terms and conditions")
1188
+ return
1189
+ }
1190
+ else {
1191
+ if isSavedForFuture {
1192
+ navigateBankDataToEmailVerificationVC()
1193
+ } else {
1194
+ accountChargeApi()
1195
+ }
1196
+ }
1197
+ }
1198
+ }
1199
+ }
1200
+
1201
+ // Function to navigate to EmailVerificationVC
1202
+ func navigateCardDataToEmailVerificationVC() {
1203
+ if let emailVerificationVC = storyboard?.instantiateViewController(withIdentifier: "EmailVerificationVC") as? EmailVerificationVC {
1204
+ emailVerificationVC.cardNumber = self.cardNumberTextField.text
1205
+ emailVerificationVC.expiryDate = self.cardExpiryTextField.text
1206
+ emailVerificationVC.cvv = self.cardCvvTextField.text
1207
+ emailVerificationVC.nameOnCard = self.cardNameTextField.text
1208
+ emailVerificationVC.selectedPaymentMethod = self.selectedPaymentMethod
1209
+ emailVerificationVC.isSavedForFuture = isSavedForFuture
1210
+ emailVerificationVC.easyPayDelegate = self.easyPayDelegate // Pass delegate if needed
1211
+ self.navigationController?.pushViewController(emailVerificationVC, animated: true)
1212
+ }
1213
+ }
1214
+
1215
+ func navigateBankDataToEmailVerificationVC() {
1216
+ if let emailVerificationVC = storyboard?.instantiateViewController(withIdentifier: "EmailVerificationVC") as? EmailVerificationVC {
1217
+ emailVerificationVC.accountName = txtFieldAccountName.text
1218
+ emailVerificationVC.routingNumber = txtFieldRoutingNumber.text
1219
+ emailVerificationVC.accountType = txtFieldAccountType.text
1220
+ emailVerificationVC.accountNumber = txtFieldAccountNumber.text
1221
+ emailVerificationVC.selectedPaymentMethod = self.selectedPaymentMethod
1222
+ emailVerificationVC.isSavedForFuture = isSavedForFuture
1223
+ emailVerificationVC.easyPayDelegate = self.easyPayDelegate // Pass delegate if needed
1224
+ self.navigationController?.pushViewController(emailVerificationVC, animated: true)
1225
+ }
1226
+ }
1227
+
1228
+ @IBAction func actionBtnSettings(_ sender: UIButton) {
1229
+ settingsView.isHidden = false
1230
+ }
1231
+
1232
+ @IBAction func actionBtnLogout(_ sender: UIButton) {
1233
+ UserStoreSingleton.shared.clearUserData()
1234
+ UserStoreSingleton.shared.isLoggedIn = false
1235
+ settingsView.isHidden = true
1236
+ btnSettings.isHidden = true
1237
+ txtFieldEmail.text = ""
1238
+ txtFieldOTPText1.text = ""
1239
+ txtFieldOTPText2.text = ""
1240
+ txtFieldOTPText3.text = ""
1241
+ txtFieldOTPText4.text = ""
1242
+ txtFieldOTPText5.text = ""
1243
+ txtFieldOTPText6.text = ""
1244
+
1245
+ if selectedPaymentMethod == "Card" {
1246
+ self.viewSingleSavedAccount.isHidden = true
1247
+ self.viewBankFields.isHidden = true
1248
+ self.viewBtnShowSavedCards.isHidden = false
1249
+ self.lblBtnShowSaveCard.text = "Show Saved Cards"
1250
+ self.viewBtnShowSavedCardHeight.constant = 50
1251
+ self.viewBtnShowSavedCardTopCon.constant = 20
1252
+ self.OTPView.isHidden = true
1253
+ self.emailView.isHidden = true
1254
+ self.viewCardFields.isHidden = false
1255
+ self.viewSingleSavedCard.isHidden = true
1256
+ self.viewChangeCard.isHidden = true
1257
+ self.viewUpdateCard.isHidden = true
1258
+ self.viewAddNewCard.isHidden = true
1259
+ self.btnNext.isHidden = false
1260
+ self.btnNextHeight.constant = 50
1261
+ self.btnNextTopCon.constant = 20
1262
+ self.viewTermsAndConditions.isHidden = true
1263
+ self.viewCrypto.isHidden = true
1264
+ }
1265
+ else if selectedPaymentMethod == "Bank" {
1266
+ self.viewBankFields.isHidden = false
1267
+ self.viewBtnShowSavedCards.isHidden = false
1268
+ self.lblBtnShowSaveCard.text = "Use Saved Accounts"
1269
+ self.viewBtnShowSavedCardHeight.constant = 50
1270
+ self.viewBtnShowSavedCardTopCon.constant = 20
1271
+ self.OTPView.isHidden = true
1272
+ self.emailView.isHidden = true
1273
+ self.viewCardFields.isHidden = true
1274
+ self.viewSingleSavedCard.isHidden = true
1275
+ self.viewChangeCard.isHidden = true
1276
+ self.viewUpdateCard.isHidden = true
1277
+ self.viewAddNewCard.isHidden = true
1278
+ self.btnNext.isHidden = false
1279
+ self.btnNextHeight.constant = 50
1280
+ self.btnNextTopCon.constant = 20
1281
+ self.viewTermsAndConditions.isHidden = false
1282
+ self.viewCrypto.isHidden = true
1283
+ self.viewSingleSavedAccount.isHidden = true
1284
+ self.viewChangedAccount.isHidden = true
1285
+ }
1286
+ }
1287
+
1288
+ @IBAction func actionBtnCloseSettingView(_ sender: UIButton) {
1289
+ settingsView.isHidden = true
1290
+ }
1291
+
1292
+ //MARK: - Bank View Buttons Actions
1293
+ @IBAction func actionBtnSelectAccountType(_ sender: UIButton) {
1294
+ UIView.animate(withDuration: 0.3) {
1295
+ self.viewAccountType.isHidden.toggle()
1296
+ }
1297
+ }
1298
+
1299
+ @IBAction func actionBtnSaveAccountForFuture(_ sender: UIButton) {
1300
+ isSavedForFuture.toggle()
1301
+ // Change the image based on the state
1302
+ let imageName = isSavedForFuture ? "checkmark.square.fill" : "square"
1303
+ btnCheckSavedAccountForFuture.setImage(UIImage(systemName: imageName), for: .normal)
1304
+ }
1305
+
1306
+ @IBAction func actionBtnCheckAgreeTermsConditions(_ sender: UIButton) {
1307
+ agreeTermsAndCondtition.toggle()
1308
+ // Change the image based on the state
1309
+ let imageName = agreeTermsAndCondtition ? "checkmark.square.fill" : "square"
1310
+ btnAgreeTermsConditions.setImage(UIImage(systemName: imageName), for: .normal)
1311
+ }
1312
+
1313
+ @IBAction func actionBtnSelectSingleAccountView(_ sender: UIButton) {
1314
+ // Toggle the boolean property
1315
+ isSelectForPayBank.toggle()
1316
+ // Update the button image based on the state
1317
+ let imageName = isSelectForPayBank ? "circle.inset.filled" : "circle"
1318
+ sender.setImage(UIImage(systemName: imageName), for: .normal)
1319
+ // Show or hide the view based on the state
1320
+ if isSelectForPayBank {
1321
+ viewTermAndConditionsSingleAccountView.isHidden = false
1322
+ btnPayNowSingleAccountView.isHidden = false
1323
+ viewSingleAccountViewHeight.constant = 170
1324
+ } else {
1325
+ viewTermAndConditionsSingleAccountView.isHidden = true
1326
+ btnPayNowSingleAccountView.isHidden = true
1327
+ viewSingleAccountViewHeight.constant = 68
1328
+ btnNext.isHidden = true
1329
+ btnNextHeight.constant = 0
1330
+ btnNextTopCon.constant = 0
1331
+ }
1332
+ }
1333
+
1334
+ @IBAction func actionBtnChangeSingleAccountView(_ sender: UIButton) {
1335
+ self.viewChangedAccount.isHidden = false
1336
+ self.viewBankFields.isHidden = true
1337
+ self.viewSingleSavedAccount.isHidden = true
1338
+ }
1339
+
1340
+ @IBAction func actionBtnAgreeTermsConditionsSingleAccountView(_ sender: UIButton) {
1341
+ agreeTermsAndCondtition.toggle()
1342
+ // Change the image based on the state
1343
+ let imageName = agreeTermsAndCondtition ? "checkmark.square.fill" : "square"
1344
+ btnCheckAgreeTermsAndConditionsSingleAccount.setImage(UIImage(systemName: imageName), for: .normal)
1345
+ }
1346
+
1347
+ @IBAction func actionBtnPayNowSingleAccountView(_ sender: UIButton) {
1348
+ // Check if billingInfoData is not nil or empty
1349
+ if let billingInfoData = request.billingInfoData,
1350
+ let json = try? JSONSerialization.jsonObject(with: billingInfoData, options: []),
1351
+ let jsonDict = json as? [String: Any], !jsonDict.isEmpty {
1352
+
1353
+ // Check if the terms and conditions are agreed
1354
+ if !agreeTermsAndCondtition {
1355
+ showAlert(title: "Terms and Conditions", message: "Please agree to the terms and conditions")
1356
+ return
1357
+ }
1358
+
1359
+ // Proceed with the Pay Now action
1360
+ let vc = EasyPaySdk.instantiateViewController(withIdentifier: "BillingInfoVC") as! BillingInfoVC
1361
+ // Pass the customer_id and account_id to BillingInfoVC
1362
+ vc.customerID = selectedbankAccounts?.customer_id
1363
+ vc.accountID = selectedbankAccounts?.account_id
1364
+ vc.billingInfoData = jsonDict
1365
+ vc.isFrom = "SavedBank"
1366
+ vc.selectedPaymentMethod = "Bank"
1367
+ self.navigationController?.pushViewController(vc, animated: true)
1368
+ }
1369
+ else {
1370
+ //If Billing info is nil or empty
1371
+ if !agreeTermsAndCondtition {
1372
+ showAlert(title: "Terms and Conditions", message: "Please agree to the terms and conditions")
1373
+ return
1374
+ }
1375
+
1376
+ accountChargeSavedBankAccountApi()
1377
+ }
1378
+ }
1379
+
1380
+ @IBAction func actionBtnCloseChangeAccountView(_ sender: UIButton) {
1381
+ viewChangedAccount.isHidden = true
1382
+ viewSingleSavedAccount.isHidden = false
1383
+ }
1384
+
1385
+ @IBAction func actionBtnSaveAccountNewAccountView(_ sender: UIButton) {
1386
+ isSavedNewAccount.toggle()
1387
+ // Change the image based on the state
1388
+ let imageName = isSavedNewAccount ? "checkmark.square.fill" : "square"
1389
+ btnSavedNewAccountForFuture.setImage(UIImage(systemName: imageName), for: .normal)
1390
+ }
1391
+
1392
+ @IBAction func actionBtnAccontTypeNewAccountView(_ sender: UIButton) {
1393
+ UIView.animate(withDuration: 0.3) {
1394
+ self.viewAccountTypeNewAccountView.isHidden.toggle()
1395
+ }
1396
+ }
1397
+
1398
+ @IBAction func actionBtnPayNowNewAccountView(_ sender: UIButton) {
1399
+ // Check if billingInfoData is not nil or empty
1400
+ if let billingInfoData = request.billingInfoData,
1401
+ let json = try? JSONSerialization.jsonObject(with: billingInfoData, options: []),
1402
+ let jsonDict = json as? [String: Any], !jsonDict.isEmpty {
1403
+ print("Billing Info Data: \(jsonDict)")
1404
+
1405
+ // Retrieve and trim text field values (removing leading and trailing whitespace)
1406
+ let accountName = txtFieldAccountNameNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
1407
+ let routingNumber = txtFieldRoutingNumberNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
1408
+ let accountType = txtFieldAccountTypeNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
1409
+ let accountNumber = txtFieldAccountNumberNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
1410
+
1411
+ // Check if any field is empty
1412
+ if accountName.isEmpty || routingNumber.isEmpty || accountType.isEmpty || accountNumber.isEmpty {
1413
+ let alert = UIAlertController(title: "Missing Information", message: "Please fill in all bank details.", preferredStyle: .alert)
1414
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
1415
+ self.present(alert, animated: true, completion: nil)
1416
+ return
1417
+ }
1418
+
1419
+ // Determine the vc.isFrom value based on isSavedNewAccount
1420
+ let isSavedNewAccount = isSavedNewAccount
1421
+ let isFromValue = isSavedNewAccount ? "AddNewAccountWithSave" : "AddNewAccountWithoutSave"
1422
+
1423
+ // Proceed with the Pay Now action
1424
+ let vc = EasyPaySdk.instantiateViewController(withIdentifier: "BillingInfoVC") as! BillingInfoVC
1425
+ vc.accountName = accountName
1426
+ vc.routingNumber = routingNumber
1427
+ vc.accountType = accountType
1428
+ vc.accountNumber = accountNumber
1429
+ vc.billingInfoData = jsonDict
1430
+ vc.selectedPaymentMethod = "Bank"
1431
+ vc.isFrom = isFromValue
1432
+ vc.isSavedNewAccount = isSavedNewAccount
1433
+ vc.delegate = self
1434
+ self.navigationController?.pushViewController(vc, animated: true)
1435
+ }
1436
+ else {
1437
+ //If Billing info is nil or empty
1438
+ let isSavedNewAccount = isSavedNewAccount
1439
+ // If Billing info is nil or empty, call the appropriate API
1440
+ if isSavedNewAccount {
1441
+ // Call accountChargeApi if the account is saved
1442
+ accountChargeApi(customerId: UserStoreSingleton.shared.customerId)
1443
+ } else {
1444
+ // Call accountChargeAddNewAccountApi if the account is new
1445
+ accountChargeAddNewAccountApi()
1446
+ }
1447
+ }
1448
+ }
1449
+
1450
+ @IBAction func actionBtnCloseNewAccountView(_ sender: UIButton) {
1451
+ self.viewNewBankAccount.isHidden = true
1452
+ self.viewSingleSavedAccount.isHidden = true
1453
+ self.viewBankFields.isHidden = true
1454
+ self.viewBtnShowSavedCards.isHidden = true
1455
+ self.viewBtnShowSavedCardHeight.constant = 0
1456
+ self.viewBtnShowSavedCardTopCon.constant = 0
1457
+ self.OTPView.isHidden = true
1458
+ self.emailView.isHidden = true
1459
+ self.viewCardFields.isHidden = true
1460
+ self.viewSingleSavedCard.isHidden = true
1461
+ self.viewChangeCard.isHidden = true
1462
+ self.viewUpdateCard.isHidden = true
1463
+ self.viewAddNewCard.isHidden = true
1464
+ self.btnNext.isHidden = true
1465
+ self.btnNextHeight.constant = 0
1466
+ self.btnNextTopCon.constant = 8
1467
+ self.viewChangedAccount.isHidden = false
1468
+ }
1469
+
1470
+ //MARK: - Crypto View Buttons Actions
1471
+ @IBAction func actionBtnTryAgain(_ sender: UIButton) {
1472
+ // Call the crypto charge API again
1473
+ cryptoChargeApi()
1474
+ // Reset the view and timer
1475
+ DispatchQueue.main.async {
1476
+ self.viewCryptoQRCode.isHidden = false
1477
+ self.viewCryptoTryAgain.isHidden = true
1478
+ self.viewQrCodeHeight.constant = 440
1479
+ self.viewCryptoHeight.constant = 440
1480
+ self.resetTimer()
1481
+ self.startCryptoTimer()
1482
+ }
1483
+ }
1484
+
1485
+ func setOnChainSelected() {
1486
+ // Set the selected state for OnChain button
1487
+ btnOnChain.backgroundColor = .systemBlue
1488
+ btnOnChain.setTitleColor(.white, for: .normal)
1489
+ // Reset the state for Lightning button
1490
+ btnLightning.backgroundColor = .clear
1491
+ btnLightning.setTitleColor(.systemBlue, for: .normal)
1492
+ }
1493
+
1494
+ @IBAction func actionBtnOnChain(_ sender: UIButton) {
1495
+ // Set the selected state for OnChain button
1496
+ setOnChainSelected()
1497
+ // Generate QR code for Chain Invoice Address
1498
+ if let address = chainInvoiceAddress {
1499
+ qrImageView.image = generateQRCode(from: address)
1500
+ lblBTCAddress.text = "BTC Address: \(address)" // Update lblBTCAddress with the Chain Invoice Address
1501
+ }
1502
+ }
1503
+
1504
+ @IBAction func actionBtnLightning(_ sender: UIButton) {
1505
+ // Set the selected state for Lightning button
1506
+ btnLightning.backgroundColor = .systemBlue
1507
+ btnLightning.setTitleColor(.white, for: .normal)
1508
+ // Reset the state for OnChain button
1509
+ btnOnChain.backgroundColor = .clear
1510
+ btnOnChain.setTitleColor(.systemBlue, for: .normal)
1511
+
1512
+ // Generate QR code for Lightning URI
1513
+ if let uri = lightningURI {
1514
+ qrImageView.image = generateQRCode(from: uri)
1515
+ lblBTCAddress.text = "BTC Address: \(uri)" // Update lblBTCAddress with the Lightning URI
1516
+ }
1517
+ }
1518
+
1519
+ @IBAction func actionBtnCopyBTCAddress(_ sender: UIButton) {
1520
+ // Check if there is text to copy
1521
+ guard let btcAddressText = lblBTCAddress.text, !btcAddressText.isEmpty else {
1522
+ print("No address to copy")
1523
+ return
1524
+ }
1525
+
1526
+ // Extract the actual BTC address from the label text
1527
+ let components = btcAddressText.components(separatedBy: "BTC Address: ")
1528
+ if components.count > 1 {
1529
+ let btcAddress = components[1]
1530
+
1531
+ // Copy the address to the clipboard
1532
+ UIPasteboard.general.string = btcAddress
1533
+
1534
+ // Print success message to the console
1535
+ print("Address copied to clipboard successfully: \(btcAddress)")
1536
+
1537
+ // Optionally, show a toast message to the user
1538
+ showToast(message: "BTC Address copied to clipboard")
1539
+ }
1540
+ }
1541
+
1542
+ //MARK: - Send OTP Email Verification Api
1543
+ func emailVerificationApi() {
1544
+ showLoadingIndicator()
1545
+
1546
+ // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
1547
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.emailVerification.path()
1548
+
1549
+ guard let serviceURL = URL(string: fullURL) else {
1550
+ print("Invalid URL")
1551
+ hideLoadingIndicator()
1552
+ return
1553
+ }
1554
+
1555
+ var request = URLRequest(url: serviceURL)
1556
+ request.httpMethod = "POST"
1557
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
1558
+
1559
+ // Use the clientToken from your singleton
1560
+ let token = UserStoreSingleton.shared.clientToken
1561
+ print("Setting clientToken header: \(token ?? "None")")
1562
+ request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1563
+
1564
+ // Define the parameters for the request
1565
+ let params: [String: Any] = [
1566
+ "card_search_value": txtFieldEmail.text ?? "",
1567
+ "card_search_key": "email"
1568
+ ]
1569
+
1570
+ do {
1571
+ // Convert the dictionary to JSON
1572
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1573
+ request.httpBody = jsonData
1574
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
1575
+ print("JSON Payload: \(jsonString)")
1576
+ }
1577
+ } catch let error {
1578
+ print("Error creating JSON data: \(error)")
1579
+ hideLoadingIndicator()
1580
+ return
1581
+ }
1582
+
1583
+ let session = URLSession.shared
1584
+ let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
1585
+
1586
+ DispatchQueue.main.async {
1587
+ self.hideLoadingIndicator() // Stop loader when response is received
1588
+ }
1589
+
1590
+ if let error = error {
1591
+ print("Error: \(error.localizedDescription)")
1592
+ return
1593
+ }
1594
+
1595
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1596
+ print("Invalid response")
1597
+ return
1598
+ }
1599
+
1600
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1601
+ if let data = serviceData {
1602
+ do {
1603
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1604
+ print("Response Data: \(responseObject)")
1605
+ DispatchQueue.main.async {
1606
+ // Handle successful response
1607
+ }
1608
+ } else {
1609
+ print("Invalid JSON format")
1610
+ }
1611
+ } catch let jsonError {
1612
+ print("Error parsing JSON: \(jsonError)")
1613
+ }
1614
+ } else {
1615
+ print("No data received")
1616
+ }
1617
+ } else {
1618
+ print("HTTP Status Code: \(httpResponse.statusCode)")
1619
+ }
1620
+ }
1621
+ task.resume()
1622
+ }
1623
+
1624
+ //MARK: - OTP Verification Api
1625
+ func otpVerificationApi() {
1626
+ showLoadingIndicator()
1627
+
1628
+ // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
1629
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.verifyOtp.path()
1630
+
1631
+ guard let serviceURL = URL(string: fullURL) else {
1632
+ print("Invalid URL")
1633
+ hideLoadingIndicator()
1634
+ return
1635
+ }
1636
+
1637
+ var request = URLRequest(url: serviceURL)
1638
+ request.httpMethod = "POST"
1639
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
1640
+
1641
+ let token = UserStoreSingleton.shared.clientToken
1642
+ print("Setting clientToken header: \(token ?? "None")")
1643
+ request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1644
+
1645
+ let params: [String: Any] = [
1646
+ "card_search_value": txtFieldEmail.text ?? "",
1647
+ "card_search_key": "email",
1648
+ "otp": getCombinedOTP()
1649
+ ]
1650
+
1651
+ do {
1652
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1653
+ request.httpBody = jsonData
1654
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
1655
+ print("JSON Payload: \(jsonString)")
1656
+ }
1657
+ } catch let error {
1658
+ print("Error creating JSON data: \(error)")
1659
+ hideLoadingIndicator()
1660
+ return
1661
+ }
1662
+
1663
+ let session = URLSession.shared
1664
+ let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
1665
+
1666
+ DispatchQueue.main.async {
1667
+ self.hideLoadingIndicator()
1668
+ }
1669
+
1670
+ if let error = error {
1671
+ print("Error: \(error.localizedDescription)")
1672
+ return
1673
+ }
1674
+
1675
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1676
+ print("Invalid response")
1677
+ return
1678
+ }
1679
+
1680
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1681
+ if let data = serviceData {
1682
+ do {
1683
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1684
+
1685
+ UserStoreSingleton.shared.isLoggedIn = true
1686
+
1687
+ print("Response Data: \(responseObject)")
1688
+
1689
+ UserStoreSingleton.shared.isLoggedIn = true
1690
+
1691
+ if let responseData = responseObject["data"] as? [String: Any],
1692
+ let data = responseData["data"] as? [String: Any],
1693
+ let customerId = data["customer_id"] as? String,
1694
+ let customerToken = data["customer_token"] as? String {
1695
+
1696
+ // Save customer ID and token
1697
+ UserStoreSingleton.shared.customerId = customerId
1698
+ UserStoreSingleton.shared.customerToken = customerToken
1699
+
1700
+ // Update UI-related or main thread operations
1701
+ DispatchQueue.main.async {
1702
+ UserStoreSingleton.shared.verificationEmail = self.txtFieldEmail.text
1703
+
1704
+ print("Customer ID successfully saved: \(UserStoreSingleton.shared.customerId ?? "None")")
1705
+ print("Customer Token successfully saved: \(UserStoreSingleton.shared.customerToken ?? "None")")
1706
+ print("Customer email successfully saved: \(UserStoreSingleton.shared.verificationEmail ?? "None")")
1707
+
1708
+ self.btnSettings.isHidden = false
1709
+
1710
+ // Check if customerId is nil or empty and update UI
1711
+ if self.selectedPaymentMethod == "Card" {
1712
+ if customerId.isEmpty {
1713
+ self.OTPView.isHidden = true
1714
+ self.viewCardFields.isHidden = false
1715
+ self.viewSingleSavedCard.isHidden = true
1716
+ self.viewBtnShowSavedCards.isHidden = true
1717
+ self.viewBtnShowSavedCardHeight.constant = 0
1718
+ self.viewBtnShowSavedCardTopCon.constant = 0
1719
+ self.viewChangeCard.isHidden = true
1720
+ self.viewUpdateCard.isHidden = true
1721
+ self.viewAddNewCard.isHidden = true
1722
+ }
1723
+ else {
1724
+ self.getShowCardsApi()
1725
+ self.OTPView.isHidden = true
1726
+ self.viewCardFields.isHidden = true
1727
+ self.viewSingleSavedCard.isHidden = false
1728
+ self.viewBtnShowSavedCards.isHidden = true
1729
+ self.viewBtnShowSavedCardHeight.constant = 0
1730
+ self.viewBtnShowSavedCardTopCon.constant = 0
1731
+ self.viewChangeCard.isHidden = true
1732
+ self.viewUpdateCard.isHidden = true
1733
+ self.viewAddNewCard.isHidden = true
1734
+
1735
+ self.viewTxtFieldCVVSingleCard.isHidden = true
1736
+ self.btnPayNowSingleCard.isHidden = true
1737
+ self.viewSingleSavedCardHeight.constant = 60
1738
+ self.btnNext.isHidden = true
1739
+ self.btnNextHeight.constant = 0
1740
+ self.btnNextTopCon.constant = 8
1741
+ }
1742
+ }
1743
+ else if self.selectedPaymentMethod == "Bank" {
1744
+ self.getShowBankAccountsApi()
1745
+ }
1746
+ }
1747
+ }
1748
+ } else {
1749
+ print("Invalid JSON format")
1750
+ }
1751
+ } catch let jsonError {
1752
+ print("Error parsing JSON: \(jsonError)")
1753
+ }
1754
+ } else {
1755
+ print("No data received")
1756
+ }
1757
+ } else {
1758
+ print("HTTP Status Code: \(httpResponse.statusCode)")
1759
+ }
1760
+ }
1761
+ task.resume()
1762
+ }
1763
+
1764
+ //MARK: - GET Show Cards API
1765
+ func getShowCardsApi() {
1766
+ showLoadingIndicator()
1767
+
1768
+ // var components = URLComponents()
1769
+ // components.scheme = "https"
1770
+ // components.host = "stage-api.stage-easymerchant.io"
1771
+ // components.path = "/api/v1/card"
1772
+ //
1773
+ // guard let serviceURL = components.url else {
1774
+ // print("Invalid URL")
1775
+ // hideLoadingIndicator()
1776
+ // return
1777
+ // }
1778
+
1779
+ // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
1780
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.getCards.path()
1781
+
1782
+ guard let serviceURL = URL(string: fullURL) else {
1783
+ print("Invalid URL")
1784
+ hideLoadingIndicator()
1785
+ return
1786
+ }
1787
+
1788
+ var request = URLRequest(url: serviceURL)
1789
+ request.httpMethod = "GET"
1790
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
1791
+
1792
+ let token = UserStoreSingleton.shared.customerToken
1793
+ print("Setting customerToken header: \(token ?? "None")")
1794
+ request.addValue(token ?? "", forHTTPHeaderField: "Customer-Token")
1795
+
1796
+ let session = URLSession.shared
1797
+ let task = session.dataTask(with: request) { [weak self] (data, response, error) in
1798
+ guard let self = self else { return }
1799
+
1800
+ DispatchQueue.main.async {
1801
+ self.hideLoadingIndicator() // Stop loader when response is received
1802
+ }
1803
+
1804
+ if let error = error {
1805
+ print("Error: \(error.localizedDescription)")
1806
+ return
1807
+ }
1808
+
1809
+ guard let data = data else { return }
1810
+
1811
+ do {
1812
+ if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1813
+ let cards = jsonResponse["Cards"] as? [[String: Any]] {
1814
+
1815
+ // Map JSON response to CardModel array
1816
+ self.savedCards = cards.compactMap { cardDict in
1817
+ return CardModel(
1818
+ cardBrandName: cardDict["card_brand_name"] as? String ?? "",
1819
+ cardId: cardDict["card_id"] as? String ?? "",
1820
+ cardType: cardDict["card_type"] as? String ?? "",
1821
+ ccLast4: cardDict["cc_last_4"] as? String ?? "",
1822
+ ccValidThru: cardDict["cc_valid_thru"] as? String ?? "",
1823
+ customerId: cardDict["customer_id"] as? String ?? "",
1824
+ isDefault: (cardDict["is_default"] as? Int ?? 0) == 1
1825
+ )
1826
+ }
1827
+
1828
+ DispatchQueue.main.async {
1829
+ if let firstCard = self.savedCards.first {
1830
+ self.lblCardNumberSigleSavedCard.text = "****\(firstCard.ccLast4)"
1831
+ self.lblExpireDateSingelSavedCard.text = "Expiry: \(firstCard.ccValidThru)"
1832
+ }
1833
+
1834
+ self.tblViewSavedCardsList.reloadData()
1835
+ }
1836
+ }
1837
+ } catch {
1838
+ print("Failed to parse response: \(error.localizedDescription)")
1839
+ }
1840
+ }
1841
+ task.resume()
1842
+ }
1843
+
1844
+ // MARK: - Credit Card Charge Api If Billing info is nil and Without Login.
1845
+ func paymentIntentApi() {
1846
+ showLoadingIndicator()
1847
+
1848
+ // var components = URLComponents()
1849
+ // components.scheme = "https"
1850
+ // components.host = "stage-api.stage-easymerchant.io"
1851
+ // components.path = "/api/v1/charges"
1852
+ //
1853
+ // guard let serviceURL = components.url else {
1854
+ // print("Invalid URL")
1855
+ // hideLoadingIndicator()
1856
+ // return
1857
+ // }
1858
+
1859
+ // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
1860
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.creditCharges.path()
1861
+
1862
+ guard let serviceURL = URL(string: fullURL) else {
1863
+ print("Invalid URL")
1864
+ hideLoadingIndicator()
1865
+ return
1866
+ }
1867
+
1868
+ var request = URLRequest(url: serviceURL)
1869
+ request.httpMethod = "POST"
1870
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
1871
+
1872
+ let token = UserStoreSingleton.shared.clientToken
1873
+ print("Setting clientToken header: \(token ?? "None")")
1874
+ request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1875
+
1876
+ let params: [String: Any] = [
1877
+ "name": cardNameTextField.text,
1878
+ "email": "test@gmail.com",
1879
+ "card_number": cardNumberTextField.text.replacingOccurrences(of: " ", with: ""),
1880
+ "cardholder_name": cardNameTextField.text,
1881
+ "exp_month": cardExpiryTextField.text.components(separatedBy: "/").first ?? "",
1882
+ "exp_year": cardExpiryTextField.text.components(separatedBy: "/").last ?? "",
1883
+ "cvc": cardCvvTextField.text,
1884
+ "description": "TestDescription",
1885
+ "currency": "usd",
1886
+ "payment_method": "card"
1887
+ ]
1888
+
1889
+ do {
1890
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1891
+ request.httpBody = jsonData
1892
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
1893
+ print("JSON Payload: \(jsonString)")
1894
+ }
1895
+ } catch let error {
1896
+ print("Error creating JSON data: \(error)")
1897
+ hideLoadingIndicator()
1898
+ return
1899
+ }
1900
+
1901
+ let session = URLSession.shared
1902
+ let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
1903
+
1904
+ DispatchQueue.main.async {
1905
+ self.hideLoadingIndicator() // Stop loader when response is received
1906
+ }
1907
+
1908
+ if let error = error {
1909
+ print("Error: \(error.localizedDescription)")
1910
+ return
1911
+ }
1912
+
1913
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1914
+ print("Invalid response")
1915
+ return
1916
+ }
1917
+
1918
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1919
+ if let data = serviceData {
1920
+ do {
1921
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1922
+ print("Response Data: \(responseObject)")
1923
+
1924
+ // Check if status is 0 and handle the error
1925
+ if let status = responseObject["status"] as? Int, status == 0 {
1926
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1927
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
1928
+ } else {
1929
+ DispatchQueue.main.async {
1930
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
1931
+ paymentDoneVC.chargeData = responseObject
1932
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
1933
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1934
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1935
+ }
1936
+ }
1937
+ }
1938
+ } else {
1939
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1940
+ }
1941
+ } catch let jsonError {
1942
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1943
+ }
1944
+ } else {
1945
+ self.presentPaymentErrorVC(errorMessage: "No data received")
1946
+ }
1947
+ } else {
1948
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1949
+ }
1950
+ }
1951
+ task.resume()
1952
+ }
1953
+
1954
+ // MARK: - Credit Card Charge Api from Saved cards If Billing info is nil and if Logged in.
1955
+ func paymentIntentFromShowSavedCardApi() {
1956
+ showLoadingIndicator()
1957
+
1958
+ // var components = URLComponents()
1959
+ // components.scheme = "https"
1960
+ // components.host = "stage-api.stage-easymerchant.io"
1961
+ // components.path = "/api/v1/charges"
1962
+ //
1963
+ // guard let serviceURL = components.url else {
1964
+ // print("Invalid URL")
1965
+ // hideLoadingIndicator()
1966
+ // return
1967
+ // }
1968
+
1969
+ // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
1970
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.creditCharges.path()
1971
+
1972
+ guard let serviceURL = URL(string: fullURL) else {
1973
+ print("Invalid URL")
1974
+ hideLoadingIndicator()
1975
+ return
1976
+ }
1977
+
1978
+ var request = URLRequest(url: serviceURL)
1979
+ request.httpMethod = "POST"
1980
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
1981
+
1982
+ let token = UserStoreSingleton.shared.clientToken
1983
+ print("Setting clientToken header: \(token ?? "None")")
1984
+ request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1985
+
1986
+ // Prepare parameters for the API request
1987
+ let params: [String: Any] = [
1988
+ "name": UserStoreSingleton.shared.verificationEmail?.split(separator: "@").first ?? "",
1989
+ "email": UserStoreSingleton.shared.verificationEmail ?? "",
1990
+ "description": "TestDescription",
1991
+ "currency": "usd",
1992
+ "payment_method": "card",
1993
+ "save_card": 0,
1994
+ "customer": selectedCard?.customerId ?? "", // Use the selectedCard's customerId
1995
+ "card_id": selectedCard?.cardId ?? "", // Use the selectedCard's cardId
1996
+ "cvc": txtFieldCVVSingleSavedCard.text ?? "",
1997
+ "customer_id": selectedCard?.customerId ?? ""
1998
+ ]
1999
+
2000
+ print(params)
2001
+
2002
+ do {
2003
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2004
+ request.httpBody = jsonData
2005
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
2006
+ print("JSON Payload: \(jsonString)")
2007
+ }
2008
+ } catch let error {
2009
+ print("Error creating JSON data: \(error)")
2010
+ hideLoadingIndicator()
2011
+ return
2012
+ }
2013
+
2014
+ let session = URLSession.shared
2015
+ let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
2016
+
2017
+ DispatchQueue.main.async {
2018
+ self.hideLoadingIndicator() // Stop loader when response is received
2019
+ }
2020
+
2021
+ if let error = error {
2022
+ print("Error: \(error.localizedDescription)")
2023
+ return
2024
+ }
2025
+
2026
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2027
+ print("Invalid response")
2028
+ return
2029
+ }
2030
+
2031
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2032
+ if let data = serviceData {
2033
+ do {
2034
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2035
+ print("Response Data: \(responseObject)")
2036
+
2037
+ // Check if status is 0 and handle the error
2038
+ if let status = responseObject["status"] as? Int, status == 0 {
2039
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
2040
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
2041
+ } else {
2042
+ DispatchQueue.main.async {
2043
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
2044
+ paymentDoneVC.chargeData = responseObject
2045
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
2046
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
2047
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2048
+ }
2049
+ }
2050
+ }
2051
+ } else {
2052
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2053
+ }
2054
+ } catch let jsonError {
2055
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2056
+ }
2057
+ } else {
2058
+ self.presentPaymentErrorVC(errorMessage: "No data received")
2059
+ }
2060
+ } else {
2061
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2062
+ }
2063
+ }
2064
+ task.resume()
2065
+ }
2066
+
2067
+ //MARK: - Credit Card Charge Api from Add new cards If Billing info is nil and Logged in.
2068
+ func paymentIntentFromAddNewCardApi(customerId: String?) {
2069
+ showLoadingIndicator()
2070
+
2071
+ // var components = URLComponents()
2072
+ // components.scheme = "https"
2073
+ // components.host = "stage-api.stage-easymerchant.io"
2074
+ // components.path = "/api/v1/charges"
2075
+ //
2076
+ // guard let serviceURL = components.url else {
2077
+ // print("Invalid URL")
2078
+ // hideLoadingIndicator()
2079
+ // return
2080
+ // }
2081
+
2082
+ // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
2083
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.creditCharges.path()
2084
+
2085
+ guard let serviceURL = URL(string: fullURL) else {
2086
+ print("Invalid URL")
2087
+ hideLoadingIndicator()
2088
+ return
2089
+ }
2090
+
2091
+ var request = URLRequest(url: serviceURL)
2092
+ request.httpMethod = "POST"
2093
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2094
+
2095
+ let token = UserStoreSingleton.shared.clientToken
2096
+ print("Setting clientToken header: \(token ?? "None")")
2097
+ request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2098
+
2099
+ // Get the text fields from the selected cell and trim whitespace
2100
+ let nameText = txtFieldNameOnCardNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2101
+ let cvvText = txtFieldCVVNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2102
+ let cardNumberText = (txtFieldCardNumberNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "").replacingOccurrences(of: " ", with: "")
2103
+ let expiryText = txtFieldExpiryDateNewCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2104
+
2105
+
2106
+ // Check if any field is empty
2107
+ if cardNumberText.isEmpty || expiryText.isEmpty || cvvText.isEmpty || nameText.isEmpty {
2108
+ let alert = UIAlertController(title: "Missing Information", message: "Please fill in all card details.", preferredStyle: .alert)
2109
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
2110
+ self.present(alert, animated: true, completion: nil)
2111
+ return
2112
+ }
2113
+
2114
+ // Validate expiry date format
2115
+ let expiryDateFormat = "MM/yyyy"
2116
+ let dateFormatter = DateFormatter()
2117
+ dateFormatter.dateFormat = expiryDateFormat
2118
+ dateFormatter.locale = Locale(identifier: "en_US_POSIX")
2119
+
2120
+ // Split the expiry date and validate its format
2121
+ let exp = expiryText.split(separator: "/")
2122
+ if exp.count != 2 || exp[0].count != 2 || exp[1].count != 4 {
2123
+ let alert = UIAlertController(title: "Invalid Expiry Date", message: "Please enter the expiry date in the format MM/yyyy.", preferredStyle: .alert)
2124
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
2125
+ self.present(alert, animated: true, completion: nil)
2126
+ return
2127
+ }
2128
+
2129
+ // Check if the expiry date is valid
2130
+ if let expiryDateObj = dateFormatter.date(from: expiryText) {
2131
+ // Check if the expiry date is in the past
2132
+ let currentDate = Date()
2133
+ let calendar = Calendar.current
2134
+ let currentMonthYear = calendar.date(from: calendar.dateComponents([.year, .month], from: currentDate))
2135
+ if expiryDateObj < currentMonthYear! {
2136
+ let alert = UIAlertController(title: "Expired Card", message: "The expiry date cannot be in the past. Please enter a valid expiry date.", preferredStyle: .alert)
2137
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
2138
+ self.present(alert, animated: true, completion: nil)
2139
+ return
2140
+ }
2141
+ } else {
2142
+ let alert = UIAlertController(title: "Invalid Expiry Date", message: "Please enter the expiry date in the format MM/yyyy.", preferredStyle: .alert)
2143
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
2144
+ self.present(alert, animated: true, completion: nil)
2145
+ return
2146
+ }
2147
+
2148
+
2149
+ let emailPrefix = UserStoreSingleton.shared.verificationEmail?.components(separatedBy: "@").first ?? ""
2150
+
2151
+ var params: [String: Any] = [
2152
+ "name": nameText,
2153
+ "card_number": cardNumberText,
2154
+ "cardholder_name": nameText,
2155
+ "exp_month": expiryText.components(separatedBy: "/").first ?? "",
2156
+ "exp_year": expiryText.components(separatedBy: "/").last ?? "",
2157
+ "cvc": cvvText,
2158
+ "description": "TestDescription",
2159
+ "currency": "usd",
2160
+ "payment_method": "card",
2161
+ "save_card": isSavedNewCardForFuture ? 1 : 0,
2162
+ "email" : UserStoreSingleton.shared.verificationEmail ?? ""
2163
+ ]
2164
+
2165
+ // Add is_default parameter if save_card is 1
2166
+ if isSavedNewCardForFuture {
2167
+ params["is_default"] = "1"
2168
+ }
2169
+
2170
+ if let customerId = customerId {
2171
+ params["customer"] = customerId
2172
+ params["customer_id"] = customerId
2173
+ } else {
2174
+ params["username"] = emailPrefix
2175
+ }
2176
+
2177
+ print(params)
2178
+
2179
+ do {
2180
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2181
+ request.httpBody = jsonData
2182
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
2183
+ print("JSON Payload: \(jsonString)")
2184
+ }
2185
+ } catch let error {
2186
+ print("Error creating JSON data: \(error)")
2187
+ hideLoadingIndicator()
2188
+ return
2189
+ }
2190
+
2191
+ let session = URLSession.shared
2192
+ let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
2193
+
2194
+ DispatchQueue.main.async {
2195
+ self.hideLoadingIndicator() // Stop loader when response is received
2196
+ }
2197
+
2198
+ if let error = error {
2199
+ self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
2200
+ return
2201
+ }
2202
+
2203
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2204
+ self.presentPaymentErrorVC(errorMessage: "Invalid response")
2205
+ return
2206
+ }
2207
+
2208
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2209
+ if let data = serviceData {
2210
+ do {
2211
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2212
+ print("Response Data: \(responseObject)")
2213
+
2214
+ // Check if status is 0 and handle the error
2215
+ if let status = responseObject["status"] as? Int, status == 0 {
2216
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
2217
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
2218
+ } else {
2219
+ DispatchQueue.main.async {
2220
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
2221
+ paymentDoneVC.chargeData = responseObject
2222
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
2223
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
2224
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2225
+ }
2226
+ }
2227
+ }
2228
+ } else {
2229
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2230
+ }
2231
+ } catch let jsonError {
2232
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2233
+ }
2234
+ } else {
2235
+ self.presentPaymentErrorVC(errorMessage: "No data received")
2236
+ }
2237
+ } else {
2238
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2239
+ }
2240
+ }
2241
+ task.resume()
2242
+ }
2243
+
2244
+ //MARK: - Update Cards Api.
2245
+ func updateCardApi(cardID: String) {
2246
+ showLoadingIndicator()
2247
+
2248
+ // var components = URLComponents()
2249
+ // components.scheme = "https"
2250
+ // components.host = "stage-api.stage-easymerchant.io"
2251
+ // components.path = "/api/v1/card/\(cardID)" // Append the card_id to the path
2252
+ //
2253
+ // guard let serviceURL = components.url else {
2254
+ // print("Invalid URL")
2255
+ // hideLoadingIndicator()
2256
+ // return
2257
+ // }
2258
+
2259
+ // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
2260
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.getCards.path()+"/\(cardID)"
2261
+
2262
+ guard let serviceURL = URL(string: fullURL) else {
2263
+ print("Invalid URL")
2264
+ hideLoadingIndicator()
2265
+ return
2266
+ }
2267
+
2268
+ var request = URLRequest(url: serviceURL)
2269
+ request.httpMethod = "PUT"
2270
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2271
+
2272
+ let token = UserStoreSingleton.shared.customerToken
2273
+ print("Setting customerToken header: \(token ?? "None")")
2274
+ request.addValue(token ?? "", forHTTPHeaderField: "Customer-Token")
2275
+
2276
+ // Get the text fields from the selected cell and trim whitespace
2277
+ let nameText = txtFieldNameOnCardUpdateCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2278
+ let cvvText = txtFieldCVVUpdateCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2279
+ let expiryText = txtFieldExpireDateUpdateCardView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2280
+
2281
+ // Check if any field is empty
2282
+ if expiryText.isEmpty || cvvText.isEmpty || nameText.isEmpty {
2283
+ let alert = UIAlertController(title: "Missing Information", message: "Please fill in all card details.", preferredStyle: .alert)
2284
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
2285
+ self.present(alert, animated: true, completion: nil)
2286
+ return
2287
+ }
2288
+
2289
+ // Validate expiry date format
2290
+ let expiryDateFormat = "MM/yyyy"
2291
+ let dateFormatter = DateFormatter()
2292
+ dateFormatter.dateFormat = expiryDateFormat
2293
+ dateFormatter.locale = Locale(identifier: "en_US_POSIX")
2294
+
2295
+ // Split the expiry date and validate its format
2296
+ let exp = expiryText.split(separator: "/")
2297
+ if exp.count != 2 || exp[0].count != 2 || exp[1].count != 4 {
2298
+ let alert = UIAlertController(title: "Invalid Expiry Date", message: "Please enter the expiry date in the format MM/yyyy.", preferredStyle: .alert)
2299
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
2300
+ self.present(alert, animated: true, completion: nil)
2301
+ return
2302
+ }
2303
+
2304
+ // Check if the expiry date is valid
2305
+ if let expiryDateObj = dateFormatter.date(from: expiryText) {
2306
+ // Check if the expiry date is in the past
2307
+ let currentDate = Date()
2308
+ let calendar = Calendar.current
2309
+ let currentMonthYear = calendar.date(from: calendar.dateComponents([.year, .month], from: currentDate))
2310
+ if expiryDateObj < currentMonthYear! {
2311
+ let alert = UIAlertController(title: "Expired Card", message: "The expiry date cannot be in the past. Please enter a valid expiry date.", preferredStyle: .alert)
2312
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
2313
+ self.present(alert, animated: true, completion: nil)
2314
+ return
2315
+ }
2316
+ } else {
2317
+ let alert = UIAlertController(title: "Invalid Expiry Date", message: "Please enter the expiry date in the format MM/yyyy.", preferredStyle: .alert)
2318
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
2319
+ self.present(alert, animated: true, completion: nil)
2320
+ return
2321
+ }
2322
+
2323
+ // Set up parameters with selected card details
2324
+ let params: [String: Any] = [
2325
+ "card_id": cardID,
2326
+ "cardholder_name": nameText,
2327
+ "cvc": cvvText,
2328
+ "exp_month": expiryText.components(separatedBy: "/").first ?? "",
2329
+ "exp_year": expiryText.components(separatedBy: "/").last ?? "",
2330
+ ]
2331
+
2332
+ print(params)
2333
+
2334
+ do {
2335
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2336
+ request.httpBody = jsonData
2337
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
2338
+ print("JSON Payload: \(jsonString)")
2339
+ }
2340
+ } catch let error {
2341
+ print("Error creating JSON data: \(error)")
2342
+ hideLoadingIndicator()
2343
+ return
2344
+ }
2345
+
2346
+ let session = URLSession.shared
2347
+ let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
2348
+
2349
+ DispatchQueue.main.async {
2350
+ self.hideLoadingIndicator() // Stop loader when response is received
2351
+ }
2352
+
2353
+ if let error = error {
2354
+ self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
2355
+ return
2356
+ }
2357
+
2358
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2359
+ self.presentPaymentErrorVC(errorMessage: "Invalid response")
2360
+ return
2361
+ }
2362
+
2363
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2364
+ if let data = serviceData {
2365
+ do {
2366
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2367
+ print("Response Data: \(responseObject)")
2368
+ DispatchQueue.main.async {
2369
+
2370
+ self.viewUpdateCard.isHidden = true
2371
+ self.viewChangeCard.isHidden = false
2372
+ self.OTPView.isHidden = true
2373
+ self.viewCardFields.isHidden = true
2374
+ self.viewSingleSavedCard.isHidden = true
2375
+ self.viewBtnShowSavedCards.isHidden = true
2376
+ self.viewBtnShowSavedCardHeight.constant = 0
2377
+ self.viewBtnShowSavedCardTopCon.constant = 0
2378
+ self.viewUpdateCard.isHidden = true
2379
+ self.viewAddNewCard.isHidden = true
2380
+ self.btnNext.isHidden = true
2381
+ self.btnNextHeight.constant = 0
2382
+ self.btnNextTopCon.constant = 8
2383
+
2384
+ self.getShowCardsApi()
2385
+
2386
+ self.txtFieldNameOnCardUpdateCardView.text = ""
2387
+ self.txtFieldExpireDateUpdateCardView.text = ""
2388
+ self.txtFieldCVVUpdateCardView.text = ""
2389
+ }
2390
+ } else {
2391
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2392
+ }
2393
+ } catch let jsonError {
2394
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2395
+ }
2396
+ } else {
2397
+ self.presentPaymentErrorVC(errorMessage: "No data received")
2398
+ }
2399
+ } else {
2400
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2401
+ }
2402
+ }
2403
+ task.resume()
2404
+ }
2405
+
2406
+ //MARK: - Delete Cards Api.
2407
+ func deleteCardApi(cardID: String, at index: Int) {
2408
+ showLoadingIndicator()
2409
+
2410
+ // var components = URLComponents()
2411
+ // components.scheme = "https"
2412
+ // components.host = "stage-api.stage-easymerchant.io"
2413
+ // components.path = "/api/v1/card/\(cardID)" // Append the card_id to the path
2414
+ //
2415
+ // guard let serviceURL = components.url else {
2416
+ // print("Invalid URL")
2417
+ // hideLoadingIndicator()
2418
+ // return
2419
+ // }
2420
+
2421
+ // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
2422
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.getCards.path()+"/\(cardID)"
2423
+
2424
+ guard let serviceURL = URL(string: fullURL) else {
2425
+ print("Invalid URL")
2426
+ hideLoadingIndicator()
2427
+ return
2428
+ }
2429
+
2430
+ var request = URLRequest(url: serviceURL)
2431
+ request.httpMethod = "DELETE"
2432
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2433
+
2434
+ let token = UserStoreSingleton.shared.customerToken
2435
+ print("Setting customerToken header: \(token ?? "None")")
2436
+ request.addValue(token ?? "", forHTTPHeaderField: "Customer-Token")
2437
+
2438
+ let session = URLSession.shared
2439
+ let task = session.dataTask(with: request) { [weak self] (serviceData, serviceResponse, error) in
2440
+ guard let self = self else { return }
2441
+
2442
+ DispatchQueue.main.async {
2443
+ self.hideLoadingIndicator() // Stop loader when response is received
2444
+ }
2445
+
2446
+ if let error = error {
2447
+ DispatchQueue.main.async {
2448
+ self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
2449
+ }
2450
+ return
2451
+ }
2452
+
2453
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2454
+ DispatchQueue.main.async {
2455
+ self.presentPaymentErrorVC(errorMessage: "Invalid response")
2456
+ }
2457
+ return
2458
+ }
2459
+
2460
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2461
+
2462
+ if let data = serviceData {
2463
+ do {
2464
+ // Try to parse the data as JSON
2465
+ if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
2466
+ let message = jsonResponse["message"] as? String {
2467
+ DispatchQueue.main.async {
2468
+ self.showToast(message: message)
2469
+ }
2470
+ }
2471
+ } catch let jsonError {
2472
+ print("Error parsing JSON response: \(jsonError.localizedDescription)")
2473
+ }
2474
+ } else {
2475
+ print("No data received.")
2476
+ }
2477
+ } else {
2478
+ DispatchQueue.main.async {
2479
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2480
+ }
2481
+ }
2482
+ }
2483
+ task.resume()
2484
+ }
2485
+
2486
+ func presentPaymentErrorVC(errorMessage: String) {
2487
+ DispatchQueue.main.async {
2488
+ if let paymentErrorVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentErrorVC") as? PaymentErrorVC {
2489
+ paymentErrorVC.errorMessage = errorMessage
2490
+ paymentErrorVC.easyPayDelegate = self.easyPayDelegate // Pass the reference here
2491
+ self.navigationController?.pushViewController(paymentErrorVC, animated: true)
2492
+ }
2493
+ }
2494
+ }
2495
+
2496
+ //MARK: - Bank Api's
2497
+
2498
+ //MARK: - GET Show Bank Accounts API
2499
+ func getShowBankAccountsApi() {
2500
+ showLoadingIndicator()
2501
+
2502
+ // var components = URLComponents()
2503
+ // components.scheme = "https"
2504
+ // components.host = "stage-api.stage-easymerchant.io"
2505
+ // components.path = "/api/v1/ach/account"
2506
+ //
2507
+ // guard let serviceURL = components.url else {
2508
+ // print("Invalid URL")
2509
+ // hideLoadingIndicator()
2510
+ // return
2511
+ // }
2512
+
2513
+ // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
2514
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.account.path()
2515
+
2516
+ guard let serviceURL = URL(string: fullURL) else {
2517
+ print("Invalid URL")
2518
+ hideLoadingIndicator()
2519
+ return
2520
+ }
2521
+
2522
+ var request = URLRequest(url: serviceURL)
2523
+ request.httpMethod = "GET"
2524
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2525
+
2526
+ let token = UserStoreSingleton.shared.customerToken
2527
+ print("Setting customerToken header: \(token ?? "None")")
2528
+ request.addValue(token ?? "", forHTTPHeaderField: "Customer-Token")
2529
+
2530
+ let session = URLSession.shared
2531
+ let task = session.dataTask(with: request) { [weak self] (data, response, error) in
2532
+ guard let self = self else { return }
2533
+
2534
+ DispatchQueue.main.async {
2535
+ self.hideLoadingIndicator() // Stop loader when response is received
2536
+ }
2537
+
2538
+ if let error = error {
2539
+ print("Error: \(error.localizedDescription)")
2540
+ return
2541
+ }
2542
+
2543
+ guard let data = data else { return }
2544
+
2545
+ do {
2546
+ let decoder = JSONDecoder()
2547
+ let bankAccountModel = try decoder.decode(BankAccountModel.self, from: data)
2548
+
2549
+ if bankAccountModel.status == true {
2550
+ // Update bankAccounts array on the main thread
2551
+ DispatchQueue.main.async {
2552
+ self.bankAccounts = bankAccountModel.accounts ?? []
2553
+
2554
+ if let firstAccount = self.bankAccounts.first {
2555
+ // Set the account number and account type in the labels
2556
+ self.selectedbankAccounts = firstAccount
2557
+
2558
+ self.lblAccountNumberSingleAccountView.text = "****\(firstAccount.account_number_last_4 ?? "")"
2559
+ self.lblAccountTypeSingleAccountView.text = "Type: \(firstAccount.account_type ?? "")"
2560
+ }
2561
+
2562
+ self.tblViewSavedBankAccounts.reloadData()
2563
+
2564
+ self.viewSingleSavedAccount.isHidden = false
2565
+ self.viewBankFields.isHidden = true
2566
+ self.viewBtnShowSavedCards.isHidden = true
2567
+ self.viewBtnShowSavedCardHeight.constant = 0
2568
+ self.viewBtnShowSavedCardTopCon.constant = 0
2569
+ self.OTPView.isHidden = true
2570
+ self.emailView.isHidden = true
2571
+ self.viewCardFields.isHidden = true
2572
+ self.viewSingleSavedCard.isHidden = true
2573
+ self.viewChangeCard.isHidden = true
2574
+ self.viewUpdateCard.isHidden = true
2575
+ self.viewAddNewCard.isHidden = true
2576
+ self.btnNext.isHidden = true
2577
+ self.btnNextHeight.constant = 0
2578
+ self.btnNextTopCon.constant = 8
2579
+
2580
+ self.viewNewBankAccount.isHidden = true
2581
+ self.viewCrypto.isHidden = true
2582
+
2583
+ self.viewTermsAndConditions.isHidden = true
2584
+
2585
+ self.viewChangedAccount.isHidden = true
2586
+
2587
+ if self.isSelectForPayBank {
2588
+ self.viewTermAndConditionsSingleAccountView.isHidden = false
2589
+ self.btnPayNowSingleAccountView.isHidden = false
2590
+ self.viewSingleAccountViewHeight.constant = 170
2591
+ } else {
2592
+ self.viewTermAndConditionsSingleAccountView.isHidden = true
2593
+ self.btnPayNowSingleAccountView.isHidden = true
2594
+ self.viewSingleAccountViewHeight.constant = 68
2595
+ self.btnNext.isHidden = true
2596
+ self.btnNextHeight.constant = 0
2597
+ self.btnNextTopCon.constant = 0
2598
+ }
2599
+ }
2600
+ } else {
2601
+ // Handle the error or show a message
2602
+ print("Error: \(bankAccountModel.message ?? "Unknown error")")
2603
+ }
2604
+ } catch {
2605
+ print("Failed to parse response: \(error.localizedDescription)")
2606
+ }
2607
+ }
2608
+
2609
+ task.resume()
2610
+ }
2611
+
2612
+ //MARK: - Banking Account Charge Api If Billing info is nil and without Login
2613
+ func accountChargeApi() {
2614
+ showLoadingIndicator()
2615
+
2616
+ // var components = URLComponents()
2617
+ // components.scheme = "https"
2618
+ // components.host = "stage-api.stage-easymerchant.io"
2619
+ // components.path = "/api/v1/ach/charge"
2620
+ //
2621
+ // guard let serviceURL = components.url else {
2622
+ // print("Invalid URL")
2623
+ // hideLoadingIndicator()
2624
+ // return
2625
+ // }
2626
+
2627
+ // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
2628
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
2629
+
2630
+ guard let serviceURL = URL(string: fullURL) else {
2631
+ print("Invalid URL")
2632
+ hideLoadingIndicator()
2633
+ return
2634
+ }
2635
+
2636
+ var request = URLRequest(url: serviceURL)
2637
+ request.httpMethod = "POST"
2638
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2639
+
2640
+ let token = UserStoreSingleton.shared.clientToken
2641
+ print("Setting clientToken header: \(token ?? "None")")
2642
+ request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2643
+
2644
+ let params: [String: Any] = [
2645
+ "name": txtFieldAccountName.text ?? "",
2646
+ "email": "test@gmail.com",
2647
+ "description": "TestDescription",
2648
+ "currency": "usd",
2649
+ "account_type": txtFieldAccountType.text?.lowercased() ?? "",
2650
+ "routing_number": txtFieldRoutingNumber.text?.replacingOccurrences(of: " ", with: "") ?? "",
2651
+ "account_number": txtFieldAccountNumber.text?.replacingOccurrences(of: " ", with: "") ?? "",
2652
+ "payment_mode": "auth_and_capture",
2653
+ "payment_intent": UserStoreSingleton.shared.paymentIntent ?? "",
2654
+ "levelIndicator": 1,
2655
+ ]
2656
+
2657
+ do {
2658
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2659
+ request.httpBody = jsonData
2660
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
2661
+ print("JSON Payload: \(jsonString)")
2662
+ }
2663
+ } catch let error {
2664
+ print("Error creating JSON data: \(error)")
2665
+ hideLoadingIndicator()
2666
+ return
2667
+ }
2668
+
2669
+ let session = URLSession.shared
2670
+ let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
2671
+
2672
+ DispatchQueue.main.async {
2673
+ self.hideLoadingIndicator() // Stop loader when response is received
2674
+ }
2675
+
2676
+ if let error = error {
2677
+ print("Error: \(error.localizedDescription)")
2678
+ return
2679
+ }
2680
+
2681
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2682
+ print("Invalid response")
2683
+ return
2684
+ }
2685
+
2686
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2687
+ if let data = serviceData {
2688
+ do {
2689
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2690
+ print("Response Data: \(responseObject)")
2691
+
2692
+ // Check if status is 0 and handle the error
2693
+ if let status = responseObject["status"] as? Int, status == 0 {
2694
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
2695
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
2696
+ } else {
2697
+ DispatchQueue.main.async {
2698
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
2699
+ paymentDoneVC.chargeData = responseObject
2700
+ // Pass the selected payment method
2701
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
2702
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate // Pass the delegate
2703
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2704
+ }
2705
+ }
2706
+ }
2707
+ } else {
2708
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2709
+ }
2710
+ } catch let jsonError {
2711
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2712
+ }
2713
+ } else {
2714
+ self.presentPaymentErrorVC(errorMessage: "No data received")
2715
+ }
2716
+ } else {
2717
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2718
+ }
2719
+ }
2720
+ task.resume()
2721
+ }
2722
+
2723
+ //MARK: - Banking Account Charge Api from Regular saved bank account if Billing info is nil
2724
+ func accountChargeSavedBankAccountApi() {
2725
+ showLoadingIndicator()
2726
+
2727
+ // var components = URLComponents()
2728
+ // components.scheme = "https"
2729
+ // components.host = "stage-api.stage-easymerchant.io"
2730
+ // components.path = "/api/v1/ach/charge"
2731
+ //
2732
+ // guard let serviceURL = components.url else {
2733
+ // print("Invalid URL")
2734
+ // hideLoadingIndicator()
2735
+ // return
2736
+ // }
2737
+
2738
+ // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
2739
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
2740
+
2741
+ guard let serviceURL = URL(string: fullURL) else {
2742
+ print("Invalid URL")
2743
+ hideLoadingIndicator()
2744
+ return
2745
+ }
2746
+
2747
+ var request = URLRequest(url: serviceURL)
2748
+ request.httpMethod = "POST"
2749
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2750
+
2751
+ let token = UserStoreSingleton.shared.clientToken
2752
+ print("Setting clientToken header: \(token ?? "None")")
2753
+ request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2754
+
2755
+ let params: [String: Any] = [
2756
+ "account_id": selectedbankAccounts?.account_id ?? "",
2757
+ "customer": selectedbankAccounts?.customer_id ?? "",
2758
+ "payment_method": "ach",
2759
+ "description": "Test Description",
2760
+ "currency": "usd",
2761
+ ]
2762
+
2763
+ print(params)
2764
+
2765
+ do {
2766
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2767
+ request.httpBody = jsonData
2768
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
2769
+ print("JSON Payload: \(jsonString)")
2770
+ }
2771
+ } catch let error {
2772
+ print("Error creating JSON data: \(error)")
2773
+ hideLoadingIndicator()
2774
+ return
2775
+ }
2776
+
2777
+ let session = URLSession.shared
2778
+ let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
2779
+
2780
+ DispatchQueue.main.async {
2781
+ self.hideLoadingIndicator() // Stop loader when response is received
2782
+ }
2783
+
2784
+ if let error = error {
2785
+ print("Error: \(error.localizedDescription)")
2786
+ return
2787
+ }
2788
+
2789
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2790
+ print("Invalid response")
2791
+ return
2792
+ }
2793
+
2794
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2795
+ if let data = serviceData {
2796
+ do {
2797
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2798
+ print("Response Data: \(responseObject)")
2799
+
2800
+ // Check if status is 0 and handle the error
2801
+ if let status = responseObject["status"] as? Int, status == 0 {
2802
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
2803
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
2804
+ } else {
2805
+ DispatchQueue.main.async {
2806
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
2807
+ paymentDoneVC.chargeData = responseObject
2808
+ // Pass the selected payment method
2809
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
2810
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate // Pass the delegate
2811
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2812
+ }
2813
+ }
2814
+ }
2815
+ } else {
2816
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2817
+ }
2818
+ } catch let jsonError {
2819
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2820
+ }
2821
+ } else {
2822
+ self.presentPaymentErrorVC(errorMessage: "No data received")
2823
+ }
2824
+ } else {
2825
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2826
+ }
2827
+ }
2828
+ task.resume()
2829
+ }
2830
+
2831
+ //MARK: - Banking Account Charge Api from add new account if billing info is nil and user not saved the account
2832
+ func accountChargeAddNewAccountApi() {
2833
+ showLoadingIndicator()
2834
+
2835
+ // var components = URLComponents()
2836
+ // components.scheme = "https"
2837
+ // components.host = "stage-api.stage-easymerchant.io"
2838
+ // components.path = "/api/v1/ach/charge"
2839
+ //
2840
+ // guard let serviceURL = components.url else {
2841
+ // print("Invalid URL")
2842
+ // hideLoadingIndicator()
2843
+ // return
2844
+ // }
2845
+
2846
+ // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
2847
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
2848
+
2849
+ guard let serviceURL = URL(string: fullURL) else {
2850
+ print("Invalid URL")
2851
+ hideLoadingIndicator()
2852
+ return
2853
+ }
2854
+
2855
+ var request = URLRequest(url: serviceURL)
2856
+ request.httpMethod = "POST"
2857
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2858
+
2859
+ let token = UserStoreSingleton.shared.clientToken
2860
+ print("Setting clientToken header: \(token ?? "None")")
2861
+ request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2862
+
2863
+ let accountName = txtFieldAccountNameNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2864
+ let routingNumber = txtFieldRoutingNumberNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2865
+ let accountType = txtFieldAccountTypeNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2866
+ let accountNumber = txtFieldAccountNumberNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2867
+
2868
+ let params: [String: Any] = [
2869
+ "name": accountName,
2870
+ "email": UserStoreSingleton.shared.verificationEmail ?? "",
2871
+ "description": "Test Description",
2872
+ "currency": "usd",
2873
+ "account_type": accountType.lowercased(),
2874
+ "routing_number": routingNumber,
2875
+ "account_number": accountNumber,
2876
+ "payment_mode": "auth_and_capture",
2877
+ "payment_intent": UserStoreSingleton.shared.paymentIntent ?? "",
2878
+ "levelIndicator": 1,
2879
+ ]
2880
+
2881
+ print(params)
2882
+
2883
+ do {
2884
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2885
+ request.httpBody = jsonData
2886
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
2887
+ print("JSON Payload: \(jsonString)")
2888
+ }
2889
+ } catch let error {
2890
+ print("Error creating JSON data: \(error)")
2891
+ hideLoadingIndicator()
2892
+ return
2893
+ }
2894
+
2895
+ let session = URLSession.shared
2896
+ let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
2897
+
2898
+ DispatchQueue.main.async {
2899
+ self.hideLoadingIndicator() // Stop loader when response is received
2900
+ }
2901
+
2902
+ if let error = error {
2903
+ print("Error: \(error.localizedDescription)")
2904
+ return
2905
+ }
2906
+
2907
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2908
+ print("Invalid response")
2909
+ return
2910
+ }
2911
+
2912
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2913
+ if let data = serviceData {
2914
+ do {
2915
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2916
+ print("Response Data: \(responseObject)")
2917
+
2918
+ // Check if status is 0 and handle the error
2919
+ if let status = responseObject["status"] as? Int, status == 0 {
2920
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
2921
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
2922
+ } else {
2923
+ DispatchQueue.main.async {
2924
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
2925
+ paymentDoneVC.chargeData = responseObject
2926
+ // Pass the selected payment method
2927
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
2928
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate // Pass the delegate
2929
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2930
+ }
2931
+ }
2932
+ }
2933
+ } else {
2934
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2935
+ }
2936
+ } catch let jsonError {
2937
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2938
+ }
2939
+ } else {
2940
+ self.presentPaymentErrorVC(errorMessage: "No data received")
2941
+ }
2942
+ } else {
2943
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2944
+ }
2945
+ }
2946
+ task.resume()
2947
+ }
2948
+
2949
+ //MARK: - Account Charge Api if user saved the account.
2950
+ func accountChargeApi(customerId: String?) {
2951
+ showLoadingIndicator()
2952
+
2953
+ // var components = URLComponents()
2954
+ // components.scheme = "https"
2955
+ // components.host = "stage-api.stage-easymerchant.io"
2956
+ // components.path = "/api/v1/ach/charge"
2957
+ //
2958
+ // guard let serviceURL = components.url else {
2959
+ // print("Invalid URL")
2960
+ // hideLoadingIndicator()
2961
+ // return
2962
+ // }
2963
+
2964
+ // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
2965
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
2966
+
2967
+ guard let serviceURL = URL(string: fullURL) else {
2968
+ print("Invalid URL")
2969
+ hideLoadingIndicator()
2970
+ return
2971
+ }
2972
+
2973
+ var request = URLRequest(url: serviceURL)
2974
+ request.httpMethod = "POST"
2975
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
2976
+
2977
+ let token = UserStoreSingleton.shared.clientToken
2978
+ print("Setting clientToken header: \(token ?? "None")")
2979
+ request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2980
+
2981
+ // Retrieve and trim text field values (removing leading and trailing whitespace)
2982
+ let accountName = txtFieldAccountNameNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2983
+ let routingNumber = txtFieldRoutingNumberNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2984
+ let accountType = txtFieldAccountTypeNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2985
+ let accountNumber = txtFieldAccountNumberNewAccountView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2986
+
2987
+ let emailPrefix = UserStoreSingleton.shared.verificationEmail?.components(separatedBy: "@").first ?? ""
2988
+
2989
+ var params: [String: Any] = [
2990
+ "name": accountName,
2991
+ "email": UserStoreSingleton.shared.verificationEmail ?? "",
2992
+ "description": "Test Description",
2993
+ "currency": "usd",
2994
+ "account_type": accountType.lowercased(),
2995
+ "routing_number": routingNumber,
2996
+ "account_number": accountNumber,
2997
+ "payment_mode": "auth_and_capture",
2998
+ "payment_intent": UserStoreSingleton.shared.paymentIntent ?? "",
2999
+ "levelIndicator": 1,
3000
+ "save_account": 1,
3001
+ "payment_method": "ach"
3002
+ ]
3003
+
3004
+ if let customerId = customerId {
3005
+ params["customer"] = customerId
3006
+ } else {
3007
+ params["username"] = emailPrefix
3008
+ }
3009
+
3010
+ print(params)
3011
+
3012
+ do {
3013
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
3014
+ request.httpBody = jsonData
3015
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
3016
+ print("JSON Payload: \(jsonString)")
3017
+ }
3018
+ } catch let error {
3019
+ print("Error creating JSON data: \(error)")
3020
+ hideLoadingIndicator()
3021
+ return
3022
+ }
3023
+
3024
+ let session = URLSession.shared
3025
+ let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
3026
+
3027
+ DispatchQueue.main.async {
3028
+ self.hideLoadingIndicator() // Stop loader when response is received
3029
+ }
3030
+
3031
+ if let error = error {
3032
+ self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
3033
+ return
3034
+ }
3035
+
3036
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
3037
+ self.presentPaymentErrorVC(errorMessage: "Invalid response")
3038
+ return
3039
+ }
3040
+
3041
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
3042
+ if let data = serviceData {
3043
+ do {
3044
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
3045
+ print("Response Data: \(responseObject)")
3046
+
3047
+ // Check if status is 0 and handle the error
3048
+ if let status = responseObject["status"] as? Int, status == 0 {
3049
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
3050
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
3051
+ } else {
3052
+ DispatchQueue.main.async {
3053
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
3054
+ paymentDoneVC.chargeData = responseObject
3055
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
3056
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
3057
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
3058
+ }
3059
+ }
3060
+ }
3061
+ } else {
3062
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
3063
+ }
3064
+ } catch let jsonError {
3065
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
3066
+ }
3067
+ } else {
3068
+ self.presentPaymentErrorVC(errorMessage: "No data received")
3069
+ }
3070
+ } else {
3071
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
3072
+ }
3073
+ }
3074
+ task.resume()
3075
+ }
3076
+
3077
+ //MARK: - Delete Bank Account Api.
3078
+ func deleteAccountApi(accountID: String, at index: Int) {
3079
+ showLoadingIndicator()
3080
+
3081
+ // var components = URLComponents()
3082
+ // components.scheme = "https"
3083
+ // components.host = "stage-api.stage-easymerchant.io"
3084
+ // components.path = "/api/v1/ach/account/\(accountID)" // Append the account_id to the path
3085
+ //
3086
+ // guard let serviceURL = components.url else {
3087
+ // print("Invalid URL")
3088
+ // hideLoadingIndicator()
3089
+ // return
3090
+ // }
3091
+
3092
+ // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
3093
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()+"/\(accountID)"
3094
+
3095
+ guard let serviceURL = URL(string: fullURL) else {
3096
+ print("Invalid URL")
3097
+ hideLoadingIndicator()
3098
+ return
3099
+ }
3100
+
3101
+ var request = URLRequest(url: serviceURL)
3102
+ request.httpMethod = "DELETE"
3103
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
3104
+
3105
+ let token = UserStoreSingleton.shared.customerToken
3106
+ print("Setting customerToken header: \(token ?? "None")")
3107
+ request.addValue(token ?? "", forHTTPHeaderField: "Customer-Token")
3108
+
3109
+ let session = URLSession.shared
3110
+ let task = session.dataTask(with: request) { [weak self] (serviceData, serviceResponse, error) in
3111
+ guard let self = self else { return }
3112
+
3113
+ DispatchQueue.main.async {
3114
+ self.hideLoadingIndicator() // Stop loader when response is received
3115
+ }
3116
+
3117
+ if let error = error {
3118
+ DispatchQueue.main.async {
3119
+ self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
3120
+ }
3121
+ return
3122
+ }
3123
+
3124
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
3125
+ DispatchQueue.main.async {
3126
+ self.presentPaymentErrorVC(errorMessage: "Invalid response")
3127
+ }
3128
+ return
3129
+ }
3130
+
3131
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
3132
+
3133
+ if let data = serviceData {
3134
+ do {
3135
+ // Try to parse the data as JSON
3136
+ if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
3137
+ let message = jsonResponse["message"] as? String {
3138
+ DispatchQueue.main.async {
3139
+ self.showToast(message: message)
3140
+ self.getShowBankAccountsApi()
3141
+ }
3142
+ }
3143
+ } catch let jsonError {
3144
+ print("Error parsing JSON response: \(jsonError.localizedDescription)")
3145
+ }
3146
+ } else {
3147
+ print("No data received.")
3148
+ }
3149
+ } else {
3150
+ DispatchQueue.main.async {
3151
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
3152
+ }
3153
+ }
3154
+ }
3155
+ task.resume()
3156
+ }
3157
+
3158
+ // MARK: - Crypto Charge API
3159
+ func cryptoChargeApi() {
3160
+ showLoadingIndicator()
3161
+ // var components = URLComponents()
3162
+ // components.scheme = "https"
3163
+ // components.host = "stage-api.stage-easymerchant.io"
3164
+ // components.path = "/api/v1/charges"
3165
+ //
3166
+ // guard let serviceURL = components.url else {
3167
+ // print("Invalid URL")
3168
+ // hideLoadingIndicator()
3169
+ // return
3170
+ // }
3171
+
3172
+ // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
3173
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
3174
+
3175
+ guard let serviceURL = URL(string: fullURL) else {
3176
+ print("Invalid URL")
3177
+ hideLoadingIndicator()
3178
+ return
3179
+ }
3180
+
3181
+ var request = URLRequest(url: serviceURL)
3182
+ request.httpMethod = "POST"
3183
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
3184
+
3185
+ let token = UserStoreSingleton.shared.clientToken
3186
+ print("Setting clientToken header: \(token ?? "None")")
3187
+ request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
3188
+
3189
+ let params: [String: Any] = [
3190
+ "name": UserStoreSingleton.shared.merchantName ?? "",
3191
+ "email": UserStoreSingleton.shared.merchantEmail ?? "",
3192
+ "description": "Payment checkout",
3193
+ "amount": amount ?? "",
3194
+ "currency": "satoshi",
3195
+ "payment_account": UserStoreSingleton.shared.paymentAccount ?? "",
3196
+ "success_url": "https://stage-api.stage-easymerchant.io/webhook/crypto",
3197
+ "callback_url": "https://stage-api.stage-easymerchant.io/webhook/crypto"
3198
+ ]
3199
+
3200
+ print(params)
3201
+
3202
+ do {
3203
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
3204
+ request.httpBody = jsonData
3205
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
3206
+ print("JSON Payload: \(jsonString)")
3207
+ }
3208
+ } catch let error {
3209
+ print("Error creating JSON data: \(error)")
3210
+ hideLoadingIndicator()
3211
+ return
3212
+ }
3213
+
3214
+ let session = URLSession.shared
3215
+ let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
3216
+
3217
+ DispatchQueue.main.async {
3218
+ self.hideLoadingIndicator() // Stop loader when response is received
3219
+ }
3220
+
3221
+ if let error = error {
3222
+ print("Error: \(error.localizedDescription)")
3223
+ return
3224
+ }
3225
+
3226
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
3227
+ print("Invalid response")
3228
+ return
3229
+ }
3230
+
3231
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
3232
+ if let data = serviceData {
3233
+ do {
3234
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
3235
+ print("Response Data: \(responseObject)")
3236
+
3237
+ // Check if status is 0 and handle the error
3238
+ if let status = responseObject["status"] as? Int, status == 0 {
3239
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
3240
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
3241
+ } else {
3242
+ // Extract charge_id
3243
+ if let chargeId = responseObject["charge_id"] as? String {
3244
+ self.chargeId = chargeId
3245
+ print("Charge ID: \(chargeId)")
3246
+ }
3247
+
3248
+ // Extract chain_invoice address and lightning invoice fields
3249
+ if let data = responseObject["data"] as? [String: Any] {
3250
+ self.chainInvoiceAddress = (data["chain_invoice"] as? [String: Any])?["address"] as? String
3251
+ self.lightningURI = (data["lightning_invoice"] as? [String: Any])?["payreq"] as? String
3252
+
3253
+ // Default to OnChain QR code initially
3254
+ DispatchQueue.main.async {
3255
+ if let address = self.chainInvoiceAddress {
3256
+ self.qrImageView.image = self.generateQRCode(from: address)
3257
+ self.lblBTCAddress.text = "BTC Address: \(address)"
3258
+
3259
+ // Extract the amount from the URI
3260
+ if let uri = data["uri"] as? String {
3261
+ if let amountString = self.extractAmount(from: uri) {
3262
+ // Update the label with the extracted amount
3263
+ DispatchQueue.main.async {
3264
+ self.lblAmmoutCrypto.text = "\(amountString) BTC = $\(self.amount ?? 0) USD"
3265
+ self.lblCryptoAmmountViewTryAgain.text = "\(amountString) BTC = $\(self.amount ?? 0) USD"
3266
+ }
3267
+ }
3268
+ }
3269
+ }
3270
+
3271
+ self.viewCryptoQRCode.isHidden = false
3272
+ self.viewCryptoTryAgain.isHidden = true
3273
+ }
3274
+ } else {
3275
+ self.presentPaymentErrorVC(errorMessage: "Invalid response format")
3276
+ }
3277
+ }
3278
+ } else {
3279
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
3280
+ }
3281
+ } catch let jsonError {
3282
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
3283
+ }
3284
+ } else {
3285
+ self.presentPaymentErrorVC(errorMessage: "No data received")
3286
+ }
3287
+ } else {
3288
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
3289
+ }
3290
+ }
3291
+ task.resume()
3292
+ }
3293
+
3294
+ // Helper function to extract the amount from the URI
3295
+ func extractAmount(from uri: String) -> String? {
3296
+ let regex = try? NSRegularExpression(pattern: "amount=([\\d.]+)", options: [])
3297
+ let matches = regex?.matches(in: uri, options: [], range: NSRange(location: 0, length: uri.count))
3298
+
3299
+ if let match = matches?.first, let amountRange = Range(match.range(at: 1), in: uri) {
3300
+ return String(uri[amountRange])
3301
+ }
3302
+ return nil
3303
+ }
3304
+
3305
+ //MARK: - GET Crypto Payment Info API
3306
+ func getCryptoPaymentInfoApi(chargeId: String) {
3307
+
3308
+ // var components = URLComponents()
3309
+ // components.scheme = "https"
3310
+ // components.host = "stage-api.stage-easymerchant.io"
3311
+ // components.path = "/api/v1/charges/\(chargeId)" // Append chargeId to the path
3312
+ //
3313
+ // guard let serviceURL = components.url else {
3314
+ // print("Invalid URL")
3315
+ // return
3316
+ // }
3317
+
3318
+ // Construct the full URL using baseURL from EnvironmentConfig and path from the endpoint
3319
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()+"/\(chargeId)"
3320
+
3321
+ guard let serviceURL = URL(string: fullURL) else {
3322
+ print("Invalid URL")
3323
+ hideLoadingIndicator()
3324
+ return
3325
+ }
3326
+
3327
+ var request = URLRequest(url: serviceURL)
3328
+ request.httpMethod = "GET"
3329
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
3330
+
3331
+ let token = UserStoreSingleton.shared.clientToken
3332
+ print("Setting clientToken header: \(token ?? "None")")
3333
+ request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
3334
+
3335
+ let session = URLSession.shared
3336
+ let task = session.dataTask(with: request) { [weak self] (data, response, error) in
3337
+ guard let self = self else { return }
3338
+
3339
+ if let error = error {
3340
+ print("Error: \(error.localizedDescription)")
3341
+ return
3342
+ }
3343
+
3344
+ guard let data = data else { return }
3345
+
3346
+ do {
3347
+ if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
3348
+ let responseData = jsonResponse["data"] as? [String: Any] {
3349
+
3350
+ // Extract required data
3351
+ let amount = responseData["amount"] as? String ?? ""
3352
+ let dateCreated = responseData["date_created"] as? String ?? ""
3353
+ let status = responseData["status"] as? String ?? ""
3354
+ let transactionId = responseData["transaction_id"] as? String ?? ""
3355
+
3356
+ print("Amount: \(amount), Date Created: \(dateCreated), Status: \(status), Transaction ID: \(transactionId)")
3357
+
3358
+ // Check if the status is "completed"
3359
+ if status.lowercased() == "completed" {
3360
+ DispatchQueue.main.async {
3361
+ // Navigate to PaymentDoneVC and pass data
3362
+ self.navigateToPaymentDoneVC(amount: amount, dateCreated: dateCreated, transactionId: transactionId)
3363
+ }
3364
+ }
3365
+ }
3366
+ } catch {
3367
+ print("Failed to parse response: \(error.localizedDescription)")
3368
+ }
3369
+ }
3370
+ task.resume()
3371
+ }
3372
+
3373
+ //MARK: - Navigation to PaymentDoneVC
3374
+ func navigateToPaymentDoneVC(amount: String, dateCreated: String, transactionId: String) {
3375
+ if let paymentDoneVC = storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
3376
+ // Set the properties of PaymentDoneVC with the extracted data
3377
+ paymentDoneVC.amount = amount
3378
+ paymentDoneVC.dateCreated = dateCreated
3379
+ paymentDoneVC.transactionId = transactionId
3380
+ paymentDoneVC.isFrom = "Crypto"
3381
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
3382
+ }
3383
+ }
3384
+
3385
+ }
3386
+
3387
+ //MARK: - Collection View.
3388
+ extension PaymentInfoVC: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
3389
+ public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
3390
+ return arrPaymentMethods.count
3391
+ }
3392
+
3393
+ func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
3394
+ let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PaymentInformationCVC", for: indexPath) as! PaymentInformationCVC
3395
+ cell.setUI()
3396
+ cell.imgVwPayments.image = UIImage.init(systemName: arrPaymentMethods[indexPath.row].image)
3397
+ cell.lblPayment.text = arrPaymentMethods[indexPath.row].name
3398
+
3399
+ // Update border color based on selection
3400
+ if selectedIndex == indexPath {
3401
+ cell.vwMain.layer.borderColor = UIColor.systemBlue.cgColor
3402
+ cell.imgVwPayments.tintColor = .systemBlue
3403
+ cell.lblPayment.textColor = .systemBlue
3404
+ } else {
3405
+ cell.vwMain.layer.borderColor = UIColor.gray.cgColor
3406
+ cell.imgVwPayments.tintColor = .systemGray
3407
+ cell.lblPayment.textColor = .systemGray
3408
+ }
3409
+
3410
+ return cell
3411
+ }
3412
+
3413
+ func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
3414
+ selectedIndex = indexPath
3415
+ selectedPaymentMethod = arrPaymentMethods[indexPath.row].name
3416
+ collectionView.reloadData()
3417
+
3418
+ // Show the appropriate view based on the selected payment method
3419
+ switch arrPaymentMethods[indexPath.row].name {
3420
+ case "Card":
3421
+ if selectedPaymentMethod == "Card" {
3422
+ if UserStoreSingleton.shared.isLoggedIn == false {
3423
+ self.viewSingleSavedAccount.isHidden = true
3424
+ self.viewBankFields.isHidden = true
3425
+ self.viewBtnShowSavedCards.isHidden = false
3426
+ self.lblBtnShowSaveCard.text = "Show Saved Cards"
3427
+ self.viewBtnShowSavedCardHeight.constant = 50
3428
+ self.viewBtnShowSavedCardTopCon.constant = 20
3429
+ self.OTPView.isHidden = true
3430
+ self.emailView.isHidden = true
3431
+ self.viewCardFields.isHidden = false
3432
+ self.viewSingleSavedCard.isHidden = true
3433
+ self.viewChangeCard.isHidden = true
3434
+ self.viewUpdateCard.isHidden = true
3435
+ self.viewAddNewCard.isHidden = true
3436
+ self.btnNext.isHidden = false
3437
+ self.btnNextHeight.constant = 50
3438
+ self.btnNextTopCon.constant = 20
3439
+ self.viewTermsAndConditions.isHidden = true
3440
+ self.viewCrypto.isHidden = true
3441
+ }
3442
+ else {
3443
+ getShowCardsApi()
3444
+ self.viewSingleSavedAccount.isHidden = true
3445
+ self.viewBankFields.isHidden = true
3446
+ self.viewBtnShowSavedCards.isHidden = false
3447
+ self.viewBtnShowSavedCards.isHidden = true
3448
+ self.viewBtnShowSavedCardHeight.constant = 0
3449
+ self.viewBtnShowSavedCardTopCon.constant = 0
3450
+ self.OTPView.isHidden = true
3451
+ self.emailView.isHidden = true
3452
+ self.viewCardFields.isHidden = true
3453
+ self.viewSingleSavedCard.isHidden = false
3454
+ self.viewChangeCard.isHidden = true
3455
+ self.viewUpdateCard.isHidden = true
3456
+ self.viewAddNewCard.isHidden = true
3457
+ self.btnNext.isHidden = true
3458
+ self.btnNextHeight.constant = 0
3459
+ self.btnNextTopCon.constant = 8
3460
+ self.viewChangedAccount.isHidden = true
3461
+ self.viewNewBankAccount.isHidden = true
3462
+ self.viewCrypto.isHidden = true
3463
+ }
3464
+ }
3465
+ case "Bank":
3466
+ if selectedPaymentMethod == "Bank" {
3467
+ if UserStoreSingleton.shared.isLoggedIn == false {
3468
+ self.viewBankFields.isHidden = false
3469
+ self.viewBtnShowSavedCards.isHidden = false
3470
+ self.lblBtnShowSaveCard.text = "Use Saved Accounts"
3471
+ self.viewBtnShowSavedCardHeight.constant = 50
3472
+ self.viewBtnShowSavedCardTopCon.constant = 20
3473
+ self.OTPView.isHidden = true
3474
+ self.emailView.isHidden = true
3475
+ self.viewCardFields.isHidden = true
3476
+ self.viewSingleSavedCard.isHidden = true
3477
+ self.viewChangeCard.isHidden = true
3478
+ self.viewUpdateCard.isHidden = true
3479
+ self.viewAddNewCard.isHidden = true
3480
+ self.btnNext.isHidden = false
3481
+ self.btnNextHeight.constant = 50
3482
+ self.btnNextTopCon.constant = 20
3483
+ self.viewTermsAndConditions.isHidden = false
3484
+ self.viewCrypto.isHidden = true
3485
+ self.viewSingleSavedAccount.isHidden = true
3486
+ }
3487
+ else {
3488
+ getShowBankAccountsApi()
3489
+ }
3490
+ }
3491
+ case "Crypto":
3492
+ if selectedPaymentMethod == "Crypto" {
3493
+ self.cryptoChargeApi()
3494
+ self.startCryptoTimer()
3495
+ self.totalTimeInSeconds = 300
3496
+
3497
+ self.viewCrypto.isHidden = false
3498
+ self.viewQrCodeHeight.constant = 440
3499
+ self.viewCryptoHeight.constant = 440
3500
+ self.viewSingleSavedAccount.isHidden = true
3501
+ self.viewBankFields.isHidden = true
3502
+ self.viewBtnShowSavedCards.isHidden = true
3503
+ self.viewBtnShowSavedCardHeight.constant = 0
3504
+ self.viewBtnShowSavedCardTopCon.constant = 0
3505
+ self.OTPView.isHidden = true
3506
+ self.emailView.isHidden = true
3507
+ self.viewCardFields.isHidden = true
3508
+ self.viewSingleSavedCard.isHidden = true
3509
+ self.viewChangeCard.isHidden = true
3510
+ self.viewUpdateCard.isHidden = true
3511
+ self.viewAddNewCard.isHidden = true
3512
+ self.btnNext.isHidden = true
3513
+ self.btnNextHeight.constant = 0
3514
+ self.btnNextTopCon.constant = 8
3515
+ self.viewTermsAndConditions.isHidden = true
3516
+ self.viewChangedAccount.isHidden = true
3517
+ self.viewNewBankAccount.isHidden = true
3518
+
3519
+ }
3520
+ default:
3521
+ break
3522
+ }
3523
+ }
3524
+
3525
+ func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
3526
+ return CGSize(width: 100, height: 70)
3527
+ }
3528
+
3529
+ }
3530
+
3531
+ //MARK: - Table View
3532
+ extension PaymentInfoVC: UITableViewDelegate, UITableViewDataSource {
3533
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
3534
+ if tableView == tblViewSavedCardsList {
3535
+ return savedCards.count + 1
3536
+ }
3537
+ else if tableView == tblViewSavedBankAccounts {
3538
+ return bankAccounts.count + 1
3539
+ }
3540
+ else if tableView == tblViewAccountTypes {
3541
+ return arrAccountType.count
3542
+ }
3543
+ else if tableView == tblViewAccountTypeNewAccountView {
3544
+ return arrAccountType.count
3545
+ }
3546
+ return 0
3547
+ }
3548
+
3549
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
3550
+ if tableView == tblViewSavedCardsList {
3551
+ let cell = tableView.dequeueReusableCell(withIdentifier: "SavedCardsTVC", for: indexPath) as! SavedCardsTVC
3552
+ // Clear previous button targets to avoid duplicate actions
3553
+ cell.btnSelectCard.removeTarget(nil, action: nil, for: .allEvents)
3554
+ cell.btnThreeDot.removeTarget(nil, action: nil, for: .allEvents)
3555
+ cell.btnRemoveCard.removeTarget(nil, action: nil, for: .allEvents)
3556
+ cell.btnUpdateCard.removeTarget(nil, action: nil, for: .allEvents)
3557
+
3558
+ // Check if it's the "Add New Card" cell
3559
+ if indexPath.row == savedCards.count {
3560
+ configureAddNewCardCell(cell)
3561
+ } else {
3562
+ configureSavedCardCell(cell, at: indexPath)
3563
+ }
3564
+
3565
+ return cell
3566
+ }
3567
+ else if tableView == tblViewSavedBankAccounts {
3568
+ let cell = tableView.dequeueReusableCell(withIdentifier: "SavedAccountTVC", for: indexPath) as! SavedAccountTVC
3569
+ // Clear previous button targets to avoid duplicate actions
3570
+ cell.btnSelectAccount.removeTarget(nil, action: nil, for: .allEvents)
3571
+ cell.btnThreeDotBank.removeTarget(nil, action: nil, for: .allEvents)
3572
+ cell.btnRemoveBankAccount.removeTarget(nil, action: nil, for: .allEvents)
3573
+
3574
+ // Check if it's the "Add New Card" cell
3575
+ if indexPath.row == bankAccounts.count {
3576
+ configureAddNewAccountCell(cell)
3577
+ } else {
3578
+ configureSavedAccountCell(cell, at: indexPath)
3579
+ }
3580
+
3581
+ return cell
3582
+ }
3583
+ else if tableView == tblViewAccountTypes {
3584
+ let cell = tableView.dequeueReusableCell(withIdentifier: "AccountTypeTVC") as! AccountTypeTVC
3585
+ cell.lblAccountType.text = arrAccountType[indexPath.row]
3586
+ return cell
3587
+ }
3588
+ else if tableView == tblViewAccountTypeNewAccountView {
3589
+ let cell = tableView.dequeueReusableCell(withIdentifier: "AccountTypeTVC") as! AccountTypeTVC
3590
+ cell.lblAccountType.text = arrAccountType[indexPath.row]
3591
+ return cell
3592
+ }
3593
+
3594
+ return UITableViewCell()
3595
+ }
3596
+
3597
+ //Card
3598
+ func configureAddNewCardCell(_ cell: SavedCardsTVC) {
3599
+ cell.lblCardNumber.text = "New Card"
3600
+ cell.lblExpireDate.isHidden = true
3601
+ cell.btnThreeDot.isHidden = true
3602
+ cell.imgViewCard.isHidden = true
3603
+ cell.btnSelectCard.setImage(UIImage(systemName: "plus"), for: .normal)
3604
+ cell.btnSelectCard.tag = savedCards.count
3605
+ cell.btnSelectCard.addTarget(self, action: #selector(addNewCardTapped(_:)), for: .touchUpInside)
3606
+ cell.viewBtnRemoveUpdate.isHidden = true
3607
+
3608
+ cell.imgViewCardWidth.constant = 0
3609
+ cell.imgViewCardRightCon.constant = 0
3610
+ }
3611
+
3612
+ func configureSavedCardCell(_ cell: SavedCardsTVC, at indexPath: IndexPath) {
3613
+ let card = savedCards[indexPath.row]
3614
+ cell.lblCardNumber.text = "****\(card.ccLast4)"
3615
+ cell.lblExpireDate.isHidden = false
3616
+ cell.lblExpireDate.text = "Expiry: \(card.ccValidThru)"
3617
+ cell.btnThreeDot.isHidden = false
3618
+ cell.imgViewCard.isHidden = false
3619
+
3620
+ // Reset constraints for regular saved card cells
3621
+ cell.imgViewCardWidth.constant = 40 // Set to the original width constraint value
3622
+ cell.imgViewCardRightCon.constant = 16 // Set to the original right constraint value
3623
+
3624
+ // Update button image based on the selected state
3625
+ let isSelected = (selectedCardIndex == indexPath.row)
3626
+ let imageName = isSelected ? "circle.inset.filled" : "circle"
3627
+ cell.btnSelectCard.setImage(UIImage(systemName: imageName), for: .normal)
3628
+
3629
+ // Set up actions for buttons
3630
+ cell.btnSelectCard.tag = indexPath.row
3631
+ cell.btnSelectCard.addTarget(self, action: #selector(btnSelectCardTapped(_:)), for: .touchUpInside)
3632
+
3633
+ cell.btnThreeDot.tag = indexPath.row
3634
+ cell.btnThreeDot.addTarget(self, action: #selector(btnThreeDotTapped(_:)), for: .touchUpInside)
3635
+
3636
+ cell.btnRemoveCard.tag = indexPath.row
3637
+ cell.btnRemoveCard.addTarget(self, action: #selector(btnRemoveCardTapped(_:)), for: .touchUpInside)
3638
+
3639
+ cell.btnUpdateCard.tag = indexPath.row
3640
+ cell.btnUpdateCard.addTarget(self, action: #selector(btnUpdateCardTapped(_:)), for: .touchUpInside)
3641
+
3642
+ // Hide or show the remove/update view based on cell state
3643
+ cell.viewBtnRemoveUpdate.isHidden = !cell.isRemoveUpdateViewVisible
3644
+ }
3645
+
3646
+ //Bank
3647
+ func configureAddNewAccountCell(_ cell: SavedAccountTVC) {
3648
+ cell.lblAccountNumber.text = "New Account"
3649
+ cell.lblAccountType.isHidden = true
3650
+ cell.btnThreeDotBank.isHidden = true
3651
+ cell.imgViewBank.isHidden = true
3652
+ cell.btnSelectAccount.setImage(UIImage(systemName: "plus"), for: .normal)
3653
+ cell.btnSelectAccount.tag = bankAccounts.count
3654
+ cell.btnSelectAccount.addTarget(self, action: #selector(addNewBankAccountTapped(_:)), for: .touchUpInside)
3655
+ cell.viewBtnRemove.isHidden = true
3656
+
3657
+ cell.imgViewBankWidth.constant = 0
3658
+ cell.imgViewBankRightCon.constant = 0
3659
+ }
3660
+
3661
+ func configureSavedAccountCell(_ cell: SavedAccountTVC, at indexPath: IndexPath) {
3662
+ let bank = bankAccounts[indexPath.row]
3663
+ cell.lblAccountNumber.text = "****\(bank.account_number_last_4 ?? "")"
3664
+ cell.lblAccountType.isHidden = false
3665
+ cell.lblAccountType.text = "Type: \(bank.account_type ?? "")"
3666
+ cell.btnThreeDotBank.isHidden = false
3667
+ cell.imgViewBank.isHidden = false
3668
+
3669
+ // Reset constraints for regular saved card cells
3670
+ cell.imgViewBankWidth.constant = 40 // Set to the original width constraint value
3671
+ cell.imgViewBankRightCon.constant = 16 // Set to the original right constraint value
3672
+
3673
+ // Update button image based on the selected state
3674
+ let isSelected = (selectedBankIndex == indexPath.row)
3675
+ let imageName = isSelected ? "circle.inset.filled" : "circle"
3676
+ cell.btnSelectAccount.setImage(UIImage(systemName: imageName), for: .normal)
3677
+
3678
+ // Set up actions for buttons
3679
+ cell.btnSelectAccount.tag = indexPath.row
3680
+ cell.btnSelectAccount.addTarget(self, action: #selector(btnSelectAccountTapped(_:)), for: .touchUpInside)
3681
+
3682
+ cell.btnThreeDotBank.tag = indexPath.row
3683
+ cell.btnThreeDotBank.addTarget(self, action: #selector(btnThreeDotBankTapped(_:)), for: .touchUpInside)
3684
+
3685
+ cell.btnRemoveBankAccount.tag = indexPath.row
3686
+ cell.btnRemoveBankAccount.addTarget(self, action: #selector(btnRemoveBankAccountTapped(_:)), for: .touchUpInside)
3687
+
3688
+ // Hide or show the remove/update view based on cell state
3689
+ cell.viewBtnRemove.isHidden = !cell.isRemoveUpdateViewVisible
3690
+ }
3691
+
3692
+ func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
3693
+ if tableView == tblViewAccountTypes {
3694
+ return 45
3695
+ }
3696
+ else if tableView == tblViewAccountTypeNewAccountView {
3697
+ return 45
3698
+ }
3699
+ else {
3700
+ return 80
3701
+ }
3702
+ }
3703
+
3704
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
3705
+ if tableView == tblViewAccountTypes {
3706
+ let selectedAccountType = arrAccountType[indexPath.row]
3707
+ txtFieldAccountType.text = selectedAccountType // Fill the selected account type into the text field
3708
+ viewAccountType.isHidden = true // Hide the table view after selection
3709
+ }
3710
+ else if tableView == tblViewAccountTypeNewAccountView {
3711
+ let selectedAccountType = arrAccountType[indexPath.row]
3712
+ txtFieldAccountTypeNewAccountView.text = selectedAccountType // Fill the selected account type into the text field
3713
+ viewAccountTypeNewAccountView.isHidden = true // Hide the table view after selection
3714
+ }
3715
+ }
3716
+
3717
+ @objc func btnSelectCardTapped(_ sender: UIButton) {
3718
+ let index = sender.tag
3719
+
3720
+ // Store the index of the previously selected card
3721
+ let previousSelectedIndex = selectedCardIndex
3722
+
3723
+ // Toggle the selection state: if the same card is selected, deselect it; otherwise, select the new one
3724
+ selectedCardIndex = (selectedCardIndex == index) ? nil : index
3725
+
3726
+ // Update the single card view
3727
+ if let selectedIndex = selectedCardIndex {
3728
+ updateSingleCardView(for: selectedIndex)
3729
+ viewSingleSavedCard.isHidden = false
3730
+ viewChangeCard.isHidden = true
3731
+ } else {
3732
+ viewSingleSavedCard.isHidden = true
3733
+ }
3734
+
3735
+ // Reload the previously selected row to update its state
3736
+ if let previousIndex = previousSelectedIndex {
3737
+ tblViewSavedCardsList.reloadRows(at: [IndexPath(row: previousIndex, section: 0)], with: .automatic)
3738
+ }
3739
+
3740
+ // Reload the newly selected row to update its state
3741
+ tblViewSavedCardsList.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
3742
+ }
3743
+
3744
+ private func updateSingleCardView(for index: Int) {
3745
+ let selectedCard = savedCards[index]
3746
+ lblCardNumberSigleSavedCard.text = "****\(selectedCard.ccLast4)"
3747
+ lblExpireDateSingelSavedCard.text = "Expiry: \(selectedCard.ccValidThru)"
3748
+
3749
+ if isSelectForPay {
3750
+ viewTxtFieldCVVSingleCard.isHidden = false
3751
+ btnPayNowSingleCard.isHidden = false
3752
+ viewSingleSavedCardHeight.constant = 220
3753
+ } else {
3754
+ viewTxtFieldCVVSingleCard.isHidden = true
3755
+ btnPayNowSingleCard.isHidden = true
3756
+ viewSingleSavedCardHeight.constant = 60
3757
+ }
3758
+ }
3759
+
3760
+ @objc func btnThreeDotTapped(_ sender: UIButton) {
3761
+ let index = sender.tag
3762
+ guard let cell = tblViewSavedCardsList.cellForRow(at: IndexPath(row: index, section: 0)) as? SavedCardsTVC else {
3763
+ return
3764
+ }
3765
+ // Toggle visibility of remove/update buttons
3766
+ cell.isRemoveUpdateViewVisible.toggle()
3767
+ cell.viewBtnRemoveUpdate.isHidden = !cell.isRemoveUpdateViewVisible
3768
+ }
3769
+
3770
+ @objc func btnRemoveCardTapped(_ sender: UIButton) {
3771
+ let index = sender.tag
3772
+
3773
+ let selectedCard = savedCards[index]
3774
+ selectedCardID = selectedCard.cardId
3775
+
3776
+ let alertController = UIAlertController(
3777
+ title: "Confirm Deletion",
3778
+ message: "Are you sure you want to delete this card?",
3779
+ preferredStyle: .alert
3780
+ )
3781
+ let noAction = UIAlertAction(title: "No", style: .cancel, handler: nil)
3782
+ let yesAction = UIAlertAction(title: "Yes", style: .destructive) { [weak self] _ in
3783
+ guard let self = self else { return }
3784
+ // Call the deleteCardApi with the selected card ID
3785
+ self.deleteCardApi(cardID: selectedCard.cardId, at: index)
3786
+ }
3787
+ alertController.addAction(noAction)
3788
+ alertController.addAction(yesAction)
3789
+ present(alertController, animated: true, completion: nil)
3790
+ }
3791
+
3792
+ @objc func btnUpdateCardTapped(_ sender: UIButton) {
3793
+ let index = sender.tag
3794
+ let selectedCard = savedCards[index]
3795
+ lblCardNumberUpdateCardView.text = "****\(selectedCard.ccLast4)"
3796
+ lblExpireDateUpdateCardView.text = "Expiry: \(selectedCard.ccValidThru)"
3797
+
3798
+ selectedCardID = selectedCard.cardId
3799
+
3800
+ viewUpdateCard.isHidden = false
3801
+ // Adjust view visibility
3802
+ viewBtnShowSavedCards.isHidden = true
3803
+ viewBtnShowSavedCardHeight.constant = 0
3804
+ viewBtnShowSavedCardTopCon.constant = 0
3805
+ viewCardFields.isHidden = true
3806
+ btnNext.isHidden = true
3807
+ btnNextHeight.constant = 0
3808
+ btnNextTopCon.constant = 8
3809
+ viewChangeCard.isHidden = true
3810
+ viewAddNewCard.isHidden = true
3811
+ viewSingleSavedCard.isHidden = true
3812
+ }
3813
+
3814
+ @objc func addNewCardTapped(_ sender: UIButton) {
3815
+ // Make the Add New Card view visible
3816
+ viewAddNewCard.isHidden = false
3817
+
3818
+ // Hide other conflicting views to prevent UI overlap
3819
+ viewSingleSavedCard.isHidden = true
3820
+ viewChangeCard.isHidden = true
3821
+ viewUpdateCard.isHidden = true
3822
+ viewCardFields.isHidden = true
3823
+
3824
+ // Adjust visibility of any other related views if necessary
3825
+ viewBtnShowSavedCards.isHidden = true
3826
+ viewBtnShowSavedCardHeight.constant = 0
3827
+ viewBtnShowSavedCardTopCon.constant = 0
3828
+ btnNext.isHidden = true
3829
+ btnNextHeight.constant = 0
3830
+ btnNextTopCon.constant = 8
3831
+ }
3832
+
3833
+ //Bank Cell
3834
+ @objc func addNewBankAccountTapped(_ sender: UIButton) {
3835
+ self.viewNewBankAccount.isHidden = false
3836
+ self.viewSingleSavedAccount.isHidden = true
3837
+ self.viewBankFields.isHidden = true
3838
+ self.viewBtnShowSavedCards.isHidden = true
3839
+ self.viewBtnShowSavedCardHeight.constant = 0
3840
+ self.viewBtnShowSavedCardTopCon.constant = 0
3841
+ self.OTPView.isHidden = true
3842
+ self.emailView.isHidden = true
3843
+ self.viewCardFields.isHidden = true
3844
+ self.viewSingleSavedCard.isHidden = true
3845
+ self.viewChangeCard.isHidden = true
3846
+ self.viewUpdateCard.isHidden = true
3847
+ self.viewAddNewCard.isHidden = true
3848
+ self.btnNext.isHidden = true
3849
+ self.btnNextHeight.constant = 0
3850
+ self.btnNextTopCon.constant = 8
3851
+ self.viewChangedAccount.isHidden = true
3852
+ }
3853
+
3854
+ @objc func btnSelectAccountTapped(_ sender: UIButton) {
3855
+ let index = sender.tag
3856
+ // Store the index of the previously selected card
3857
+ let previousSelectedIndex = selectedBankIndex
3858
+
3859
+ // Toggle the selection state: if the same card is selected, deselect it; otherwise, select the new one
3860
+ selectedBankIndex = (selectedBankIndex == index) ? nil : index
3861
+
3862
+ // Update the single card view
3863
+ if let selectedIndex = selectedBankIndex {
3864
+ updateSingleBankView(for: selectedIndex)
3865
+ viewSingleSavedAccount.isHidden = false
3866
+ viewChangedAccount.isHidden = true
3867
+ } else {
3868
+ viewSingleSavedAccount.isHidden = true
3869
+ }
3870
+
3871
+ // Reload the previously selected row to update its state
3872
+ if let previousIndex = previousSelectedIndex {
3873
+ tblViewSavedBankAccounts.reloadRows(at: [IndexPath(row: previousIndex, section: 0)], with: .automatic)
3874
+ }
3875
+
3876
+ // Reload the newly selected row to update its state
3877
+ tblViewSavedBankAccounts.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
3878
+ }
3879
+
3880
+ private func updateSingleBankView(for index: Int) {
3881
+ let selectedCard = bankAccounts[index]
3882
+ lblAccountNumberSingleAccountView.text = "****\(selectedCard.account_number_last_4 ?? "")"
3883
+ lblAccountTypeSingleAccountView.text = "Type: \(selectedCard.account_type ?? "")"
3884
+
3885
+ if isSelectForPayBank {
3886
+ viewTermAndConditionsSingleAccountView.isHidden = false
3887
+ btnPayNowSingleAccountView.isHidden = false
3888
+ viewSingleAccountViewHeight.constant = 170
3889
+ } else {
3890
+ viewTermAndConditionsSingleAccountView.isHidden = true
3891
+ btnPayNowSingleAccountView.isHidden = true
3892
+ viewSingleAccountViewHeight.constant = 68
3893
+ btnNext.isHidden = true
3894
+ btnNextHeight.constant = 0
3895
+ btnNextTopCon.constant = 0
3896
+ }
3897
+ }
3898
+
3899
+ @objc func btnThreeDotBankTapped(_ sender: UIButton) {
3900
+ let index = sender.tag
3901
+ guard let cell = tblViewSavedBankAccounts.cellForRow(at: IndexPath(row: index, section: 0)) as? SavedAccountTVC else {
3902
+ return
3903
+ }
3904
+ // Toggle visibility of remove/update buttons
3905
+ cell.isRemoveUpdateViewVisible.toggle()
3906
+ cell.viewBtnRemove.isHidden = !cell.isRemoveUpdateViewVisible
3907
+ }
3908
+
3909
+ @objc func btnRemoveBankAccountTapped(_ sender: UIButton) {
3910
+ let index = sender.tag
3911
+
3912
+ let selectedAccount = bankAccounts[index]
3913
+ selectedBankID = selectedAccount.account_id
3914
+
3915
+ let alertController = UIAlertController(
3916
+ title: "Confirm Deletion",
3917
+ message: "Are you sure you want to delete this account?",
3918
+ preferredStyle: .alert
3919
+ )
3920
+ let noAction = UIAlertAction(title: "No", style: .cancel, handler: nil)
3921
+ let yesAction = UIAlertAction(title: "Yes", style: .destructive) { [weak self] _ in
3922
+ guard let self = self else { return }
3923
+ // Call the deleteCardApi with the selected card ID
3924
+ self.deleteAccountApi(accountID: selectedAccount.account_id ?? "", at: index)
3925
+ }
3926
+ alertController.addAction(noAction)
3927
+ alertController.addAction(yesAction)
3928
+ present(alertController, animated: true, completion: nil)
3929
+ }
3930
+
3931
+ }
3932
+
3933
+ // MARK: - Text Field Delegate Methods
3934
+ extension PaymentInfoVC: UITextFieldDelegate {
3935
+
3936
+ func textFieldShouldReturn(_ textField: UITextField) -> Bool {
3937
+ if textField == txtFieldEmail {
3938
+ // Check if the email field is empty
3939
+ guard let email = textField.text, !email.isEmpty else {
3940
+ // Show an alert if the email field is empty
3941
+ showAlert(message: "Please enter an email address.")
3942
+ return false
3943
+ }
3944
+ // Validate email format before calling the API
3945
+ if isValidEmail(email) {
3946
+ // Hide email view, show OTP view, and start the timer
3947
+ emailView.isHidden = true
3948
+ OTPView.isHidden = false
3949
+ startTimer()
3950
+ // Call the email verification API
3951
+ emailVerificationApi()
3952
+ } else {
3953
+ // Show an alert or error message if the email is not valid
3954
+ showAlert(message: "Please enter a valid email address.")
3955
+ }
3956
+ }
3957
+ textField.resignFirstResponder()
3958
+ return true
3959
+ }
3960
+
3961
+ func textFieldDidChangeSelection(_ textField: UITextField) {
3962
+ guard let otpCode = textField.text, otpCode.count >= 6, textField.textContentType == .oneTimeCode else { return }
3963
+ txtFieldOTPText1.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 0)])
3964
+ txtFieldOTPText2.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 1)])
3965
+ txtFieldOTPText3.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 2)])
3966
+ txtFieldOTPText4.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 3)])
3967
+ txtFieldOTPText5.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 4)])
3968
+ txtFieldOTPText6.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 5)])
3969
+ txtFieldOTPText6.becomeFirstResponder()
3970
+ }
3971
+
3972
+ func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
3973
+ // Handle card number input for both old and new card fields
3974
+ if textField == cardNumberTextField.textField || textField == txtFieldCardNumberNewCardView {
3975
+ if string.isEmpty {
3976
+ let modifiedText = NSString(string: textField.text ?? "").replacingCharacters(in: range, with: string)
3977
+ textField.text = formatCardNumber(modifiedText)
3978
+ return false
3979
+ }
3980
+
3981
+ let text = (textField.text ?? "") + string
3982
+ if text.filter({ $0.isNumber }).count > 16 {
3983
+ return false
3984
+ }
3985
+
3986
+ textField.text = formatCardNumber(text)
3987
+ if let lastCharacter = text.last, lastCharacter == " " {
3988
+ textField.text = String(text.dropLast())
3989
+ }
3990
+
3991
+ return false
3992
+ }
3993
+
3994
+ // Handle card CVV input for both old and new card fields
3995
+ else if textField == cardCvvTextField.textField || textField == txtFieldCVVNewCardView || textField == txtFieldCVVUpdateCardView {
3996
+ let maxLength = 3
3997
+ let currentString = (textField.text ?? "") as NSString
3998
+ let newString = currentString.replacingCharacters(in: range, with: string)
3999
+ return newString.count <= maxLength
4000
+ }
4001
+
4002
+ // Handle card expiry date input for both old and new card fields
4003
+ else if textField == cardExpiryTextField.textField || textField == txtFieldExpiryDateNewCardView || textField == txtFieldExpireDateUpdateCardView {
4004
+ if range.length > 0 {
4005
+ return true
4006
+ }
4007
+ if string.isEmpty {
4008
+ return false
4009
+ }
4010
+ if range.location > 6 {
4011
+ return false
4012
+ }
4013
+
4014
+ var originalText = textField.text ?? ""
4015
+ let replacementText = string.replacingOccurrences(of: " ", with: "")
4016
+
4017
+ // Ensure only digits are allowed
4018
+ if !CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: replacementText)) {
4019
+ return false
4020
+ }
4021
+
4022
+ // Insert "/" after MM
4023
+ if range.location == 2 {
4024
+ originalText.append("/")
4025
+ }
4026
+
4027
+ textField.text = originalText + replacementText
4028
+ return false
4029
+ }
4030
+
4031
+ // Handle OTP input fields
4032
+ else if [txtFieldOTPText1, txtFieldOTPText2, txtFieldOTPText3, txtFieldOTPText4, txtFieldOTPText5, txtFieldOTPText6].contains(textField) {
4033
+ if string.count == 1 {
4034
+ // Single character input
4035
+ textField.text = string
4036
+ switch textField {
4037
+ case txtFieldOTPText1:
4038
+ txtFieldOTPText2.becomeFirstResponder()
4039
+ case txtFieldOTPText2:
4040
+ txtFieldOTPText3.becomeFirstResponder()
4041
+ case txtFieldOTPText3:
4042
+ txtFieldOTPText4.becomeFirstResponder()
4043
+ case txtFieldOTPText4:
4044
+ txtFieldOTPText5.becomeFirstResponder()
4045
+ case txtFieldOTPText5:
4046
+ txtFieldOTPText6.becomeFirstResponder()
4047
+ case txtFieldOTPText6:
4048
+ txtFieldOTPText6.resignFirstResponder()
4049
+ // Call OTP verification API when the last field is filled
4050
+ if getCombinedOTP().count == 6 {
4051
+ otpVerificationApi()
4052
+ }
4053
+ default:
4054
+ break
4055
+ }
4056
+ updateViewColors()
4057
+ return false
4058
+ } else if string.isEmpty {
4059
+ // Handle backspace
4060
+ textField.text = ""
4061
+ switch textField {
4062
+ case txtFieldOTPText6:
4063
+ txtFieldOTPText5.becomeFirstResponder()
4064
+ case txtFieldOTPText5:
4065
+ txtFieldOTPText4.becomeFirstResponder()
4066
+ case txtFieldOTPText4:
4067
+ txtFieldOTPText3.becomeFirstResponder()
4068
+ case txtFieldOTPText3:
4069
+ txtFieldOTPText2.becomeFirstResponder()
4070
+ case txtFieldOTPText2:
4071
+ txtFieldOTPText1.becomeFirstResponder()
4072
+ default:
4073
+ break
4074
+ }
4075
+ updateViewColors()
4076
+ return false
4077
+ }
4078
+ return textField.text?.count == 0
4079
+ }
4080
+
4081
+ return true
4082
+ }
4083
+
4084
+ // Helper method to format the card number with spaces
4085
+ func formatCardNumber(_ input: String) -> String {
4086
+ var formattedString = ""
4087
+ let trimmedString = input.replacingOccurrences(of: " ", with: "")
4088
+ for i in 0..<trimmedString.count {
4089
+ if i > 0 && i % 4 == 0 {
4090
+ formattedString += " "
4091
+ }
4092
+ let index = trimmedString.index(trimmedString.startIndex, offsetBy: i)
4093
+ formattedString.append(trimmedString[index])
4094
+ }
4095
+ return formattedString
4096
+ }
4097
+
4098
+ // Update the view colors based on text presence in OTP fields
4099
+ private func updateViewColors() {
4100
+ viewTextOTP1.backgroundColor = txtFieldOTPText1.text?.isEmpty == false ? .systemBlue : .systemGray
4101
+ viewTextOTP2.backgroundColor = txtFieldOTPText2.text?.isEmpty == false ? .systemBlue : .systemGray
4102
+ viewTextOTP3.backgroundColor = txtFieldOTPText3.text?.isEmpty == false ? .systemBlue : .systemGray
4103
+ viewTextOTP4.backgroundColor = txtFieldOTPText4.text?.isEmpty == false ? .systemBlue : .systemGray
4104
+ viewTextOTP5.backgroundColor = txtFieldOTPText5.text?.isEmpty == false ? .systemBlue : .systemGray
4105
+ viewTextOTP6.backgroundColor = txtFieldOTPText6.text?.isEmpty == false ? .systemBlue : .systemGray
4106
+ }
4107
+
4108
+ @objc func textFieldDidChange(_ textField: UITextField) {
4109
+ guard let otpCode = textField.text, otpCode.count >= 6, textField.textContentType == .oneTimeCode else { return }
4110
+ // Split the OTP into each text field
4111
+ txtFieldOTPText1.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 0)])
4112
+ txtFieldOTPText2.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 1)])
4113
+ txtFieldOTPText3.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 2)])
4114
+ txtFieldOTPText4.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 3)])
4115
+ txtFieldOTPText5.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 4)])
4116
+ txtFieldOTPText6.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 5)])
4117
+ // Automatically move to the last text field
4118
+ txtFieldOTPText6.becomeFirstResponder()
4119
+ }
4120
+
4121
+ func getCombinedOTP() -> String {
4122
+ let otp1 = txtFieldOTPText1.text ?? ""
4123
+ let otp2 = txtFieldOTPText2.text ?? ""
4124
+ let otp3 = txtFieldOTPText3.text ?? ""
4125
+ let otp4 = txtFieldOTPText4.text ?? ""
4126
+ let otp5 = txtFieldOTPText5.text ?? ""
4127
+ let otp6 = txtFieldOTPText6.text ?? ""
4128
+
4129
+ return otp1 + otp2 + otp3 + otp4 + otp5 + otp6
4130
+ }
4131
+
4132
+ // UITextFieldDelegate methods
4133
+ func textFieldDidBeginEditing(_ textField: UITextField) {
4134
+ if textField == cardNumberTextField.textField {
4135
+ viewTextFieldCardNumber.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4136
+ } else if textField == cardExpiryTextField.textField {
4137
+ viewTxtFieldExpiryDate.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4138
+ } else if textField == cardCvvTextField.textField {
4139
+ viewTxtFieldCVV.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4140
+ } else if textField == cardNameTextField.textField {
4141
+ viewTxtFieldNameOnCard.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4142
+ }
4143
+ else if textField == txtFieldEmail {
4144
+ viewTxtFieldEmail.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4145
+ }
4146
+ //Single Saved Card view
4147
+ else if textField == txtFieldCVVSingleSavedCard {
4148
+ viewTxtFieldCVVSingleSavedCard.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4149
+ }
4150
+ //Update Card View
4151
+ else if textField == txtFieldExpireDateUpdateCardView {
4152
+ viewTxtFieldExpireDateUpdateCardView.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4153
+ }
4154
+ else if textField == txtFieldCVVUpdateCardView {
4155
+ viewtxtFieldCVVUpdateCardView.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4156
+ }
4157
+ else if textField == txtFieldNameOnCardUpdateCardView {
4158
+ viewTxtFieldNameOnCardUpdateCardView.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4159
+ }
4160
+ //NEW Card View
4161
+ else if textField == txtFieldCardNumberNewCardView {
4162
+ viewtxtFieldCardNumberNewCardView.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4163
+ }
4164
+ else if textField == txtFieldExpiryDateNewCardView {
4165
+ viewtxtFieldExpiryDateNewCardView.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4166
+ }
4167
+ else if textField == txtFieldCVVNewCardView {
4168
+ viewtxtFieldCVVNewCardView.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4169
+ }
4170
+ else if textField == txtFieldNameOnCardNewCardView {
4171
+ viewtxtFieldNameOnCardNewCardView.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4172
+ }
4173
+ //Bank View
4174
+ else if textField == txtFieldAccountName {
4175
+ viewtxtFieldAccountName.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4176
+ }
4177
+ else if textField == txtFieldRoutingNumber {
4178
+ viewtxtFieldRoutingNumber.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4179
+ }
4180
+ else if textField == txtFieldAccountType {
4181
+ viewtxtFieldAccountType.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4182
+ }
4183
+ else if textField == txtFieldAccountNumber {
4184
+ viewtxtFieldAccountNumber.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4185
+ }
4186
+ //New Bank Account View
4187
+ else if textField == txtFieldAccountNameNewAccountView {
4188
+ viewtxtFieldAccountNameNewAccountView.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4189
+ }
4190
+ else if textField == txtFieldRoutingNumberNewAccountView {
4191
+ viewtxtFieldRoutingNumberNewAccountView.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4192
+ }
4193
+ else if textField == txtFieldAccountTypeNewAccountView {
4194
+ viewtxtFieldAccountTypeNewAccountView.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4195
+ }
4196
+ else if textField == txtFieldAccountNumberNewAccountView {
4197
+ viewtxtFieldAccountNumberNewAccountView.layer.borderColor = UIColor._1757D9.withAlphaComponent(1).cgColor
4198
+ }
4199
+ }
4200
+
4201
+ func textFieldDidEndEditing(_ textField: UITextField) {
4202
+ if textField == cardNumberTextField.textField {
4203
+ viewTextFieldCardNumber.layer.borderColor = UIColor.systemGray.cgColor
4204
+ } else if textField == cardExpiryTextField.textField {
4205
+ viewTxtFieldExpiryDate.layer.borderColor = UIColor.systemGray.cgColor
4206
+ } else if textField == cardCvvTextField.textField {
4207
+ viewTxtFieldCVV.layer.borderColor = UIColor.systemGray.cgColor
4208
+ } else if textField == cardNameTextField.textField {
4209
+ viewTxtFieldNameOnCard.layer.borderColor = UIColor.systemGray.cgColor
4210
+ }
4211
+ else if textField == txtFieldEmail {
4212
+ viewTxtFieldEmail.layer.borderColor = UIColor.systemGray.cgColor
4213
+ }
4214
+ else if textField == txtFieldCVVSingleSavedCard {
4215
+ viewTxtFieldCVVSingleSavedCard.layer.borderColor = UIColor.systemGray.cgColor
4216
+ }
4217
+ //Update Card View
4218
+ else if textField == txtFieldExpireDateUpdateCardView {
4219
+ viewTxtFieldExpireDateUpdateCardView.layer.borderColor = UIColor.systemGray.cgColor
4220
+ }
4221
+ else if textField == txtFieldCVVUpdateCardView {
4222
+ viewtxtFieldCVVUpdateCardView.layer.borderColor = UIColor.systemGray.cgColor
4223
+ }
4224
+ else if textField == txtFieldNameOnCardUpdateCardView {
4225
+ viewTxtFieldNameOnCardUpdateCardView.layer.borderColor = UIColor.systemGray.cgColor
4226
+ }
4227
+ //NEW Card View
4228
+ else if textField == txtFieldCardNumberNewCardView {
4229
+ viewtxtFieldCardNumberNewCardView.layer.borderColor = UIColor.systemGray.cgColor
4230
+ }
4231
+ else if textField == txtFieldExpiryDateNewCardView {
4232
+ viewtxtFieldExpiryDateNewCardView.layer.borderColor = UIColor.systemGray.cgColor
4233
+ }
4234
+ else if textField == txtFieldCVVNewCardView {
4235
+ viewtxtFieldCVVNewCardView.layer.borderColor = UIColor.systemGray.cgColor
4236
+ }
4237
+ else if textField == txtFieldNameOnCardNewCardView {
4238
+ viewtxtFieldNameOnCardNewCardView.layer.borderColor = UIColor.systemGray.cgColor
4239
+ }
4240
+ //Bank View
4241
+ else if textField == txtFieldAccountName {
4242
+ viewtxtFieldAccountName.layer.borderColor = UIColor.systemGray.cgColor
4243
+ }
4244
+ else if textField == txtFieldRoutingNumber {
4245
+ viewtxtFieldRoutingNumber.layer.borderColor = UIColor.systemGray.cgColor
4246
+ }
4247
+ else if textField == txtFieldAccountType {
4248
+ viewtxtFieldAccountType.layer.borderColor = UIColor.systemGray.cgColor
4249
+ }
4250
+ else if textField == txtFieldAccountNumber {
4251
+ viewtxtFieldAccountNumber.layer.borderColor = UIColor.systemGray.cgColor
4252
+ }
4253
+ //New Bank Account View
4254
+ else if textField == txtFieldAccountNameNewAccountView {
4255
+ viewtxtFieldAccountNameNewAccountView.layer.borderColor = UIColor.systemGray.cgColor
4256
+ }
4257
+ else if textField == txtFieldRoutingNumberNewAccountView {
4258
+ viewtxtFieldRoutingNumberNewAccountView.layer.borderColor = UIColor.systemGray.cgColor
4259
+ }
4260
+ else if textField == txtFieldAccountTypeNewAccountView {
4261
+ viewtxtFieldAccountTypeNewAccountView.layer.borderColor = UIColor.systemGray.cgColor
4262
+ }
4263
+ else if textField == txtFieldAccountNumberNewAccountView {
4264
+ viewtxtFieldAccountNumberNewAccountView.layer.borderColor = UIColor.systemGray.cgColor
4265
+ }
4266
+ }
4267
+
4268
+ }
4269
+