@jimrising/easymerchantsdk-react-native 1.3.9 → 1.4.2

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 (80) hide show
  1. package/.idea/caches/deviceStreaming.xml +77 -0
  2. package/README.md +140 -81
  3. package/android/.gradle/8.10/checksums/checksums.lock +0 -0
  4. package/android/.gradle/8.10/checksums/md5-checksums.bin +0 -0
  5. package/android/.gradle/8.10/checksums/sha1-checksums.bin +0 -0
  6. package/android/.gradle/8.10/fileHashes/fileHashes.lock +0 -0
  7. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  8. package/android/.gradle/8.9/checksums/md5-checksums.bin +0 -0
  9. package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
  10. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  11. package/android/.gradle/buildOutputCleanup/cache.properties +1 -1
  12. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/results.bin +1 -0
  13. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/BuildConfig.dex +0 -0
  14. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$1.dex +0 -0
  15. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule$2.dex +0 -0
  16. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkModule.dex +0 -0
  17. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/RNEasymerchantsdkPackage.dex +0 -0
  18. package/android/build/.transforms/20e1216b87bd06eaab3c9e5c68d4267a/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +0 -0
  19. package/android/build/.transforms/e9a664a11ce12edf79cd87b1e07aa243/results.bin +1 -0
  20. package/android/build/.transforms/e9a664a11ce12edf79cd87b1e07aa243/transformed/classes/classes_dex/classes.dex +0 -0
  21. package/android/build/generated/source/buildConfig/debug/com/reactlibrary/BuildConfig.java +10 -0
  22. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +7 -0
  23. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +18 -0
  24. package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +6 -0
  25. package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +1 -0
  26. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  27. package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
  28. package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
  29. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -0
  30. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
  31. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
  32. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
  33. package/android/build/intermediates/incremental/packageDebugAssets/merger.xml +2 -0
  34. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/BuildConfig.class +0 -0
  35. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  36. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  37. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  38. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
  39. package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +2 -0
  40. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +7 -0
  41. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +7 -0
  42. package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +1 -0
  43. package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +1 -0
  44. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/BuildConfig.class +0 -0
  45. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  46. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  47. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  48. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
  49. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  50. package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +1 -0
  51. package/android/build/outputs/logs/manifest-merger-debug-report.txt +17 -0
  52. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkModule$1.class.uniqueId2 +0 -0
  53. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkModule$2.class.uniqueId0 +0 -0
  54. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkModule.class.uniqueId3 +0 -0
  55. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/RNEasymerchantsdkPackage.class.uniqueId1 +0 -0
  56. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  57. package/android/build.gradle +4 -1
  58. package/android/src/main/java/com/reactlibrary/RNEasymerchantsdkModule.java +158 -36
  59. package/android/src/main/java/com/reactlibrary/RNEasymerchantsdkPackage.java +45 -13
  60. package/ios/Classes/EasyMerchantSdk.m +106 -55
  61. package/ios/Classes/EasyMerchantSdk.swift +199 -77
  62. package/ios/Classes/EasyPayViewController.swift +1 -1
  63. package/ios/CustomComponents/DatePickerHandler.swift +15 -4
  64. package/ios/EnvironmentConfig.swift +32 -30
  65. package/ios/Models/Request.swift +176 -14
  66. package/ios/Models/Result.swift +12 -5
  67. package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +855 -366
  68. package/ios/Pods/ViewControllers/BaseVC.swift +51 -36
  69. package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +1985 -178
  70. package/ios/Pods/ViewControllers/CountryListVC.swift +20 -1
  71. package/ios/Pods/ViewControllers/CustomOverlay.swift +199 -0
  72. package/ios/Pods/ViewControllers/EmailVerificationVC.swift +74 -5
  73. package/ios/Pods/ViewControllers/GrailPayVC.swift +131 -107
  74. package/ios/Pods/ViewControllers/OTPVerificationVC.swift +296 -106
  75. package/ios/Pods/ViewControllers/PaymentDoneVC.swift +35 -26
  76. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +1276 -545
  77. package/ios/Pods/ViewControllers/ThreeDSecurePaymentDoneVC.swift +607 -24
  78. package/ios/easymerchantsdk.podspec +1 -1
  79. package/ios/easymerchantsdk.storyboard +1388 -1165
  80. package/package.json +1 -1
@@ -15,7 +15,7 @@ protocol BillingInfoVCDelegate: AnyObject {
15
15
 
16
16
  class BillingInfoVC: BaseVC {
17
17
 
18
- @IBOutlet weak var viewBillingInfo: UIView!
18
+ // @IBOutlet weak var viewBillingInfo: UIView!
19
19
  @IBOutlet weak var btnPrevious: UIButton!
20
20
  @IBOutlet weak var viewCountryList: UIView!
21
21
  @IBOutlet weak var tblViewCountryList: UITableView!
@@ -51,12 +51,14 @@ class BillingInfoVC: BaseVC {
51
51
  var stateList: [[String: Any]] = []
52
52
  var cityList: [[String: Any]] = []
53
53
 
54
- var billingInfoData: [String: Any]?
54
+ // var billingInfoData: [String: Any]?
55
+ var billingInfoData: Data?
55
56
 
56
57
  var cardNumber: String?
57
58
  var expiryDate: String?
58
59
  var cvv: String?
59
60
  var nameOnCard: String?
61
+ var userEmail: String?
60
62
 
61
63
  //Banking Params
62
64
  var accountName: String?
@@ -77,6 +79,7 @@ class BillingInfoVC: BaseVC {
77
79
  var cvvText: String?
78
80
 
79
81
  var isFrom = String()
82
+ var isFromm = String()
80
83
 
81
84
  //From Regular Saved Bank Accounts
82
85
  var customerID: String?
@@ -89,13 +92,26 @@ class BillingInfoVC: BaseVC {
89
92
  var chosenPlan: String?
90
93
  var startDate: String?
91
94
 
95
+ //GrailPay Params
96
+ var grailPayAccountID: String?
97
+ var selectedGrailPayAccountType: String?
98
+ var selectedGrailPayAccountName: String?
99
+
100
+ var billingInfo: [FieldItem]?
101
+ var additionalInfo: [FieldItem]?
102
+ var visibility: FieldsVisibility?
103
+ var fieldSection: FieldSection?
104
+
105
+ var easyPayDelegate: EasyPayViewControllerDelegate?
106
+
92
107
  override func viewDidLoad() {
93
108
  super.viewDidLoad()
109
+ updateNextButtonTitle()
94
110
  btnSelectState.isHidden = true
95
111
  btnSelectCity.isHidden = true
96
112
 
97
113
  uiFinishingTouchElements()
98
- configureFieldVisibility()
114
+ // configureFieldVisibility()
99
115
  setupShadowForListViews()
100
116
 
101
117
  tblViewCountryList.delegate = self
@@ -123,37 +139,44 @@ class BillingInfoVC: BaseVC {
123
139
  tapGesture.cancelsTouchesInView = false
124
140
  self.view.addGestureRecognizer(tapGesture)
125
141
 
126
- // Print data for verification
127
- if let billingInfoData = billingInfoData {
128
- print("Billing Info Data: \(billingInfoData)")
129
-
130
- if let address = billingInfoData["address"] as? String {
131
- txtFieldAddress.text = address
132
- }
133
-
134
- if let country = billingInfoData["country"] as? String {
135
- txtFieldCountry.text = country
136
- }
137
-
138
- if let state = billingInfoData["state"] as? String {
139
- txtFieldState.text = state
140
- }
142
+ guard let billingInfoData = billingInfoData else {
143
+ print("No billing info data")
144
+ return
145
+ }
146
+
147
+ do {
148
+ let decodedFieldSection = try JSONDecoder().decode(FieldSection.self, from: billingInfoData)
149
+ self.fieldSection = decodedFieldSection // <--- Assign here!
141
150
 
142
- if let city = billingInfoData["city"] as? String {
143
- txtFieldCity.text = city
151
+ // Fill Billing Fields UI
152
+ for item in decodedFieldSection.billing {
153
+ switch item.name {
154
+ case "address":
155
+ txtFieldAddress.text = item.value
156
+ case "country":
157
+ txtFieldCountry.text = item.value
158
+ case "state":
159
+ txtFieldState.text = item.value
160
+ case "city":
161
+ txtFieldCity.text = item.value
162
+ case "postal_code":
163
+ txtFieldPostalCode.text = item.value
164
+ default:
165
+ break
166
+ }
144
167
  }
168
+ // Now that fieldSection is set, update star labels visibility
169
+ configureFieldVisibility()
145
170
 
146
- if let postalCode = billingInfoData["postal_code"] as? String {
147
- txtFieldPostalCode.text = postalCode
148
- }
171
+ } catch {
172
+ print("Failed to decode billing info data: \(error)")
149
173
  }
150
174
 
151
- print(selectedCard ?? "")
152
175
  }
153
176
 
154
177
  override func viewWillAppear(_ animated: Bool) {
155
178
  super.viewWillAppear(animated)
156
-
179
+ updateNextButtonTitle()
157
180
  uiFinishingTouchElements()
158
181
 
159
182
  keyboardObserver.animateChanges({ [self] height in
@@ -171,6 +194,38 @@ class BillingInfoVC: BaseVC {
171
194
  keyboardObserver.invalidate()
172
195
  }
173
196
 
197
+ private func updateNextButtonTitle() {
198
+ guard let request = request else { return }
199
+
200
+ if let billingInfoData = request.billingInfoData,
201
+ let fieldSection = try? JSONDecoder().decode(FieldSection.self, from: billingInfoData) {
202
+
203
+ let isAdditionalVisible = fieldSection.visibility.additional
204
+ let isBillingVisible = fieldSection.visibility.billing
205
+ let amountText = String(format: "$%.2f", request.amount ?? 0)
206
+ let submitText = request.submitButtonText ?? "Submit"
207
+
208
+ if !isAdditionalVisible {
209
+ // Only billing info is visible
210
+ btnNext.setTitle("\(submitText) (\(amountText))", for: .normal)
211
+ } else {
212
+ // Additional info is visible
213
+ let suffix = isBillingVisible ? "(Additional Info)" : ""
214
+ btnNext.setTitle("\(submitText) \(suffix)", for: .normal)
215
+ }
216
+ } else {
217
+ let amountValue = request.amount ?? 0
218
+ let amountText = String(format: "$%.2f", amountValue)
219
+ let submitText = request.submitButtonText
220
+
221
+ let defaultTitle = (submitText?.isEmpty == false)
222
+ ? "\(submitText!) (\(amountText))"
223
+ : "Pay Now (\(amountText))"
224
+
225
+ btnNext.setTitle(defaultTitle, for: .normal)
226
+ }
227
+ }
228
+
174
229
  func uiFinishingTouchElements() {
175
230
  // Set background color for the main view
176
231
  if let containerBGcolor = UserStoreSingleton.shared.container_bg_col,
@@ -199,10 +254,10 @@ class BillingInfoVC: BaseVC {
199
254
  if let secondaryFontColor = UserStoreSingleton.shared.secondary_font_col,
200
255
  let placeholderColor = UIColor(hex: secondaryFontColor) {
201
256
  lblEasyMerchant.textColor = placeholderColor
202
- viewBillingInfo.layer.borderColor = placeholderColor.cgColor
257
+ // viewBillingInfo.layer.borderColor = placeholderColor.cgColor
203
258
  }
204
259
  else {
205
- viewBillingInfo.layer.borderColor = UIColor.systemGray.cgColor
260
+ // viewBillingInfo.layer.borderColor = UIColor.systemGray.cgColor
206
261
  }
207
262
 
208
263
  if let borderRadiusString = UserStoreSingleton.shared.border_radious,
@@ -210,17 +265,17 @@ class BillingInfoVC: BaseVC {
210
265
  btnNext.layer.cornerRadius = CGFloat(borderRadius) // Set corner radius
211
266
  btnPrevious.layer.cornerRadius = CGFloat(borderRadius)
212
267
  btnPrevious.layer.borderWidth = 1
213
- viewBillingInfo.layer.cornerRadius = CGFloat(borderRadius)
214
- viewBillingInfo.layer.borderWidth = 1
268
+ // viewBillingInfo.layer.cornerRadius = CGFloat(borderRadius)
269
+ // viewBillingInfo.layer.borderWidth = 1
215
270
  } else {
216
271
  btnNext.layer.cornerRadius = 8 // Default value
217
272
  btnPrevious.layer.cornerRadius = 8
218
- viewBillingInfo.layer.borderWidth = 1
219
- viewBillingInfo.layer.cornerRadius = 8
273
+ // viewBillingInfo.layer.borderWidth = 1
274
+ // viewBillingInfo.layer.cornerRadius = 8
220
275
  }
221
276
  btnNext.layer.masksToBounds = true // Ensure the corners are clipped properly
222
277
  btnPrevious.layer.masksToBounds = true
223
- viewBillingInfo.layer.masksToBounds = true
278
+ // viewBillingInfo.layer.masksToBounds = true
224
279
 
225
280
  if let primaryFontColor = UserStoreSingleton.shared.primary_font_col,
226
281
  let uiColor = UIColor(hex: primaryFontColor) {
@@ -236,19 +291,34 @@ class BillingInfoVC: BaseVC {
236
291
  }
237
292
  }
238
293
 
294
+ private func getFieldValue(for name: String) -> String? {
295
+ return fieldSection?.billing.first(where: { $0.name == name })?.value
296
+ }
297
+
298
+ private func setFieldValue(_ name: String, to value: String?) {
299
+ guard let index = fieldSection?.billing.firstIndex(where: { $0.name == name }) else { return }
300
+ fieldSection?.billing[index].value = value ?? ""
301
+ }
302
+
239
303
  private func configureFieldVisibility() {
240
- let address = billingInfoData?["address"] as? String
241
- let country = billingInfoData?["country"] as? String
242
- let state = billingInfoData?["state"] as? String
243
- let city = billingInfoData?["city"] as? String
244
- let postalCode = billingInfoData?["postal_code"] as? String
304
+ guard let billingFields = fieldSection?.billing else {
305
+ print("No billing fields found")
306
+ return
307
+ }
308
+
309
+ func isFieldRequired(_ name: String) -> Bool {
310
+ let required = billingFields.first(where: { $0.name == name })?.required == true
311
+ print("Field \(name) required: \(required)")
312
+ return required
313
+ }
245
314
 
246
315
  DispatchQueue.main.async {
247
- self.lblStarAddressField.isHidden = (address ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
248
- self.lblStarCountryField.isHidden = (country ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
249
- self.lblStarStateField.isHidden = (state ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
250
- self.lblStarCityField.isHidden = (city ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
251
- self.lblStarPostalCodeField.isHidden = (postalCode ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
316
+ self.lblStarAddressField.isHidden = !isFieldRequired("address")
317
+ self.lblStarCountryField.isHidden = !isFieldRequired("country")
318
+ self.lblStarStateField.isHidden = !isFieldRequired("state")
319
+ self.lblStarCityField.isHidden = !isFieldRequired("city")
320
+ self.lblStarPostalCodeField.isHidden = !isFieldRequired("postal_code")
321
+ print("Star visibility set")
252
322
  self.view.layoutIfNeeded()
253
323
  }
254
324
  }
@@ -502,11 +572,13 @@ class BillingInfoVC: BaseVC {
502
572
  }
503
573
 
504
574
  func updateBillingInfoData() {
505
- billingInfoData?["address"] = txtFieldAddress.text
506
- billingInfoData?["country"] = txtFieldCountry.text
507
- billingInfoData?["state"] = txtFieldState.text
508
- billingInfoData?["city"] = txtFieldCity.text
509
- billingInfoData?["postal_code"] = txtFieldPostalCode.text
575
+ setFieldValue("address", to: txtFieldAddress.text)
576
+ setFieldValue("country", to: txtFieldCountry.text)
577
+ setFieldValue("state", to: txtFieldState.text)
578
+ setFieldValue("city", to: txtFieldCity.text)
579
+ setFieldValue("postal_code", to: txtFieldPostalCode.text)
580
+
581
+ billingInfo = fieldSection?.billing
510
582
  }
511
583
 
512
584
  @IBAction func actionBtnSelectCountry(_ sender: UIButton) {
@@ -534,190 +606,1925 @@ class BillingInfoVC: BaseVC {
534
606
  }
535
607
 
536
608
  @IBAction func actionBtnNext(_ sender: UIButton) {
609
+ guard let request = request else { return }
610
+
611
+ // MARK: - Validate Billing Fields
537
612
  if !lblStarAddressField.isHidden &&
538
613
  txtFieldAddress.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
539
614
  showAlert(title: "Missing Information", message: "Please enter your address.")
540
615
  return
541
- }
542
- else if !lblStarCountryField.isHidden &&
616
+ } else if !lblStarCountryField.isHidden &&
543
617
  txtFieldCountry.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
544
618
  showAlert(title: "Missing Information", message: "Please select your country.")
545
619
  return
546
- }
547
- else if !lblStarStateField.isHidden &&
620
+ } else if !lblStarStateField.isHidden &&
548
621
  txtFieldState.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
549
622
  showAlert(title: "Missing Information", message: "Please select your state.")
550
623
  return
551
- }
552
- else if !lblStarCityField.isHidden &&
624
+ } else if !lblStarCityField.isHidden &&
553
625
  txtFieldCity.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
554
626
  showAlert(title: "Missing Information", message: "Please select your city.")
555
627
  return
556
- }
557
- else if !lblStarPostalCodeField.isHidden &&
628
+ } else if !lblStarPostalCodeField.isHidden &&
558
629
  txtFieldPostalCode.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
559
630
  showAlert(title: "Missing Information", message: "Please enter your postal code.")
560
631
  return
561
632
  }
562
- else {
563
- let vc = easymerchantsdk.instantiateViewController(withIdentifier: "AdditionalInfoVC") as! AdditionalInfoVC
564
- // Set properties on AdditionalInfoVC
565
- vc.cardNumber = cardNumber
566
- vc.expiryDate = expiryDate
567
- vc.cvv = cvv
568
- vc.nameOnCard = nameOnCard
569
- updateBillingInfoData()
570
- vc.billingInfoData = billingInfoData
571
- // Pass the selected payment method
572
- vc.selectedPaymentMethod = selectedPaymentMethod
573
- vc.isSavedForFuture = isSavedForFuture
574
-
575
- //Banking Case
576
- vc.accountName = accountName
577
- vc.routingNumber = routingNumber
578
- vc.accountType = accountType
579
- vc.accountNumber = accountNumber
580
-
581
- vc.customerID = customerID
582
- vc.accountID = accountID
583
- vc.isFrom = isFrom
584
-
585
- vc.isSavedNewAccount = isSavedNewAccount
586
- vc.amount = Int(request.amount)
587
-
588
- vc.request = self.request
589
-
590
- vc.chosenPlan = chosenPlan
591
- vc.startDate = startDate
592
-
593
- if isFrom == "SavedCards" {
594
- vc.isFrom = "SavedCards"
595
- vc.selectedCard = selectedCard // Passing the selected card data
596
- vc.cvvText = cvvText // Passing the CVV text
597
- vc.amount = Int(request.amount)
633
+
634
+ // Update billing info
635
+ updateBillingInfoData()
636
+
637
+ guard let updatedFieldSection = fieldSection else {
638
+ print("Missing updated fieldSection")
639
+ return
640
+ }
641
+
642
+ let isAdditionalVisible = updatedFieldSection.visibility.additional
643
+
644
+ // Get updated billingInfoData after updateBillingInfoData()
645
+ guard let updatedBillingData = try? JSONEncoder().encode(fieldSection),
646
+ let updatedFieldSection = try? JSONDecoder().decode(FieldSection.self, from: updatedBillingData) else {
647
+ print("Failed to encode or decode updated billing info")
648
+ return
649
+ }
650
+
651
+ // MARK: - Card Flow
652
+ if selectedPaymentMethod == "Card" {
653
+
654
+ if isAdditionalVisible {
655
+ // ✅ Go to AdditionalInfoVC
656
+ let vc = easymerchantsdk.instantiateViewController(withIdentifier: "AdditionalInfoVC") as! AdditionalInfoVC
657
+ vc.cardNumber = cardNumber
658
+ vc.expiryDate = expiryDate
659
+ vc.cvv = cvv
660
+ vc.nameOnCard = nameOnCard
661
+ vc.userEmail = userEmail
662
+ vc.billingInfoData = updatedBillingData
663
+ vc.fieldSection = updatedFieldSection
664
+ vc.billingInfo = updatedFieldSection.billing
665
+ vc.additionalInfo = updatedFieldSection.additional
666
+ vc.visibility = updatedFieldSection.visibility
667
+ vc.selectedPaymentMethod = selectedPaymentMethod
668
+ vc.isSavedForFuture = isSavedForFuture
669
+ vc.amount = Int(request.amount ?? 0)
670
+ vc.request = request
671
+ vc.chosenPlan = chosenPlan
672
+ vc.startDate = startDate
673
+ vc.isFrom = isFrom
674
+ if isFrom == "SavedCards" {
675
+ vc.selectedCard = selectedCard
676
+ vc.cvvText = cvvText
677
+ }
678
+ else if isFrom == "AddNewCard" {
679
+ vc.isSavedNewCard = isSavedNewCard
680
+ }
681
+ navigationController?.pushViewController(vc, animated: true)
598
682
 
599
- vc.request = self.request
683
+ }
684
+ else if !isAdditionalVisible && isSavedForFuture {
685
+ let vc = easymerchantsdk.instantiateViewController(withIdentifier: "EmailVerificationVC") as! EmailVerificationVC
686
+ vc.cardNumber = cardNumber
687
+ vc.expiryDate = expiryDate
688
+ vc.cvv = cvv
689
+ vc.nameOnCard = nameOnCard
690
+ vc.userEmail = userEmail
691
+ vc.billingInfoData = updatedBillingData
692
+ vc.fieldSection = updatedFieldSection
693
+ vc.selectedPaymentMethod = selectedPaymentMethod
694
+ vc.easyPayDelegate = easyPayDelegate
695
+ vc.request = request
600
696
  vc.chosenPlan = chosenPlan
601
697
  vc.startDate = startDate
698
+ vc.billingInfo = updatedFieldSection.billing
699
+ vc.additionalInfo = updatedFieldSection.additional
700
+ vc.visibility = updatedFieldSection.visibility
701
+ vc.isSavedForFuture = true
702
+ vc.amount = amount
703
+ navigationController?.pushViewController(vc, animated: true)
602
704
  }
603
-
604
- if isFrom == "AddNewCard" {
605
- vc.isFrom = "AddNewCard"
606
- vc.amount = Int(request.amount)
607
- vc.isSavedNewCard = isSavedNewCard
608
-
609
- vc.request = self.request
705
+ else if !isAdditionalVisible && isFrom == "SavedCards" {
706
+ paymentIntentFromShowCardApi()
707
+ }
708
+ else if !isAdditionalVisible && isSavedNewCard {
709
+ if isFrom == "AddNewCard" {
710
+ if request.secureAuthentication == true {
711
+ threeDSecurePaymentAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
712
+ }
713
+ else {
714
+ paymentIntentAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
715
+ }
716
+ }
717
+ }
718
+ else {
719
+ // ✅ Skip to Payment directly
720
+ if request.secureAuthentication == true {
721
+ threeDSecurePaymentApi()
722
+ } else {
723
+ paymentIntentApi()
724
+ }
725
+ }
726
+ }
727
+ else if selectedPaymentMethod == "Bank" {
728
+ if isAdditionalVisible {
729
+ // ✅ Go to AdditionalInfoVC
730
+ let vc = easymerchantsdk.instantiateViewController(withIdentifier: "AdditionalInfoVC") as! AdditionalInfoVC
731
+ vc.userEmail = userEmail
732
+ vc.billingInfoData = updatedBillingData
733
+ vc.fieldSection = updatedFieldSection
734
+ vc.billingInfo = updatedFieldSection.billing
735
+ vc.additionalInfo = updatedFieldSection.additional
736
+ vc.visibility = updatedFieldSection.visibility
737
+ vc.selectedPaymentMethod = selectedPaymentMethod
738
+ vc.isSavedForFuture = isSavedForFuture
739
+ vc.accountName = accountName
740
+ vc.routingNumber = routingNumber
741
+ vc.accountType = accountType
742
+ vc.accountNumber = accountNumber
743
+ vc.customerID = customerID
744
+ vc.accountID = accountID
745
+ vc.isFrom = isFrom
746
+ vc.isSavedNewAccount = isSavedNewAccount
747
+ vc.amount = Int(request.amount ?? 0)
748
+ vc.request = request
610
749
  vc.chosenPlan = chosenPlan
611
750
  vc.startDate = startDate
751
+ vc.grailPayAccountID = grailPayAccountID
752
+ vc.selectedGrailPayAccountType = selectedGrailPayAccountType
753
+ vc.selectedGrailPayAccountName = selectedGrailPayAccountName
754
+ navigationController?.pushViewController(vc, animated: true)
755
+ }
756
+ else if !isAdditionalVisible && isSavedForFuture {
757
+ let vc = easymerchantsdk.instantiateViewController(withIdentifier: "EmailVerificationVC") as! EmailVerificationVC
758
+ vc.accountName = accountName
759
+ vc.routingNumber = routingNumber
760
+ vc.accountType = accountType
761
+ vc.accountNumber = accountNumber
762
+ vc.userEmail = userEmail
763
+ vc.billingInfoData = updatedBillingData
764
+ vc.fieldSection = updatedFieldSection
765
+ vc.selectedPaymentMethod = selectedPaymentMethod
766
+ vc.easyPayDelegate = easyPayDelegate
767
+ vc.request = request
768
+ vc.chosenPlan = chosenPlan
769
+ vc.startDate = startDate
770
+ vc.billingInfo = updatedFieldSection.billing
771
+ vc.additionalInfo = updatedFieldSection.additional
772
+ vc.visibility = updatedFieldSection.visibility
773
+ vc.isSavedForFuture = isSavedForFuture
774
+ vc.isSavedNewAccount = isSavedNewAccount
775
+ vc.amount = amount
776
+ vc.grailPayAccountID = grailPayAccountID
777
+ vc.selectedGrailPayAccountType = selectedGrailPayAccountType
778
+ vc.selectedGrailPayAccountName = selectedGrailPayAccountName
779
+ navigationController?.pushViewController(vc, animated: true)
780
+ }
781
+ else if isFrom == "SavedBank" {
782
+ accountChargeSavedBankAccountApi()
783
+ }
784
+ else if isFrom == "NormalBankPayWithoutSave" {
785
+ accountChargeApi()
786
+ }
787
+ else if isFrom == "AddNewAccountWithoutSave" {
788
+ accountChargeApi()
789
+ }
790
+ else if isFrom == "AddNewAccountWithSave" {
791
+ accountChargeApi(customerId: UserStoreSingleton.shared.customerId)
792
+ }
793
+ }
794
+ else if selectedPaymentMethod == "GrailPay" {
795
+ if isAdditionalVisible {
796
+ // ✅ Go to AdditionalInfoVC
797
+ let vc = easymerchantsdk.instantiateViewController(withIdentifier: "AdditionalInfoVC") as! AdditionalInfoVC
798
+ vc.billingInfoData = updatedBillingData
799
+ vc.fieldSection = updatedFieldSection
800
+ vc.billingInfo = updatedFieldSection.billing
801
+ vc.additionalInfo = updatedFieldSection.additional
802
+ vc.visibility = updatedFieldSection.visibility
803
+ vc.selectedPaymentMethod = selectedPaymentMethod
804
+ vc.isSavedForFuture = isSavedForFuture
805
+ vc.isFrom = isFrom
806
+ vc.isSavedNewAccount = isSavedNewAccount
807
+ vc.amount = Int(request.amount ?? 0)
808
+ vc.request = request
809
+ vc.chosenPlan = chosenPlan
810
+ vc.startDate = startDate
811
+ vc.grailPayAccountID = grailPayAccountID
812
+ vc.selectedGrailPayAccountType = selectedGrailPayAccountType
813
+ vc.selectedGrailPayAccountName = selectedGrailPayAccountName
814
+ navigationController?.pushViewController(vc, animated: true)
815
+ }
816
+ else if !isAdditionalVisible && isSavedForFuture {
817
+ let vc = easymerchantsdk.instantiateViewController(withIdentifier: "EmailVerificationVC") as! EmailVerificationVC
818
+ vc.billingInfoData = updatedBillingData
819
+ vc.fieldSection = updatedFieldSection
820
+ vc.billingInfo = updatedFieldSection.billing
821
+ vc.additionalInfo = updatedFieldSection.additional
822
+ vc.visibility = updatedFieldSection.visibility
823
+ vc.selectedPaymentMethod = selectedPaymentMethod
824
+ vc.isSavedForFuture = isSavedForFuture
825
+ vc.isFrom = isFrom
826
+ vc.isSavedNewAccount = isSavedNewAccount
827
+ vc.amount = Int(request.amount ?? 0)
828
+ vc.request = request
829
+ vc.chosenPlan = chosenPlan
830
+ vc.startDate = startDate
831
+ vc.grailPayAccountID = grailPayAccountID
832
+ vc.selectedGrailPayAccountType = selectedGrailPayAccountType
833
+ vc.selectedGrailPayAccountName = selectedGrailPayAccountName
834
+ navigationController?.pushViewController(vc, animated: true)
835
+ }
836
+ else {
837
+ grailPayAccountChargeApi()
838
+ }
839
+ }
840
+ else if selectedPaymentMethod == "NewGrailPayAccount" {
841
+ if isAdditionalVisible {
842
+ // ✅ Go to AdditionalInfoVC
843
+ let vc = easymerchantsdk.instantiateViewController(withIdentifier: "AdditionalInfoVC") as! AdditionalInfoVC
844
+ vc.billingInfoData = updatedBillingData
845
+ vc.fieldSection = updatedFieldSection
846
+ vc.billingInfo = updatedFieldSection.billing
847
+ vc.additionalInfo = updatedFieldSection.additional
848
+ vc.visibility = updatedFieldSection.visibility
849
+ vc.selectedPaymentMethod = selectedPaymentMethod
850
+ vc.isSavedForFuture = isSavedForFuture
851
+ vc.isFrom = isFrom
852
+ vc.isSavedNewAccount = isSavedNewAccount
853
+ vc.amount = Int(request.amount ?? 0)
854
+ vc.request = request
855
+ vc.chosenPlan = chosenPlan
856
+ vc.startDate = startDate
857
+ vc.grailPayAccountID = grailPayAccountID
858
+ vc.selectedGrailPayAccountType = selectedGrailPayAccountType
859
+ vc.selectedGrailPayAccountName = selectedGrailPayAccountName
860
+ navigationController?.pushViewController(vc, animated: true)
861
+ }
862
+ else if !isAdditionalVisible && isSavedForFuture {
863
+ let vc = easymerchantsdk.instantiateViewController(withIdentifier: "EmailVerificationVC") as! EmailVerificationVC
864
+ vc.billingInfoData = updatedBillingData
865
+ vc.fieldSection = updatedFieldSection
866
+ vc.billingInfo = updatedFieldSection.billing
867
+ vc.additionalInfo = updatedFieldSection.additional
868
+ vc.visibility = updatedFieldSection.visibility
869
+ vc.selectedPaymentMethod = selectedPaymentMethod
870
+ vc.isSavedForFuture = isSavedForFuture
871
+ vc.isFrom = isFrom
872
+ vc.isSavedNewAccount = isSavedNewAccount
873
+ vc.amount = Int(request.amount ?? 0)
874
+ vc.request = request
875
+ vc.chosenPlan = chosenPlan
876
+ vc.startDate = startDate
877
+ vc.grailPayAccountID = grailPayAccountID
878
+ vc.selectedGrailPayAccountType = selectedGrailPayAccountType
879
+ vc.selectedGrailPayAccountName = selectedGrailPayAccountName
880
+ navigationController?.pushViewController(vc, animated: true)
881
+ }
882
+ else {
883
+ grailPayAccountChargeApi()
612
884
  }
613
-
614
- self.navigationController?.pushViewController(vc, animated: true)
615
885
  }
616
886
  }
617
887
 
618
- }
619
-
620
- //MARK: - Table View
621
- @available(iOS 16.0, *)
622
- extension BillingInfoVC: UITableViewDelegate, UITableViewDataSource {
623
- func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
624
- if tableView == tblViewCountryList {
625
- return countryList.count
888
+ // MARK: - Credit Card Charge Api
889
+ func paymentIntentApi() {
890
+ showLoadingIndicator()
891
+
892
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
893
+
894
+ guard let serviceURL = URL(string: fullURL) else {
895
+ print("Invalid URL")
896
+ hideLoadingIndicator()
897
+ return
626
898
  }
627
- else if tableView == tblViewStateList {
628
- return stateList.count
899
+
900
+ var urlRequest = URLRequest(url: serviceURL)
901
+ urlRequest.httpMethod = "POST"
902
+ urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
903
+
904
+ let token = UserStoreSingleton.shared.clientToken
905
+ print("Setting clientToken header: \(token ?? "None")")
906
+ urlRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
907
+
908
+ var params: [String: Any] = [
909
+ "name": nameOnCard ?? "",
910
+ "email": userEmail ?? "",
911
+ "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
912
+ "cardholder_name": nameOnCard ?? "",
913
+ "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
914
+ "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
915
+ "cvc": cvv ?? "",
916
+ "currency": "usd",
917
+ "description": "Hosted payment checkout"
918
+ ]
919
+
920
+ // Conditionally add billing info
921
+ if let visibility = visibility, visibility.billing == true,
922
+ let billing = billingInfo, !billing.isEmpty {
923
+
924
+ var billingInfoDict: [String: Any] = [:]
925
+ for item in billing {
926
+ billingInfoDict[item.name] = item.value
927
+ }
928
+
929
+ params["address"] = billingInfoDict["address"] as? String ?? ""
930
+ params["country"] = billingInfoDict["country"] as? String ?? ""
931
+ params["state"] = billingInfoDict["state"] as? String ?? ""
932
+ params["city"] = billingInfoDict["city"] as? String ?? ""
933
+ params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
629
934
  }
630
- else if tableView == tblViewCityList {
631
- return cityList.count
935
+
936
+ // Add these if recurring is enabled
937
+ if let req = request, req.is_recurring == true {
938
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
939
+ // Only send start_date if type is .custom and field is not empty
940
+ if let startDateText = startDate, !startDateText.isEmpty {
941
+ let inputFormatter = DateFormatter()
942
+ inputFormatter.dateFormat = "dd/MM/yyyy"
943
+
944
+ let outputFormatter = DateFormatter()
945
+ outputFormatter.dateFormat = "MM/dd/yyyy"
946
+
947
+ if let date = inputFormatter.date(from: startDateText) {
948
+ let apiFormattedDate = outputFormatter.string(from: date)
949
+ params["start_date"] = apiFormattedDate
950
+ } else {
951
+ print("Invalid date format in startDateText")
952
+ }
953
+ }
954
+ }
955
+
956
+ params["interval"] = chosenPlan?.lowercased()
632
957
  }
633
- else {
634
- return 0
958
+
959
+ print(params)
960
+
961
+ do {
962
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
963
+ urlRequest.httpBody = jsonData
964
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
965
+ print("JSON Payload: \(jsonString)")
966
+ }
967
+ } catch let error {
968
+ print("Error creating JSON data: \(error)")
969
+ hideLoadingIndicator()
970
+ return
971
+ }
972
+
973
+ let session = URLSession.shared
974
+ let task = session.dataTask(with: urlRequest) { (serviceData, serviceResponse, error) in
975
+
976
+ DispatchQueue.main.async {
977
+ self.hideLoadingIndicator()
978
+ }
979
+
980
+ if let error = error {
981
+ print("Error: \(error.localizedDescription)")
982
+ self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
983
+ return
984
+ }
985
+
986
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
987
+ print("Invalid response")
988
+ self.presentPaymentErrorVC(errorMessage: "Invalid response from server.")
989
+ return
990
+ }
991
+
992
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
993
+ if let data = serviceData {
994
+ do {
995
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
996
+ print("Response Data: \(responseObject)")
997
+
998
+ if let status = responseObject["status"] as? Int, status == 0 {
999
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error occurred."
1000
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
1001
+ } else {
1002
+ DispatchQueue.main.async {
1003
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
1004
+ paymentDoneVC.chargeData = responseObject
1005
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
1006
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1007
+ // Pass billing and additional info
1008
+ // Conditionally pass raw FieldItem array
1009
+ paymentDoneVC.visibility = self.visibility
1010
+
1011
+ if self.visibility?.billing == true {
1012
+ paymentDoneVC.billingInfoData = self.billingInfo
1013
+ var billingDict: [String: Any] = [:]
1014
+ self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
1015
+ paymentDoneVC.billingInfo = billingDict
1016
+ }
1017
+
1018
+ if self.visibility?.additional == true {
1019
+ paymentDoneVC.additionalInfoData = self.additionalInfo
1020
+ var additionalDict: [String: Any] = [:]
1021
+ self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
1022
+ paymentDoneVC.additionalInfo = additionalDict
1023
+ }
1024
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1025
+ }
1026
+ }
1027
+ }
1028
+ } else {
1029
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1030
+ }
1031
+ } catch let jsonError {
1032
+ self.presentPaymentErrorVC(errorMessage: "Error parsing response: \(jsonError.localizedDescription)")
1033
+ }
1034
+ } else {
1035
+ self.presentPaymentErrorVC(errorMessage: "No data received from server.")
1036
+ }
1037
+ } else {
1038
+ if let data = serviceData,
1039
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1040
+ let message = responseObj["message"] as? String {
1041
+ self.presentPaymentErrorVC(errorMessage: message)
1042
+ } else {
1043
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1044
+ }
1045
+ }
635
1046
  }
1047
+ task.resume()
636
1048
  }
637
1049
 
638
- func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
639
- if tableView == tblViewCountryList {
640
- let cell = tableView.dequeueReusableCell(withIdentifier: "CountryListTVC") as! CountryListTVC
641
- let country = countryList[indexPath.row]
642
- cell.lblCountryName.text = country["name"] as? String
643
- return cell
644
- } else if tableView == tblViewStateList {
645
- let cell = tableView.dequeueReusableCell(withIdentifier: "StateListTVC") as! StateListTVC
646
- let state = stateList[indexPath.row]
647
- cell.lblStateName.text = state["name"] as? String
648
- return cell
649
- } else if tableView == tblViewCityList {
650
- let cell = tableView.dequeueReusableCell(withIdentifier: "CityListTVC") as! CityListTVC
651
- let city = cityList[indexPath.row]
652
- cell.lblCityName.text = city["city_name"] as? String
653
- return cell
654
- } else {
655
- return UITableViewCell()
1050
+ func presentPaymentErrorVC(errorMessage: String) {
1051
+ DispatchQueue.main.async {
1052
+ if let paymentErrorVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentErrorVC") as? PaymentErrorVC {
1053
+ paymentErrorVC.errorMessage = errorMessage
1054
+ paymentErrorVC.easyPayDelegate = self.easyPayDelegate // Pass the reference here
1055
+ self.navigationController?.pushViewController(paymentErrorVC, animated: true)
1056
+ }
656
1057
  }
657
1058
  }
658
1059
 
659
- func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
660
- if tableView == tblViewCountryList {
661
- let selectedCountry = countryList[indexPath.row]["name"] as? String
662
- txtFieldCountry.text = selectedCountry
663
- viewCountryList.isHidden = true
1060
+ //MARK: - 3DSecure
1061
+ // MARK: - Credit Card Charge Api If Billing info is not nil and Without Login.
1062
+ func threeDSecurePaymentApi() {
1063
+ showLoadingIndicator()
1064
+
1065
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecure.path()
1066
+
1067
+ guard let serviceURL = URL(string: fullURL) else {
1068
+ print("Invalid URL")
1069
+ hideLoadingIndicator()
1070
+ return
1071
+ }
1072
+
1073
+ var uRLRequest = URLRequest(url: serviceURL)
1074
+ uRLRequest.httpMethod = "POST"
1075
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1076
+
1077
+ let token = UserStoreSingleton.shared.clientToken
1078
+ print("Setting clientToken header: \(token ?? "None")")
1079
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1080
+
1081
+ // Add API headers
1082
+ if let apiKey = EnvironmentConfig.apiKey,
1083
+ let apiSecret = EnvironmentConfig.apiSecret {
1084
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
1085
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
1086
+ }
1087
+
1088
+ var params: [String: Any] = [
1089
+ "name": nameOnCard ?? "",
1090
+ "email": userEmail ?? "",
1091
+ "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
1092
+ "cardholder_name": nameOnCard ?? "",
1093
+ "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
1094
+ "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
1095
+ "cvc": cvv ?? "",
1096
+ "currency": "usd",
1097
+ "tokenize": request.tokenOnly ?? false
1098
+ ]
1099
+
1100
+ // Conditionally add billing info
1101
+ if let visibility = visibility, visibility.billing == true,
1102
+ let billing = billingInfo, !billing.isEmpty {
664
1103
 
665
- // Fetch states for the selected country
666
- if let country = selectedCountry {
667
- getStateListApi(for: country)
1104
+ var billingInfoDict: [String: Any] = [:]
1105
+ for item in billing {
1106
+ billingInfoDict[item.name] = item.value
668
1107
  }
669
- } else if tableView == tblViewStateList {
670
- let selectedState = stateList[indexPath.row]["name"] as? String
671
- txtFieldState.text = selectedState
672
- viewStateList.isHidden = true
673
1108
 
674
- // Fetch cities for the selected state and country
675
- if let state = selectedState, let country = txtFieldCountry.text {
676
- getCityListListApi(for: country, state: state)
1109
+ params["address"] = billingInfoDict["address"] as? String ?? ""
1110
+ params["country"] = billingInfoDict["country"] as? String ?? ""
1111
+ params["state"] = billingInfoDict["state"] as? String ?? ""
1112
+ params["city"] = billingInfoDict["city"] as? String ?? ""
1113
+ params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
1114
+ }
1115
+
1116
+ // Set default description if additional info is not visible
1117
+ if let visibility = visibility, visibility.additional == false {
1118
+ params["description"] = "Hosted payment checkout"
1119
+ }
1120
+
1121
+ // Add these if recurring is enabled
1122
+ if let req = request, req.is_recurring == true {
1123
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1124
+ // Only send start_date if type is .custom and field is not empty
1125
+ if let startDateText = startDate, !startDateText.isEmpty {
1126
+ let inputFormatter = DateFormatter()
1127
+ inputFormatter.dateFormat = "dd/MM/yyyy"
1128
+
1129
+ let outputFormatter = DateFormatter()
1130
+ outputFormatter.dateFormat = "MM/dd/yyyy"
1131
+
1132
+ if let date = inputFormatter.date(from: startDateText) {
1133
+ let apiFormattedDate = outputFormatter.string(from: date)
1134
+ params["start_date"] = apiFormattedDate
1135
+ } else {
1136
+ print("Invalid date format in startDateText")
1137
+ }
1138
+ }
677
1139
  }
678
- } else if tableView == tblViewCityList {
679
- let selectedCity = cityList[indexPath.row]["city_name"] as? String
680
- txtFieldCity.text = selectedCity
681
- viewCityList.isHidden = true
682
1140
 
683
- // Fetch the city list again based on selected state & country
684
- if let state = txtFieldState.text, let country = txtFieldCountry.text {
685
- getCityListListApi(for: country, state: state)
1141
+ params["interval"] = chosenPlan?.lowercased()
1142
+ }
1143
+
1144
+ print(params)
1145
+
1146
+ do {
1147
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1148
+ uRLRequest.httpBody = jsonData
1149
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
1150
+ print("JSON Payload: \(jsonString)")
686
1151
  }
1152
+ } catch let error {
1153
+ print("Error creating JSON data: \(error)")
1154
+ hideLoadingIndicator()
1155
+ return
687
1156
  }
688
1157
 
689
- updateBillingInfoData()
690
- }
691
-
692
- func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
693
- return 50
694
- }
695
-
696
- }
697
-
698
- @available(iOS 16.0, *)
699
- extension BillingInfoVC: UITextFieldDelegate {
700
- func textFieldShouldReturn(_ textField: UITextField) -> Bool {
701
- textField.resignFirstResponder() // Dismiss the keyboard
702
- return true
703
- }
704
-
705
- // Update billingInfoData when user finishes editing a field
706
- func textFieldDidEndEditing(_ textField: UITextField) {
1158
+ let session = URLSession.shared
1159
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1160
+
1161
+ DispatchQueue.main.async {
1162
+ self.hideLoadingIndicator() // Stop loader when response is received
1163
+ }
1164
+
1165
+ if let error = error {
1166
+ print("Error: \(error.localizedDescription)")
1167
+ return
1168
+ }
1169
+
1170
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1171
+ print("Invalid response")
1172
+ return
1173
+ }
1174
+
1175
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1176
+ if let data = serviceData {
1177
+ do {
1178
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1179
+ print("Response Data: \(responseObject)")
1180
+
1181
+ // Check if status is 0 and handle the error
1182
+ if let status = responseObject["status"] as? Int, status == 0 {
1183
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1184
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
1185
+ } else {
1186
+ DispatchQueue.main.async {
1187
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "ThreeDSecurePaymentDoneVC") as? ThreeDSecurePaymentDoneVC {
1188
+
1189
+ let urlString = responseObject["redirect_url"] as? String ?? responseObject["location_url"] as? String ?? ""
1190
+ paymentDoneVC.redirectURL = urlString
1191
+ paymentDoneVC.chargeData = responseObject
1192
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1193
+ // Pass billing and additional info
1194
+ // Conditionally pass raw FieldItem array
1195
+ paymentDoneVC.visibility = self.visibility
1196
+ paymentDoneVC.amount = self.amount
1197
+
1198
+ if self.visibility?.billing == true {
1199
+ paymentDoneVC.billingInfoData = self.billingInfo
1200
+ var billingDict: [String: Any] = [:]
1201
+ self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
1202
+ paymentDoneVC.billingInfo = billingDict
1203
+ }
1204
+
1205
+ if self.visibility?.additional == true {
1206
+ paymentDoneVC.additionalInfoData = self.additionalInfo
1207
+ var additionalDict: [String: Any] = [:]
1208
+ self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
1209
+ paymentDoneVC.additionalInfo = additionalDict
1210
+ }
1211
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1212
+ }
1213
+ }
1214
+ }
1215
+ } else {
1216
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1217
+ }
1218
+ } catch let jsonError {
1219
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1220
+ }
1221
+ } else {
1222
+ self.presentPaymentErrorVC(errorMessage: "No data received")
1223
+ }
1224
+ } else {
1225
+ if let data = serviceData,
1226
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1227
+ let message = responseObj["message"] as? String {
1228
+ self.presentPaymentErrorVC(errorMessage: message)
1229
+ } else {
1230
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1231
+ }
1232
+ }
1233
+ }
1234
+ task.resume()
1235
+ }
1236
+
1237
+ // MARK: - Credit Card Charge Api If Billing info is not nil With Login from Add New Card.
1238
+ func threeDSecurePaymentAddNewCardApi(customerId: String?) {
1239
+ showLoadingIndicator()
1240
+
1241
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecure.path()
1242
+
1243
+ guard let serviceURL = URL(string: fullURL) else {
1244
+ print("Invalid URL")
1245
+ hideLoadingIndicator()
1246
+ return
1247
+ }
1248
+
1249
+ var uRLRequest = URLRequest(url: serviceURL)
1250
+ uRLRequest.httpMethod = "POST"
1251
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1252
+
1253
+ let token = UserStoreSingleton.shared.clientToken
1254
+ print("Setting clientToken header: \(token ?? "None")")
1255
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1256
+
1257
+ // Add API headers
1258
+ if let apiKey = EnvironmentConfig.apiKey,
1259
+ let apiSecret = EnvironmentConfig.apiSecret {
1260
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
1261
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
1262
+ }
1263
+
1264
+ var params: [String: Any] = [
1265
+ "name": nameOnCard ?? "",
1266
+ "email": userEmail ?? "",
1267
+ "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
1268
+ "cardholder_name": nameOnCard ?? "",
1269
+ "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
1270
+ "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
1271
+ "cvc": cvv ?? "",
1272
+ "description": "Hosted payment checkout",
1273
+ "currency": "usd",
1274
+ "tokenize": request.tokenOnly ?? false,
1275
+ "save_card": isSavedNewCard ? 1 : 0,
1276
+ "customer_id": customerId ?? ""
1277
+ ]
1278
+
1279
+ // Add is_default parameter if save_card is 1
1280
+ if isSavedNewCard {
1281
+ params["is_default"] = "1"
1282
+ }
1283
+
1284
+ // Conditionally add billing info
1285
+ if let visibility = visibility, visibility.billing == true,
1286
+ let billing = billingInfo, !billing.isEmpty {
1287
+
1288
+ var billingInfoDict: [String: Any] = [:]
1289
+ for item in billing {
1290
+ billingInfoDict[item.name] = item.value
1291
+ }
1292
+
1293
+ params["address"] = billingInfoDict["address"] as? String ?? ""
1294
+ params["country"] = billingInfoDict["country"] as? String ?? ""
1295
+ params["state"] = billingInfoDict["state"] as? String ?? ""
1296
+ params["city"] = billingInfoDict["city"] as? String ?? ""
1297
+ params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
1298
+ }
1299
+
1300
+ // Add these if recurring is enabled
1301
+ if let req = request, req.is_recurring == true {
1302
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1303
+ // Only send start_date if type is .custom and field is not empty
1304
+ if let startDateText = startDate, !startDateText.isEmpty {
1305
+ let inputFormatter = DateFormatter()
1306
+ inputFormatter.dateFormat = "dd/MM/yyyy"
1307
+
1308
+ let outputFormatter = DateFormatter()
1309
+ outputFormatter.dateFormat = "MM/dd/yyyy"
1310
+
1311
+ if let date = inputFormatter.date(from: startDateText) {
1312
+ let apiFormattedDate = outputFormatter.string(from: date)
1313
+ params["start_date"] = apiFormattedDate
1314
+ } else {
1315
+ print("Invalid date format in startDateText")
1316
+ }
1317
+ }
1318
+ }
1319
+
1320
+ params["interval"] = chosenPlan?.lowercased()
1321
+ }
1322
+
1323
+ do {
1324
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1325
+ uRLRequest.httpBody = jsonData
1326
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
1327
+ print("JSON Payload: \(jsonString)")
1328
+ }
1329
+ } catch let error {
1330
+ print("Error creating JSON data: \(error)")
1331
+ hideLoadingIndicator()
1332
+ return
1333
+ }
1334
+
1335
+ let session = URLSession.shared
1336
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1337
+
1338
+ DispatchQueue.main.async {
1339
+ self.hideLoadingIndicator() // Stop loader when response is received
1340
+ }
1341
+
1342
+ if let error = error {
1343
+ print("Error: \(error.localizedDescription)")
1344
+ return
1345
+ }
1346
+
1347
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1348
+ print("Invalid response")
1349
+ return
1350
+ }
1351
+
1352
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1353
+ if let data = serviceData {
1354
+ do {
1355
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1356
+ print("Response Data: \(responseObject)")
1357
+
1358
+ // Check if status is 0 and handle the error
1359
+ if let status = responseObject["status"] as? Int, status == 0 {
1360
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1361
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
1362
+ } else {
1363
+ DispatchQueue.main.async {
1364
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "ThreeDSecurePaymentDoneVC") as? ThreeDSecurePaymentDoneVC {
1365
+
1366
+ let urlString = responseObject["redirect_url"] as? String ?? responseObject["location_url"] as? String ?? ""
1367
+ paymentDoneVC.redirectURL = urlString
1368
+ paymentDoneVC.chargeData = responseObject
1369
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1370
+ // Pass billing and additional info
1371
+ // Conditionally pass raw FieldItem array
1372
+ paymentDoneVC.visibility = self.visibility
1373
+ paymentDoneVC.amount = self.amount
1374
+
1375
+ if self.visibility?.billing == true {
1376
+ paymentDoneVC.billingInfoData = self.billingInfo
1377
+ var billingDict: [String: Any] = [:]
1378
+ self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
1379
+ paymentDoneVC.billingInfo = billingDict
1380
+ }
1381
+
1382
+ if self.visibility?.additional == true {
1383
+ paymentDoneVC.additionalInfoData = self.additionalInfo
1384
+ var additionalDict: [String: Any] = [:]
1385
+ self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
1386
+ paymentDoneVC.additionalInfo = additionalDict
1387
+ }
1388
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1389
+ }
1390
+ }
1391
+ }
1392
+ } else {
1393
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1394
+ }
1395
+ } catch let jsonError {
1396
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1397
+ }
1398
+ } else {
1399
+ self.presentPaymentErrorVC(errorMessage: "No data received")
1400
+ }
1401
+ } else {
1402
+ if let data = serviceData,
1403
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1404
+ let message = responseObj["message"] as? String {
1405
+ self.presentPaymentErrorVC(errorMessage: message)
1406
+ } else {
1407
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1408
+ }
1409
+ }
1410
+ }
1411
+ task.resume()
1412
+ }
1413
+
1414
+ //MARK: - Credit Card Charge Api from Add new card from saved cards.
1415
+ func paymentIntentAddNewCardApi(customerId: String?) {
1416
+ showLoadingIndicator()
1417
+
1418
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
1419
+
1420
+ guard let serviceURL = URL(string: fullURL) else {
1421
+ print("Invalid URL")
1422
+ hideLoadingIndicator()
1423
+ return
1424
+ }
1425
+
1426
+ var uRLRequest = URLRequest(url: serviceURL)
1427
+ uRLRequest.httpMethod = "POST"
1428
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1429
+
1430
+ let token = UserStoreSingleton.shared.clientToken
1431
+ print("Setting clientToken header: \(token ?? "None")")
1432
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1433
+
1434
+ let emailPrefix = UserStoreSingleton.shared.verificationEmail?.components(separatedBy: "@").first ?? ""
1435
+
1436
+ var params: [String: Any] = [
1437
+ "name": nameOnCard ?? "",
1438
+ "email": userEmail ?? "",
1439
+ "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
1440
+ "cardholder_name": nameOnCard ?? "",
1441
+ "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
1442
+ "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
1443
+ "cvc": cvv ?? "",
1444
+ "currency": "usd",
1445
+ "payment_method": selectedPaymentMethod ?? "",
1446
+ "save_card": isSavedNewCard ? 1 : 0
1447
+ ]
1448
+
1449
+ // Add is_default parameter if save_card is 1
1450
+ if isSavedNewCard {
1451
+ params["is_default"] = "1"
1452
+ }
1453
+
1454
+ // Conditionally add billing info
1455
+ if let visibility = visibility, visibility.billing == true,
1456
+ let billing = billingInfo, !billing.isEmpty {
1457
+
1458
+ var billingInfoDict: [String: Any] = [:]
1459
+ for item in billing {
1460
+ billingInfoDict[item.name] = item.value
1461
+ }
1462
+
1463
+ params["address"] = billingInfoDict["address"] as? String ?? ""
1464
+ params["country"] = billingInfoDict["country"] as? String ?? ""
1465
+ params["state"] = billingInfoDict["state"] as? String ?? ""
1466
+ params["city"] = billingInfoDict["city"] as? String ?? ""
1467
+ params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
1468
+ }
1469
+
1470
+ // Set default description if additional info is not visible
1471
+ if let visibility = visibility, visibility.additional == false {
1472
+ params["description"] = "Hosted payment checkout"
1473
+ }
1474
+
1475
+ // Add these if recurring is enabled
1476
+ if let req = request, req.is_recurring == true {
1477
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1478
+ // Only send start_date if type is .custom and field is not empty
1479
+ if let startDateText = startDate, !startDateText.isEmpty {
1480
+ let inputFormatter = DateFormatter()
1481
+ inputFormatter.dateFormat = "dd/MM/yyyy"
1482
+
1483
+ let outputFormatter = DateFormatter()
1484
+ outputFormatter.dateFormat = "MM/dd/yyyy"
1485
+
1486
+ if let date = inputFormatter.date(from: startDateText) {
1487
+ let apiFormattedDate = outputFormatter.string(from: date)
1488
+ params["start_date"] = apiFormattedDate
1489
+ } else {
1490
+ print("Invalid date format in startDateText")
1491
+ }
1492
+ }
1493
+ }
1494
+
1495
+ params["interval"] = chosenPlan?.lowercased()
1496
+ }
1497
+
1498
+ if let customerId = customerId {
1499
+ params["customer"] = customerId
1500
+ params["customer_id"] = customerId
1501
+ } else {
1502
+ params["username"] = emailPrefix
1503
+ params["email"] = UserStoreSingleton.shared.verificationEmail
1504
+ }
1505
+
1506
+ print(params)
1507
+
1508
+ do {
1509
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1510
+ uRLRequest.httpBody = jsonData
1511
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
1512
+ print("JSON Payload: \(jsonString)")
1513
+ }
1514
+ } catch let error {
1515
+ print("Error creating JSON data: \(error)")
1516
+ hideLoadingIndicator()
1517
+ return
1518
+ }
1519
+
1520
+ let session = URLSession.shared
1521
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1522
+
1523
+ DispatchQueue.main.async {
1524
+ self.hideLoadingIndicator() // Stop loader when response is received
1525
+ }
1526
+
1527
+ if let error = error {
1528
+ self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
1529
+ return
1530
+ }
1531
+
1532
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1533
+ self.presentPaymentErrorVC(errorMessage: "Invalid response")
1534
+ return
1535
+ }
1536
+
1537
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1538
+ if let data = serviceData {
1539
+ do {
1540
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1541
+ print("Response Data: \(responseObject)")
1542
+
1543
+ // Check if status is 0 and handle the error
1544
+ if let status = responseObject["status"] as? Int, status == 0 {
1545
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1546
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
1547
+ } else {
1548
+ DispatchQueue.main.async {
1549
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
1550
+ paymentDoneVC.chargeData = responseObject
1551
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
1552
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1553
+ // Pass billing and additional info
1554
+ // Conditionally pass raw FieldItem array
1555
+ paymentDoneVC.visibility = self.visibility
1556
+
1557
+ if self.visibility?.billing == true {
1558
+ paymentDoneVC.billingInfoData = self.billingInfo
1559
+ var billingDict: [String: Any] = [:]
1560
+ self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
1561
+ paymentDoneVC.billingInfo = billingDict
1562
+ }
1563
+
1564
+ if self.visibility?.additional == true {
1565
+ paymentDoneVC.additionalInfoData = self.additionalInfo
1566
+ var additionalDict: [String: Any] = [:]
1567
+ self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
1568
+ paymentDoneVC.additionalInfo = additionalDict
1569
+ }
1570
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1571
+ }
1572
+ }
1573
+ }
1574
+ } else {
1575
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1576
+ }
1577
+ } catch let jsonError {
1578
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1579
+ }
1580
+ } else {
1581
+ self.presentPaymentErrorVC(errorMessage: "No data received")
1582
+ }
1583
+ } else {
1584
+ if let data = serviceData,
1585
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1586
+ let message = responseObj["message"] as? String {
1587
+ self.presentPaymentErrorVC(errorMessage: message)
1588
+ } else {
1589
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1590
+ }
1591
+ }
1592
+ }
1593
+ task.resume()
1594
+ }
1595
+
1596
+ //MARK: - Credit Card Charge Api from Saved cards
1597
+ func paymentIntentFromShowCardApi() {
1598
+ showLoadingIndicator()
1599
+
1600
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
1601
+
1602
+ guard let serviceURL = URL(string: fullURL) else {
1603
+ print("Invalid URL")
1604
+ hideLoadingIndicator()
1605
+ return
1606
+ }
1607
+
1608
+ var uRLRequest = URLRequest(url: serviceURL)
1609
+ uRLRequest.httpMethod = "POST"
1610
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1611
+
1612
+ let token = UserStoreSingleton.shared.clientToken
1613
+ print("Setting clientToken header: \(token ?? "None")")
1614
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1615
+
1616
+ var params: [String: Any] = [
1617
+ "description": "Hosted payment checkout",
1618
+ "currency": "usd",
1619
+ "payment_method": "card",
1620
+ "save_card": 0,
1621
+ "customer" : selectedCard?.customerId ?? "",
1622
+ "customer_id" : selectedCard?.customerId ?? "",
1623
+ "card_id" : selectedCard?.cardId ?? "",
1624
+ "cvc" : cvvText ?? ""
1625
+ ]
1626
+
1627
+ // Conditionally add billing info
1628
+ if let visibility = visibility, visibility.billing == true,
1629
+ let billing = billingInfo, !billing.isEmpty {
1630
+
1631
+ var billingInfoDict: [String: Any] = [:]
1632
+ for item in billing {
1633
+ billingInfoDict[item.name] = item.value
1634
+ }
1635
+
1636
+ params["address"] = billingInfoDict["address"] as? String ?? ""
1637
+ params["country"] = billingInfoDict["country"] as? String ?? ""
1638
+ params["state"] = billingInfoDict["state"] as? String ?? ""
1639
+ params["city"] = billingInfoDict["city"] as? String ?? ""
1640
+ params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
1641
+ }
1642
+
1643
+ // Conditionally add additional info
1644
+ if let visibility = visibility, visibility.additional == true,
1645
+ let additional = additionalInfo, !additional.isEmpty {
1646
+
1647
+ var additionalInfoDict: [String: Any] = [:]
1648
+ for item in additional {
1649
+ additionalInfoDict[item.name] = item.value
1650
+ }
1651
+
1652
+ params["description"] = additionalInfoDict["description"] as? String ?? ""
1653
+ params["phone_number"] = additionalInfoDict["phone_number"] as? String ?? ""
1654
+ params["name"] = additionalInfoDict["name"] as? String ?? ""
1655
+ params["email"] = additionalInfoDict["email"] as? String ?? ""
1656
+ }
1657
+
1658
+ // Set default description if additional info is not visible
1659
+ if let visibility = visibility, visibility.additional == false {
1660
+ params["description"] = "Hosted payment checkout"
1661
+ }
1662
+
1663
+ // Add these if recurring is enabled
1664
+ if let req = request, req.is_recurring == true {
1665
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1666
+ // Only send start_date if type is .custom and field is not empty
1667
+ if let startDateText = startDate, !startDateText.isEmpty {
1668
+ let inputFormatter = DateFormatter()
1669
+ inputFormatter.dateFormat = "dd/MM/yyyy"
1670
+
1671
+ let outputFormatter = DateFormatter()
1672
+ outputFormatter.dateFormat = "MM/dd/yyyy"
1673
+
1674
+ if let date = inputFormatter.date(from: startDateText) {
1675
+ let apiFormattedDate = outputFormatter.string(from: date)
1676
+ params["start_date"] = apiFormattedDate
1677
+ } else {
1678
+ print("Invalid date format in startDateText")
1679
+ }
1680
+ }
1681
+ }
1682
+
1683
+ params["interval"] = chosenPlan?.lowercased()
1684
+ }
1685
+
1686
+ do {
1687
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1688
+ uRLRequest.httpBody = jsonData
1689
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
1690
+ print("JSON Payload: \(jsonString)")
1691
+ }
1692
+ } catch let error {
1693
+ print("Error creating JSON data: \(error)")
1694
+ hideLoadingIndicator()
1695
+ return
1696
+ }
1697
+
1698
+ let session = URLSession.shared
1699
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1700
+
1701
+ DispatchQueue.main.async {
1702
+ self.hideLoadingIndicator() // Stop loader when response is received
1703
+ }
1704
+
1705
+ if let error = error {
1706
+ print("Error: \(error.localizedDescription)")
1707
+ return
1708
+ }
1709
+
1710
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1711
+ print("Invalid response")
1712
+ return
1713
+ }
1714
+
1715
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1716
+ if let data = serviceData {
1717
+ do {
1718
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1719
+ print("Response Data: \(responseObject)")
1720
+
1721
+ // Check if status is 0 and handle the error
1722
+ if let status = responseObject["status"] as? Int, status == 0 {
1723
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1724
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
1725
+ } else {
1726
+ DispatchQueue.main.async {
1727
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
1728
+ paymentDoneVC.chargeData = responseObject
1729
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
1730
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1731
+ // Pass billing and additional info
1732
+ // Conditionally pass raw FieldItem array
1733
+ paymentDoneVC.visibility = self.visibility
1734
+
1735
+ if self.visibility?.billing == true {
1736
+ paymentDoneVC.billingInfoData = self.billingInfo
1737
+ var billingDict: [String: Any] = [:]
1738
+ self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
1739
+ paymentDoneVC.billingInfo = billingDict
1740
+ }
1741
+
1742
+ if self.visibility?.additional == true {
1743
+ paymentDoneVC.additionalInfoData = self.additionalInfo
1744
+ var additionalDict: [String: Any] = [:]
1745
+ self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
1746
+ paymentDoneVC.additionalInfo = additionalDict
1747
+ }
1748
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1749
+ }
1750
+ }
1751
+ }
1752
+ } else {
1753
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1754
+ }
1755
+ } catch let jsonError {
1756
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1757
+ }
1758
+ } else {
1759
+ self.presentPaymentErrorVC(errorMessage: "No data received")
1760
+ }
1761
+ } else {
1762
+ if let data = serviceData,
1763
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1764
+ let message = responseObj["message"] as? String {
1765
+ self.presentPaymentErrorVC(errorMessage: message)
1766
+ } else {
1767
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1768
+ }
1769
+ }
1770
+ }
1771
+ task.resume()
1772
+ }
1773
+
1774
+ //MARK: - Banking Account Charge Api from Regular saved bank account
1775
+ func accountChargeSavedBankAccountApi() {
1776
+ showLoadingIndicator()
1777
+
1778
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
1779
+
1780
+ guard let serviceURL = URL(string: fullURL) else {
1781
+ print("Invalid URL")
1782
+ hideLoadingIndicator()
1783
+ return
1784
+ }
1785
+
1786
+ var uRLRequest = URLRequest(url: serviceURL)
1787
+ uRLRequest.httpMethod = "POST"
1788
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1789
+
1790
+ let token = UserStoreSingleton.shared.clientToken
1791
+ print("Setting clientToken header: \(token ?? "None")")
1792
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1793
+
1794
+ var params: [String: Any] = [
1795
+ "name": UserStoreSingleton.shared.merchantName ?? "",
1796
+ "account_id": accountID ?? "",
1797
+ "payment_method": "ach",
1798
+ "customer": customerID ?? "",
1799
+ "currency": "usd",
1800
+ ]
1801
+
1802
+ // Conditionally add billing info
1803
+ if let visibility = visibility, visibility.billing == true,
1804
+ let billing = billingInfo, !billing.isEmpty {
1805
+
1806
+ var billingInfoDict: [String: Any] = [:]
1807
+ for item in billing {
1808
+ billingInfoDict[item.name] = item.value
1809
+ }
1810
+
1811
+ params["address"] = billingInfoDict["address"] as? String ?? ""
1812
+ params["country"] = billingInfoDict["country"] as? String ?? ""
1813
+ params["state"] = billingInfoDict["state"] as? String ?? ""
1814
+ params["city"] = billingInfoDict["city"] as? String ?? ""
1815
+ params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
1816
+ }
1817
+
1818
+ // Set default description if additional info is not visible
1819
+ if let visibility = visibility, visibility.additional == false {
1820
+ params["description"] = "Hosted payment checkout"
1821
+ }
1822
+
1823
+ // Add these if recurring is enabled
1824
+ if let req = request, req.is_recurring == true {
1825
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1826
+ // Only send start_date if type is .custom and field is not empty
1827
+ if let startDateText = startDate, !startDateText.isEmpty {
1828
+ let inputFormatter = DateFormatter()
1829
+ inputFormatter.dateFormat = "dd/MM/yyyy"
1830
+
1831
+ let outputFormatter = DateFormatter()
1832
+ outputFormatter.dateFormat = "MM/dd/yyyy"
1833
+
1834
+ if let date = inputFormatter.date(from: startDateText) {
1835
+ let apiFormattedDate = outputFormatter.string(from: date)
1836
+ params["start_date"] = apiFormattedDate
1837
+ } else {
1838
+ print("Invalid date format in startDateText")
1839
+ }
1840
+ }
1841
+ }
1842
+
1843
+ params["interval"] = chosenPlan?.lowercased()
1844
+ }
1845
+
1846
+ print(params)
1847
+
1848
+ do {
1849
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1850
+ uRLRequest.httpBody = jsonData
1851
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
1852
+ print("JSON Payload: \(jsonString)")
1853
+ }
1854
+ } catch let error {
1855
+ print("Error creating JSON data: \(error)")
1856
+ hideLoadingIndicator()
1857
+ return
1858
+ }
1859
+
1860
+ let session = URLSession.shared
1861
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1862
+
1863
+ DispatchQueue.main.async {
1864
+ self.hideLoadingIndicator() // Stop loader when response is received
1865
+ }
1866
+
1867
+ if let error = error {
1868
+ print("Error: \(error.localizedDescription)")
1869
+ return
1870
+ }
1871
+
1872
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1873
+ print("Invalid response")
1874
+ return
1875
+ }
1876
+
1877
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1878
+ if let data = serviceData {
1879
+ do {
1880
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1881
+ print("Response Data: \(responseObject)")
1882
+
1883
+ // Check if status is 0 and handle the error
1884
+ if let status = responseObject["status"] as? Int, status == 0 {
1885
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1886
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
1887
+ } else {
1888
+ DispatchQueue.main.async {
1889
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
1890
+ paymentDoneVC.chargeData = responseObject
1891
+ // Pass the selected payment method
1892
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
1893
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate // Pass the delegate
1894
+ // Pass billing and additional info
1895
+ // Conditionally pass raw FieldItem array
1896
+ paymentDoneVC.visibility = self.visibility
1897
+
1898
+ if self.visibility?.billing == true {
1899
+ paymentDoneVC.billingInfoData = self.billingInfo
1900
+ var billingDict: [String: Any] = [:]
1901
+ self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
1902
+ paymentDoneVC.billingInfo = billingDict
1903
+ }
1904
+
1905
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1906
+ }
1907
+ }
1908
+ }
1909
+ } else {
1910
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1911
+ }
1912
+ } catch let jsonError {
1913
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1914
+ }
1915
+ } else {
1916
+ self.presentPaymentErrorVC(errorMessage: "No data received")
1917
+ }
1918
+ } else {
1919
+ if let data = serviceData,
1920
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1921
+ let message = responseObj["message"] as? String {
1922
+ self.presentPaymentErrorVC(errorMessage: message)
1923
+ } else {
1924
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1925
+ }
1926
+ }
1927
+ }
1928
+ task.resume()
1929
+ }
1930
+
1931
+ //MARK: - Banking Account Charge Api
1932
+ func accountChargeApi() {
1933
+ showLoadingIndicator()
1934
+
1935
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
1936
+
1937
+ guard let serviceURL = URL(string: fullURL) else {
1938
+ print("Invalid URL")
1939
+ hideLoadingIndicator()
1940
+ return
1941
+ }
1942
+
1943
+ var uRLRequest = URLRequest(url: serviceURL)
1944
+ uRLRequest.httpMethod = "POST"
1945
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1946
+
1947
+ let token = UserStoreSingleton.shared.clientToken
1948
+ print("Setting clientToken header: \(token ?? "None")")
1949
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1950
+
1951
+ var params: [String: Any] = [
1952
+ "name": accountName ?? "",
1953
+ "email": userEmail ?? "",
1954
+ "currency": "usd",
1955
+ "account_type": accountType?.lowercased() ?? "",
1956
+ "routing_number": routingNumber ?? "",
1957
+ "account_number": accountNumber ?? "",
1958
+ "payment_mode": "auth_and_capture",
1959
+ "payment_intent": UserStoreSingleton.shared.paymentIntent ?? "",
1960
+ "levelIndicator": 1,
1961
+ ]
1962
+
1963
+ // Conditionally add billing info
1964
+ if let visibility = visibility, visibility.billing == true,
1965
+ let billing = billingInfo, !billing.isEmpty {
1966
+
1967
+ var billingInfoDict: [String: Any] = [:]
1968
+ for item in billing {
1969
+ billingInfoDict[item.name] = item.value
1970
+ }
1971
+
1972
+ params["address"] = billingInfoDict["address"] as? String ?? ""
1973
+ params["country"] = billingInfoDict["country"] as? String ?? ""
1974
+ params["state"] = billingInfoDict["state"] as? String ?? ""
1975
+ params["city"] = billingInfoDict["city"] as? String ?? ""
1976
+ params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
1977
+ }
1978
+
1979
+ // Set default description if additional info is not visible
1980
+ if let visibility = visibility, visibility.additional == false {
1981
+ params["description"] = "Hosted payment checkout"
1982
+ }
1983
+
1984
+ // Add these if recurring is enabled
1985
+ if let req = request, req.is_recurring == true {
1986
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
1987
+ // Only send start_date if type is .custom and field is not empty
1988
+ if let startDateText = startDate, !startDateText.isEmpty {
1989
+ let inputFormatter = DateFormatter()
1990
+ inputFormatter.dateFormat = "dd/MM/yyyy"
1991
+
1992
+ let outputFormatter = DateFormatter()
1993
+ outputFormatter.dateFormat = "MM/dd/yyyy"
1994
+
1995
+ if let date = inputFormatter.date(from: startDateText) {
1996
+ let apiFormattedDate = outputFormatter.string(from: date)
1997
+ params["start_date"] = apiFormattedDate
1998
+ } else {
1999
+ print("Invalid date format in startDateText")
2000
+ }
2001
+ }
2002
+ }
2003
+
2004
+ params["interval"] = chosenPlan?.lowercased()
2005
+ }
2006
+
2007
+ print(params)
2008
+
2009
+ do {
2010
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2011
+ uRLRequest.httpBody = jsonData
2012
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
2013
+ print("JSON Payload: \(jsonString)")
2014
+ }
2015
+ } catch let error {
2016
+ print("Error creating JSON data: \(error)")
2017
+ hideLoadingIndicator()
2018
+ return
2019
+ }
2020
+
2021
+ let session = URLSession.shared
2022
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
2023
+
2024
+ DispatchQueue.main.async {
2025
+ self.hideLoadingIndicator() // Stop loader when response is received
2026
+ }
2027
+
2028
+ if let error = error {
2029
+ print("Error: \(error.localizedDescription)")
2030
+ return
2031
+ }
2032
+
2033
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2034
+ print("Invalid response")
2035
+ return
2036
+ }
2037
+
2038
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2039
+ if let data = serviceData {
2040
+ do {
2041
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2042
+ print("Response Data: \(responseObject)")
2043
+
2044
+ // Check if status is 0 and handle the error
2045
+ if let status = responseObject["status"] as? Int, status == 0 {
2046
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
2047
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
2048
+ } else {
2049
+ DispatchQueue.main.async {
2050
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
2051
+ paymentDoneVC.chargeData = responseObject
2052
+ // Pass the selected payment method
2053
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
2054
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate // Pass the delegate
2055
+ // Pass billing and additional info
2056
+ // Conditionally pass raw FieldItem array
2057
+ paymentDoneVC.visibility = self.visibility
2058
+
2059
+ if self.visibility?.billing == true {
2060
+ paymentDoneVC.billingInfoData = self.billingInfo
2061
+ var billingDict: [String: Any] = [:]
2062
+ self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
2063
+ paymentDoneVC.billingInfo = billingDict
2064
+ }
2065
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2066
+ }
2067
+ }
2068
+ }
2069
+ } else {
2070
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2071
+ }
2072
+ } catch let jsonError {
2073
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2074
+ }
2075
+ } else {
2076
+ self.presentPaymentErrorVC(errorMessage: "No data received")
2077
+ }
2078
+ } else {
2079
+ if let data = serviceData,
2080
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
2081
+ let message = responseObj["message"] as? String {
2082
+ self.presentPaymentErrorVC(errorMessage: message)
2083
+ } else {
2084
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2085
+ }
2086
+ }
2087
+ }
2088
+ task.resume()
2089
+ }
2090
+
2091
+ //MARK: - Account Charge Api if user saved the account.
2092
+ func accountChargeApi(customerId: String?) {
2093
+ showLoadingIndicator()
2094
+
2095
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
2096
+
2097
+ guard let serviceURL = URL(string: fullURL) else {
2098
+ print("Invalid URL")
2099
+ hideLoadingIndicator()
2100
+ return
2101
+ }
2102
+
2103
+ var uRLRequest = URLRequest(url: serviceURL)
2104
+ uRLRequest.httpMethod = "POST"
2105
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
2106
+
2107
+ let token = UserStoreSingleton.shared.clientToken
2108
+ print("Setting clientToken header: \(token ?? "None")")
2109
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2110
+
2111
+ let emailPrefix = UserStoreSingleton.shared.verificationEmail?.components(separatedBy: "@").first ?? ""
2112
+
2113
+ var params: [String: Any] = [
2114
+ "name": accountName ?? "",
2115
+ "email": UserStoreSingleton.shared.merchantEmail ?? "",
2116
+ "currency": "usd",
2117
+ "account_type": accountType?.lowercased() ?? "",
2118
+ "routing_number": routingNumber ?? "",
2119
+ "account_number": accountNumber ?? "",
2120
+ "payment_mode": "auth_and_capture",
2121
+ "payment_intent": UserStoreSingleton.shared.paymentIntent ?? "",
2122
+ "levelIndicator": 1,
2123
+ "save_account": 1,
2124
+ "payment_method": "ach"
2125
+ ]
2126
+
2127
+ if let customerId = customerId {
2128
+ params["customer"] = customerId
2129
+ } else {
2130
+ params["username"] = emailPrefix
2131
+ }
2132
+
2133
+ // Conditionally add billing info
2134
+ if let visibility = visibility, visibility.billing == true,
2135
+ let billing = billingInfo, !billing.isEmpty {
2136
+
2137
+ var billingInfoDict: [String: Any] = [:]
2138
+ for item in billing {
2139
+ billingInfoDict[item.name] = item.value
2140
+ }
2141
+
2142
+ params["address"] = billingInfoDict["address"] as? String ?? ""
2143
+ params["country"] = billingInfoDict["country"] as? String ?? ""
2144
+ params["state"] = billingInfoDict["state"] as? String ?? ""
2145
+ params["city"] = billingInfoDict["city"] as? String ?? ""
2146
+ params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
2147
+ }
2148
+
2149
+ // Set default description if additional info is not visible
2150
+ if let visibility = visibility, visibility.additional == false {
2151
+ params["description"] = "Hosted payment checkout"
2152
+ }
2153
+
2154
+ // Add these if recurring is enabled
2155
+ if let req = request, req.is_recurring == true {
2156
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
2157
+ // Only send start_date if type is .custom and field is not empty
2158
+ if let startDateText = startDate, !startDateText.isEmpty {
2159
+ let inputFormatter = DateFormatter()
2160
+ inputFormatter.dateFormat = "dd/MM/yyyy"
2161
+
2162
+ let outputFormatter = DateFormatter()
2163
+ outputFormatter.dateFormat = "MM/dd/yyyy"
2164
+
2165
+ if let date = inputFormatter.date(from: startDateText) {
2166
+ let apiFormattedDate = outputFormatter.string(from: date)
2167
+ params["start_date"] = apiFormattedDate
2168
+ } else {
2169
+ print("Invalid date format in startDateText")
2170
+ }
2171
+ }
2172
+ }
2173
+
2174
+ params["interval"] = chosenPlan?.lowercased()
2175
+ }
2176
+
2177
+ print(params)
2178
+
2179
+ do {
2180
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2181
+ uRLRequest.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: uRLRequest) { (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
+ // Pass billing and additional info
2225
+ // Conditionally pass raw FieldItem array
2226
+ paymentDoneVC.visibility = self.visibility
2227
+
2228
+ if self.visibility?.billing == true {
2229
+ paymentDoneVC.billingInfoData = self.billingInfo
2230
+ var billingDict: [String: Any] = [:]
2231
+ self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
2232
+ paymentDoneVC.billingInfo = billingDict
2233
+ }
2234
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2235
+ }
2236
+ }
2237
+ }
2238
+ } else {
2239
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2240
+ }
2241
+ } catch let jsonError {
2242
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2243
+ }
2244
+ } else {
2245
+ self.presentPaymentErrorVC(errorMessage: "No data received")
2246
+ }
2247
+ } else {
2248
+ if let data = serviceData,
2249
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
2250
+ let message = responseObj["message"] as? String {
2251
+ self.presentPaymentErrorVC(errorMessage: message)
2252
+ } else {
2253
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2254
+ }
2255
+ }
2256
+ }
2257
+ task.resume()
2258
+ }
2259
+
2260
+ //MARK: - GrailPay Account Charge Api if user not saved account but billing info available
2261
+ func grailPayAccountChargeApi() {
2262
+ showLoadingIndicator()
2263
+
2264
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
2265
+
2266
+ guard let serviceURL = URL(string: fullURL) else {
2267
+ print("Invalid URL")
2268
+ hideLoadingIndicator()
2269
+ return
2270
+ }
2271
+
2272
+ var uRLRequest = URLRequest(url: serviceURL)
2273
+ uRLRequest.httpMethod = "POST"
2274
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
2275
+
2276
+ let token = UserStoreSingleton.shared.clientToken
2277
+ print("Setting clientToken header: \(token ?? "None")")
2278
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2279
+
2280
+ if let apiKey = EnvironmentConfig.apiKey {
2281
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "X-Api-Key")
2282
+ }
2283
+ if let apiSecret = EnvironmentConfig.apiSecret {
2284
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "X-Api-Secret")
2285
+ }
2286
+
2287
+ var params: [String: Any] = [
2288
+ "account_id": self.grailPayAccountID ?? "",
2289
+ "account_type": self.selectedGrailPayAccountType ?? "",
2290
+ "name": self.selectedGrailPayAccountName ?? "",
2291
+ "description": "payment checkout",
2292
+ "email": UserStoreSingleton.shared.merchantEmail ?? ""
2293
+ ]
2294
+
2295
+ // Conditionally add billing info
2296
+ if let visibility = visibility, visibility.billing == true,
2297
+ let billing = billingInfo, !billing.isEmpty {
2298
+
2299
+ var billingInfoDict: [String: Any] = [:]
2300
+ for item in billing {
2301
+ billingInfoDict[item.name] = item.value
2302
+ }
2303
+
2304
+ params["address"] = billingInfoDict["address"] as? String ?? ""
2305
+ params["country"] = billingInfoDict["country"] as? String ?? ""
2306
+ params["state"] = billingInfoDict["state"] as? String ?? ""
2307
+ params["city"] = billingInfoDict["city"] as? String ?? ""
2308
+ params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
2309
+ }
2310
+
2311
+ // Set default description if additional info is not visible
2312
+ if let visibility = visibility, visibility.additional == false {
2313
+ params["description"] = "Hosted payment checkout"
2314
+ }
2315
+
2316
+ // Add these if recurring is enabled
2317
+ if let req = request, req.is_recurring == true {
2318
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
2319
+ // Only send start_date if type is .custom and field is not empty
2320
+ if let startDateText = startDate, !startDateText.isEmpty {
2321
+ let inputFormatter = DateFormatter()
2322
+ inputFormatter.dateFormat = "dd/MM/yyyy"
2323
+
2324
+ let outputFormatter = DateFormatter()
2325
+ outputFormatter.dateFormat = "MM/dd/yyyy"
2326
+
2327
+ if let date = inputFormatter.date(from: startDateText) {
2328
+ let apiFormattedDate = outputFormatter.string(from: date)
2329
+ params["start_date"] = apiFormattedDate
2330
+ } else {
2331
+ print("Invalid date format in startDateText")
2332
+ }
2333
+ }
2334
+ }
2335
+
2336
+ params["interval"] = chosenPlan?.lowercased()
2337
+ }
2338
+
2339
+ print(params)
2340
+
2341
+ do {
2342
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2343
+ uRLRequest.httpBody = jsonData
2344
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
2345
+ print("JSON Payload: \(jsonString)")
2346
+ }
2347
+ } catch let error {
2348
+ print("Error creating JSON data: \(error)")
2349
+ hideLoadingIndicator()
2350
+ return
2351
+ }
2352
+
2353
+ let session = URLSession.shared
2354
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
2355
+
2356
+ DispatchQueue.main.async {
2357
+ self.hideLoadingIndicator() // Stop loader when response is received
2358
+ }
2359
+
2360
+ if let error = error {
2361
+ self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
2362
+ return
2363
+ }
2364
+
2365
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2366
+ self.presentPaymentErrorVC(errorMessage: "Invalid response")
2367
+ return
2368
+ }
2369
+
2370
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2371
+ if let data = serviceData {
2372
+ do {
2373
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2374
+ print("Response Data: \(responseObject)")
2375
+
2376
+ // Check if status is 0 and handle the error
2377
+ if let status = responseObject["status"] as? Int, status == 0 {
2378
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
2379
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
2380
+ } else {
2381
+ DispatchQueue.main.async {
2382
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
2383
+ paymentDoneVC.chargeData = responseObject
2384
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
2385
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
2386
+ // Pass billing and additional info
2387
+ // Conditionally pass raw FieldItem array
2388
+ paymentDoneVC.visibility = self.visibility
2389
+
2390
+ if self.visibility?.billing == true {
2391
+ paymentDoneVC.billingInfoData = self.billingInfo
2392
+ var billingDict: [String: Any] = [:]
2393
+ self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
2394
+ paymentDoneVC.billingInfo = billingDict
2395
+ }
2396
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2397
+ }
2398
+ }
2399
+ }
2400
+ } else {
2401
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2402
+ }
2403
+ } catch let jsonError {
2404
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2405
+ }
2406
+ } else {
2407
+ self.presentPaymentErrorVC(errorMessage: "No data received")
2408
+ }
2409
+ } else {
2410
+ if let data = serviceData,
2411
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
2412
+ let message = responseObj["message"] as? String {
2413
+ self.presentPaymentErrorVC(errorMessage: message)
2414
+ } else {
2415
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2416
+ }
2417
+ }
2418
+ }
2419
+ task.resume()
2420
+ }
2421
+
2422
+ }
2423
+
2424
+ //MARK: - Table View
2425
+ @available(iOS 16.0, *)
2426
+ extension BillingInfoVC: UITableViewDelegate, UITableViewDataSource {
2427
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
2428
+ if tableView == tblViewCountryList {
2429
+ return countryList.count
2430
+ }
2431
+ else if tableView == tblViewStateList {
2432
+ return stateList.count
2433
+ }
2434
+ else if tableView == tblViewCityList {
2435
+ return cityList.count
2436
+ }
2437
+ else {
2438
+ return 0
2439
+ }
2440
+ }
2441
+
2442
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
2443
+ if tableView == tblViewCountryList {
2444
+ let cell = tableView.dequeueReusableCell(withIdentifier: "CountryListTVC") as! CountryListTVC
2445
+ let country = countryList[indexPath.row]
2446
+ cell.lblCountryName.text = country["name"] as? String
2447
+ return cell
2448
+ } else if tableView == tblViewStateList {
2449
+ let cell = tableView.dequeueReusableCell(withIdentifier: "StateListTVC") as! StateListTVC
2450
+ let state = stateList[indexPath.row]
2451
+ cell.lblStateName.text = state["name"] as? String
2452
+ return cell
2453
+ } else if tableView == tblViewCityList {
2454
+ let cell = tableView.dequeueReusableCell(withIdentifier: "CityListTVC") as! CityListTVC
2455
+ let city = cityList[indexPath.row]
2456
+ cell.lblCityName.text = city["city_name"] as? String
2457
+ return cell
2458
+ } else {
2459
+ return UITableViewCell()
2460
+ }
2461
+ }
2462
+
2463
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
2464
+ if tableView == tblViewCountryList {
2465
+ let selectedCountry = countryList[indexPath.row]["name"] as? String
2466
+ txtFieldCountry.text = selectedCountry
2467
+ viewCountryList.isHidden = true
2468
+
2469
+ // Fetch states for the selected country
2470
+ if let country = selectedCountry {
2471
+ getStateListApi(for: country)
2472
+ }
2473
+ } else if tableView == tblViewStateList {
2474
+ let selectedState = stateList[indexPath.row]["name"] as? String
2475
+ txtFieldState.text = selectedState
2476
+ viewStateList.isHidden = true
2477
+
2478
+ // Fetch cities for the selected state and country
2479
+ if let state = selectedState, let country = txtFieldCountry.text {
2480
+ getCityListListApi(for: country, state: state)
2481
+ }
2482
+ } else if tableView == tblViewCityList {
2483
+ let selectedCity = cityList[indexPath.row]["city_name"] as? String
2484
+ txtFieldCity.text = selectedCity
2485
+ viewCityList.isHidden = true
2486
+
2487
+ // Fetch the city list again based on selected state & country
2488
+ if let state = txtFieldState.text, let country = txtFieldCountry.text {
2489
+ getCityListListApi(for: country, state: state)
2490
+ }
2491
+ }
2492
+
2493
+ updateBillingInfoData()
2494
+ }
2495
+
2496
+ func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
2497
+ return 50
2498
+ }
2499
+
2500
+ }
2501
+
2502
+ @available(iOS 16.0, *)
2503
+ extension BillingInfoVC: UITextFieldDelegate {
2504
+ func textFieldShouldReturn(_ textField: UITextField) -> Bool {
2505
+ textField.resignFirstResponder() // Dismiss the keyboard
2506
+ return true
2507
+ }
2508
+
2509
+ // Update billingInfoData when user finishes editing a field
2510
+ func textFieldDidEndEditing(_ textField: UITextField) {
707
2511
  switch textField {
708
2512
  case txtFieldAddress:
709
- billingInfoData?["address"] = textField.text
2513
+ setFieldValue("address", to: textField.text)
710
2514
  case txtFieldCountry:
711
- billingInfoData?["country"] = textField.text
2515
+ setFieldValue("country", to: textField.text)
712
2516
  case txtFieldState:
713
- billingInfoData?["state"] = textField.text
2517
+ setFieldValue("state", to: textField.text)
714
2518
  case txtFieldCity:
715
- billingInfoData?["city"] = textField.text
2519
+ setFieldValue("city", to: textField.text)
716
2520
  case txtFieldPostalCode:
717
- billingInfoData?["postal_code"] = textField.text
2521
+ setFieldValue("postal_code", to: textField.text)
718
2522
  default:
719
2523
  break
720
2524
  }
2525
+
2526
+ billingInfo = fieldSection?.billing
721
2527
  }
2528
+
722
2529
  }
723
2530