@jimrising/easymerchantsdk-react-native 2.4.2 → 2.4.4

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 (71) hide show
  1. package/README.md +42 -113
  2. package/android/build.gradle +1 -1
  3. package/ios/Classes/EasyMerchantSdk.m +2 -2
  4. package/ios/easymerchantsdk.podspec +1 -1
  5. package/package.json +1 -1
  6. package/.idea/caches/deviceStreaming.xml +0 -340
  7. package/.idea/em-MobileCheckoutSDK-ReactNative.iml +0 -9
  8. package/.idea/misc.xml +0 -5
  9. package/.idea/modules.xml +0 -8
  10. package/.idea/vcs.xml +0 -6
  11. package/android/build/generated/source/buildConfig/debug/com/reactlibrary/BuildConfig.java +0 -10
  12. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +0 -7
  13. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +0 -18
  14. package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +0 -6
  15. package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +0 -1
  16. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  17. package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
  18. package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
  19. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +0 -1
  20. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +0 -2
  21. package/android/build/intermediates/incremental/mergeDebugAssets/merger.xml +0 -2
  22. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +0 -2
  23. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +0 -2
  24. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/BuildConfig.class +0 -0
  25. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
  26. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  27. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  28. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$3.class +0 -0
  29. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  30. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
  31. package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +0 -2
  32. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +0 -7
  33. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +0 -7
  34. package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +0 -1
  35. package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +0 -1
  36. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/BuildConfig.class +0 -0
  37. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
  38. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  39. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  40. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$3.class +0 -0
  41. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  42. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
  43. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  44. package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +0 -1
  45. package/android/build/outputs/logs/manifest-merger-debug-report.txt +0 -16
  46. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  47. package/ios/Pods/UserDefaults/UserStoreSingleton.swift +0 -425
  48. package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +0 -2996
  49. package/ios/Pods/ViewControllers/BaseVC.swift +0 -142
  50. package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +0 -3807
  51. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CityListTVC.swift +0 -46
  52. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CountryListTVC.swift +0 -47
  53. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/StateListTVC.swift +0 -46
  54. package/ios/Pods/ViewControllers/Clean Runner_2025-07-23T14-58-05.txt +0 -13
  55. package/ios/Pods/ViewControllers/CountryListVC.swift +0 -435
  56. package/ios/Pods/ViewControllers/EmailVerificationVC.swift +0 -300
  57. package/ios/Pods/ViewControllers/GrailPayVC.swift +0 -492
  58. package/ios/Pods/ViewControllers/OTPVerificationVC.swift +0 -2278
  59. package/ios/Pods/ViewControllers/PaymentDoneVC.swift +0 -287
  60. package/ios/Pods/ViewControllers/PaymentErrorVC.swift +0 -85
  61. package/ios/Pods/ViewControllers/PaymentInformation/AccountTypeTVC.swift +0 -41
  62. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +0 -13115
  63. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInformationCVC.swift +0 -35
  64. package/ios/Pods/ViewControllers/PaymentInformation/RecurringTVC.swift +0 -40
  65. package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.swift +0 -80
  66. package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.xib +0 -163
  67. package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.swift +0 -81
  68. package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.xib +0 -188
  69. package/ios/Pods/ViewControllers/PaymentStatusWebViewVC.swift +0 -167
  70. package/ios/Pods/ViewControllers/TermAndConditionsVC.swift +0 -63
  71. package/ios/Pods/ViewControllers/ThreeDSecurePaymentDoneVC.swift +0 -1254
@@ -1,3807 +0,0 @@
1
- //
2
- // BillingInfoVC.swift
3
- // EasyPay
4
- //
5
- // Created by Mony's Mac on 12/08/24.
6
- //
7
-
8
- import UIKit
9
-
10
- @available(iOS 16.0, *)
11
-
12
- protocol BillingInfoVCDelegate: AnyObject {
13
- func didPassTextBack(_ text: String)
14
- }
15
-
16
- class BillingInfoVC: BaseVC {
17
-
18
- // @IBOutlet weak var viewBillingInfo: UIView!
19
- @IBOutlet weak var btnPrevious: UIButton!
20
- @IBOutlet weak var viewCountryList: UIView!
21
- @IBOutlet weak var searchBarCountryList: UISearchBar!
22
- @IBOutlet weak var tblViewCountryList: UITableView!
23
- @IBOutlet weak var txtFieldAddress: UITextField!
24
- @IBOutlet weak var txtFieldCountry: UITextField!
25
- @IBOutlet weak var viewStateList: UIView!
26
- @IBOutlet weak var searchBarStateList: UISearchBar!
27
- @IBOutlet weak var tblViewStateList: UITableView!
28
- @IBOutlet weak var txtFieldState: UITextField!
29
- @IBOutlet weak var viewCityList: UIView!
30
- @IBOutlet weak var tblViewCityList: UITableView!
31
- @IBOutlet weak var txtFieldCity: UITextField!
32
- @IBOutlet weak var txtFieldPostalCode: UITextField!
33
-
34
- @IBOutlet weak var buttonBottomConstraint: NSLayoutConstraint!
35
-
36
- @IBOutlet weak var lblBillingInfo: UILabel!
37
- @IBOutlet weak var lblEasyMerchant: UILabel!
38
- @IBOutlet weak var btnNext: UIButton!
39
-
40
- @IBOutlet weak var lblStarAddressField: UILabel!
41
- @IBOutlet weak var lblStarCountryField: UILabel!
42
- @IBOutlet weak var lblStarStateField: UILabel!
43
- @IBOutlet weak var lblStarCityField: UILabel!
44
- @IBOutlet weak var lblStarPostalCodeField: UILabel!
45
-
46
- @IBOutlet weak var btnSelectState: UIButton!
47
- @IBOutlet weak var btnSelectCity: UIButton!
48
-
49
- // Variable to store the auth token
50
- var authToken: String?
51
-
52
- var countryList: [[String: Any]] = []
53
- var stateList: [[String: Any]] = []
54
- var cityList: [[String: Any]] = []
55
-
56
- // var billingInfoData: [String: Any]?
57
- var billingInfoData: Data?
58
-
59
- var cardNumber: String?
60
- var expiryDate: String?
61
- var cvv: String?
62
- var nameOnCard: String?
63
- var userEmail: String?
64
-
65
- //Banking Params
66
- var accountName: String?
67
- var routingNumber: String?
68
- var accountType: String?
69
- var accountNumber: String?
70
-
71
- var selectedPaymentMethod: String?
72
-
73
- private let keyboardObserver = KeyboardObserver()
74
-
75
- var isSavedForFuture: Bool = false
76
- var isSavedNewCard: Bool = false
77
-
78
- var request: Request!
79
- var selectedCard: CardModel?
80
- // var amount: Int?
81
- var amount: Double?
82
- var cvvText: String?
83
-
84
- var isFrom = String()
85
- var isFromm = String()
86
-
87
- //From Regular Saved Bank Accounts
88
- var customerID: String?
89
- var accountID: String?
90
-
91
- var isSavedNewAccount: Bool?
92
-
93
- weak var delegate: BillingInfoVCDelegate?
94
-
95
- var chosenPlan: String?
96
- var startDate: String?
97
-
98
- //GrailPay Params
99
- var grailPayAccountID: String?
100
- var selectedGrailPayAccountType: String?
101
- var selectedGrailPayAccountName: String?
102
-
103
- var billingInfo: [FieldItem]?
104
- var additionalInfo: [FieldItem]?
105
- var visibility: FieldsVisibility?
106
- var fieldSection: FieldSection?
107
-
108
- var easyPayDelegate: EasyPayViewControllerDelegate?
109
-
110
- var filteredCountryList: [[String: Any]] = []
111
- var isSearching = false
112
-
113
- var filteredStateList: [[String: Any]] = [] // For search results
114
- var isSearchingState = false // Track if user is searching
115
-
116
- private var didApplyInitialCountry = false
117
-
118
- override func viewDidLoad() {
119
- super.viewDidLoad()
120
- self.lblEasyMerchant.text = "POWERED BY \(UserStoreSingleton.shared.companyName?.uppercased() ?? "")"
121
-
122
- updateNextButtonTitle()
123
- btnSelectState.isHidden = true
124
- btnSelectCity.isHidden = true
125
-
126
- uiFinishingTouchElements()
127
- // configureFieldVisibility()
128
- setupShadowForListViews()
129
-
130
- tblViewCountryList.delegate = self
131
- tblViewCountryList.dataSource = self
132
- viewCountryList.isHidden = true
133
-
134
- tblViewStateList.delegate = self
135
- tblViewStateList.dataSource = self
136
- viewStateList.isHidden = true
137
-
138
- tblViewCityList.delegate = self
139
- tblViewCityList.dataSource = self
140
- viewCityList.isHidden = true
141
-
142
- txtFieldCity.delegate = self
143
- txtFieldState.delegate = self
144
- txtFieldAddress.delegate = self
145
- txtFieldCountry.delegate = self
146
- txtFieldPostalCode.delegate = self
147
-
148
- searchBarCountryList.delegate = self
149
- searchBarStateList.delegate = self
150
-
151
- if let textField = searchBarCountryList.value(forKey: "searchField") as? UITextField {
152
- textField.clearButtonMode = .never
153
- }
154
-
155
- if let textField = searchBarStateList.value(forKey: "searchField") as? UITextField {
156
- textField.clearButtonMode = .never
157
- }
158
-
159
- getCountryListApi()
160
-
161
- // Add tap gesture to hide the views and dismiss the keyboard
162
- let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapOutside))
163
- tapGesture.cancelsTouchesInView = false
164
- self.view.addGestureRecognizer(tapGesture)
165
-
166
- guard let billingInfoData = billingInfoData else {
167
- print("No billing info data")
168
- return
169
- }
170
-
171
- do {
172
- let decodedFieldSection = try JSONDecoder().decode(FieldSection.self, from: billingInfoData)
173
- self.fieldSection = decodedFieldSection // <--- Assign here!
174
-
175
- // Fill Billing Fields UI
176
- for item in decodedFieldSection.billing {
177
- switch item.name {
178
- case "address":
179
- txtFieldAddress.text = item.value
180
- case "country":
181
- txtFieldCountry.text = item.value
182
- case "state":
183
- txtFieldState.text = item.value
184
- case "city":
185
- txtFieldCity.text = item.value
186
- case "postal_code":
187
- txtFieldPostalCode.text = item.value
188
- default:
189
- break
190
- }
191
- }
192
- // Now that fieldSection is set, update star labels visibility
193
- configureFieldVisibility()
194
-
195
- } catch {
196
- print("Failed to decode billing info data: \(error)")
197
- }
198
-
199
- }
200
-
201
- override func viewWillAppear(_ animated: Bool) {
202
- super.viewWillAppear(animated)
203
- updateNextButtonTitle()
204
- uiFinishingTouchElements()
205
-
206
- keyboardObserver.animateChanges({ [self] height in
207
- let newConstant = CGFloat.maximum(height - self.view.safeAreaInsets.bottom + 8, 8)
208
- buttonBottomConstraint.constant = newConstant
209
- self.view.setNeedsLayout()
210
- self.view.layoutIfNeeded()
211
- })
212
-
213
- configureFieldVisibility()
214
- }
215
-
216
- override func viewDidAppear(_ animated: Bool) {
217
- super.viewDidAppear(animated)
218
-
219
- if !didApplyInitialCountry {
220
- didApplyInitialCountry = true
221
- handleCountrySelection(countryName: txtFieldCountry.text ?? "")
222
- }
223
- }
224
-
225
- override func viewWillDisappear(_ animated: Bool) {
226
- super.viewWillDisappear(animated)
227
- keyboardObserver.invalidate()
228
- }
229
-
230
- private func updateNextButtonTitle() {
231
- guard let request = request else { return }
232
-
233
- if let billingInfoData = request.fields,
234
- let fieldSection = try? JSONDecoder().decode(FieldSection.self, from: billingInfoData) {
235
-
236
- let isAdditionalVisible = fieldSection.visibility.additional
237
- let isBillingVisible = fieldSection.visibility.billing
238
- let amountText = String(format: "$%.2f", request.amount ?? 0)
239
- let submitText = request.submitButtonText ?? "Submit"
240
-
241
- if !isAdditionalVisible {
242
- // Only billing info is visible
243
- btnNext.setTitle("\(submitText) (\(amountText))", for: .normal)
244
- } else {
245
- // Additional info is visible
246
- let suffix = isBillingVisible ? "(Additional Info)" : ""
247
- btnNext.setTitle("\(submitText) \(suffix)", for: .normal)
248
- }
249
- } else {
250
- let amountValue = request.amount ?? 0
251
- let amountText = String(format: "$%.2f", amountValue)
252
- let submitText = request.submitButtonText
253
-
254
- let defaultTitle = (submitText?.isEmpty == false)
255
- ? "\(submitText!) (\(amountText))"
256
- : "Pay Now (\(amountText))"
257
-
258
- btnNext.setTitle(defaultTitle, for: .normal)
259
- }
260
- }
261
-
262
- func uiFinishingTouchElements() {
263
- // Set background color for the main view
264
- if let containerBGcolor = UserStoreSingleton.shared.container_bg_col,
265
- let uiColor = UIColor(hex: containerBGcolor) {
266
- self.view.backgroundColor = uiColor
267
- viewCountryList.backgroundColor = uiColor
268
- viewStateList.backgroundColor = uiColor
269
- viewCityList.backgroundColor = uiColor
270
- tblViewCountryList.backgroundColor = uiColor
271
- tblViewStateList.backgroundColor = uiColor
272
- tblViewCityList.backgroundColor = uiColor
273
-
274
- self.searchBarCountryList.backgroundColor = uiColor
275
- self.searchBarCountryList.barTintColor = uiColor
276
- self.searchBarStateList.backgroundColor = uiColor
277
- }
278
-
279
- if let primaryBtnBackGroundColor = UserStoreSingleton.shared.primary_btn_bg_col,
280
- let uiColor = UIColor(hex: primaryBtnBackGroundColor) {
281
- btnNext.backgroundColor = uiColor
282
- btnPrevious.setTitleColor(uiColor, for: .normal)
283
- btnPrevious.layer.borderColor = uiColor.cgColor
284
-
285
- searchBarCountryList.tintColor = uiColor
286
- searchBarStateList.tintColor = uiColor
287
- }
288
-
289
- if let primaryBtnFontColor = UserStoreSingleton.shared.primary_btn_font_col,
290
- let secondaryUIColor = UIColor(hex: primaryBtnFontColor) {
291
- btnNext.setTitleColor(secondaryUIColor, for: .normal)
292
- }
293
-
294
- if let secondaryFontColor = UserStoreSingleton.shared.secondary_font_col,
295
- let placeholderColor = UIColor(hex: secondaryFontColor) {
296
- lblEasyMerchant.textColor = placeholderColor
297
-
298
- // Set placeholder text color
299
- let placeholderAttributes: [NSAttributedString.Key: Any] = [
300
- .foregroundColor: placeholderColor
301
- ]
302
- searchBarCountryList.searchTextField.attributedPlaceholder = NSAttributedString(
303
- string: searchBarCountryList.searchTextField.placeholder ?? "Search here",
304
- attributes: placeholderAttributes
305
- )
306
-
307
- searchBarStateList.searchTextField.attributedPlaceholder = NSAttributedString(
308
- string: searchBarStateList.searchTextField.placeholder ?? "Search here",
309
- attributes: placeholderAttributes
310
- )
311
-
312
- // Set search icon color
313
- searchBarCountryList.searchTextField.leftView?.tintColor = placeholderColor
314
- searchBarStateList.searchTextField.leftView?.tintColor = placeholderColor
315
- }
316
-
317
- if let borderRadiusString = UserStoreSingleton.shared.border_radious,
318
- let borderRadius = Double(borderRadiusString) { // Convert String to Double
319
- btnNext.layer.cornerRadius = CGFloat(borderRadius) // Set corner radius
320
- btnPrevious.layer.cornerRadius = CGFloat(borderRadius)
321
- btnPrevious.layer.borderWidth = 1
322
- } else {
323
- btnNext.layer.cornerRadius = 8 // Default value
324
- btnPrevious.layer.cornerRadius = 8
325
- }
326
- btnNext.layer.masksToBounds = true // Ensure the corners are clipped properly
327
- btnPrevious.layer.masksToBounds = true
328
-
329
- if let primaryFontColor = UserStoreSingleton.shared.primary_font_col,
330
- let uiColor = UIColor(hex: primaryFontColor) {
331
- lblBillingInfo.textColor = uiColor
332
-
333
- searchBarCountryList.searchTextField.textColor = uiColor
334
- searchBarStateList.searchTextField.textColor = uiColor
335
- }
336
-
337
- if let fontSizeString = UserStoreSingleton.shared.fontSize,
338
- let fontSizeDouble = Double(fontSizeString) { // Convert String to Double
339
- let fontSize = CGFloat(fontSizeDouble) // Convert Double to CGFloat
340
- lblEasyMerchant.font = UIFont.systemFont(ofSize: fontSize)
341
- btnNext.titleLabel?.font = UIFont.systemFont(ofSize: fontSize)
342
- btnPrevious.titleLabel?.font = UIFont.systemFont(ofSize: fontSize)
343
-
344
- searchBarCountryList.searchTextField.font = UIFont.systemFont(ofSize: fontSize)
345
- searchBarStateList.searchTextField.font = UIFont.systemFont(ofSize: fontSize)
346
- }
347
-
348
- if let searchBar = self.searchBarCountryList {
349
- searchBar.backgroundImage = UIImage() // Removes background line
350
- searchBar.layer.borderWidth = 0
351
- searchBar.layer.borderColor = UIColor.clear.cgColor
352
- }
353
-
354
- if let searchBar = self.searchBarStateList {
355
- searchBar.backgroundImage = UIImage() // Removes background line
356
- searchBar.layer.borderWidth = 0
357
- searchBar.layer.borderColor = UIColor.clear.cgColor
358
- }
359
- }
360
-
361
- private func getFieldValue(for name: String) -> String? {
362
- return fieldSection?.billing.first(where: { $0.name == name })?.value
363
- }
364
-
365
- private func setFieldValue(_ name: String, to value: String?) {
366
- guard let index = fieldSection?.billing.firstIndex(where: { $0.name == name }) else { return }
367
- fieldSection?.billing[index].value = value ?? ""
368
- }
369
-
370
- private func configureFieldVisibility() {
371
- guard let billingFields = fieldSection?.billing else {
372
- print("No billing fields found")
373
- return
374
- }
375
-
376
- func isFieldRequired(_ name: String) -> Bool {
377
- let required = billingFields.first(where: { $0.name == name })?.required == true
378
- print("Field \(name) required: \(required)")
379
- return required
380
- }
381
-
382
- DispatchQueue.main.async {
383
- self.lblStarAddressField.isHidden = !isFieldRequired("address")
384
- self.lblStarCountryField.isHidden = !isFieldRequired("country")
385
- self.lblStarStateField.isHidden = !isFieldRequired("state")
386
- self.lblStarCityField.isHidden = !isFieldRequired("city")
387
- self.lblStarPostalCodeField.isHidden = !isFieldRequired("postal_code")
388
- print("Star visibility set")
389
- self.view.layoutIfNeeded()
390
- }
391
- }
392
-
393
- @objc func didTapOutside() {
394
- // Dismiss the keyboard
395
- self.view.endEditing(true)
396
-
397
- // Hide the country list view if it is visible
398
- if !viewCountryList.isHidden {
399
- viewCountryList.isHidden = true
400
- }
401
-
402
- // Hide the state list view if it is visible
403
- if !viewStateList.isHidden {
404
- viewStateList.isHidden = true
405
- }
406
-
407
- // Hide the city list view if it is visible
408
- if !viewCityList.isHidden {
409
- viewCityList.isHidden = true
410
- }
411
- }
412
-
413
- func setupShadowForListViews() {
414
- // Ensure the view does not clip its content or shadows
415
- viewCountryList.clipsToBounds = false
416
- // Apply shadow properties
417
- viewCountryList.layer.shadowColor = UIColor.black.cgColor
418
- viewCountryList.layer.shadowOpacity = 0.3
419
- viewCountryList.layer.shadowOffset = CGSize(width: 0, height: 2)
420
- viewCountryList.layer.shadowRadius = 4
421
- // Optionally, you might want to set a corner radius to the view as well
422
- viewCountryList.layer.cornerRadius = 8
423
- tblViewCountryList.layer.cornerRadius = 8
424
-
425
- // Ensure the view does not clip its content or shadows
426
- viewStateList.clipsToBounds = false
427
- // Apply shadow properties
428
- viewStateList.layer.shadowColor = UIColor.black.cgColor
429
- viewStateList.layer.shadowOpacity = 0.3
430
- viewStateList.layer.shadowOffset = CGSize(width: 0, height: 2)
431
- viewStateList.layer.shadowRadius = 4
432
- // Optionally, you might want to set a corner radius to the view as well
433
- viewStateList.layer.cornerRadius = 8
434
- tblViewStateList.layer.cornerRadius = 8
435
-
436
- // Ensure the view does not clip its content or shadows
437
- viewCityList.clipsToBounds = false
438
- // Apply shadow properties
439
- viewCityList.layer.shadowColor = UIColor.black.cgColor
440
- viewCityList.layer.shadowOpacity = 0.3
441
- viewCityList.layer.shadowOffset = CGSize(width: 0, height: 2)
442
- viewCityList.layer.shadowRadius = 4
443
- // Optionally, you might want to set a corner radius to the view as well
444
- viewCityList.layer.cornerRadius = 8
445
- tblViewCityList.layer.cornerRadius = 8
446
- }
447
-
448
- //MARK: Get Country List
449
- func getCountryListApi() {
450
- let session = URLSession.shared
451
- let serviceURL = URL(string: "https://countriesnow.space/api/v0.1/countries/iso")!
452
-
453
- var request = URLRequest(url: serviceURL)
454
- request.httpMethod = "GET"
455
-
456
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
457
- if let error = error {
458
- self.hideLoadingIndicator()
459
- print("Error: \(error.localizedDescription)")
460
- return
461
- }
462
-
463
- guard let httpResponse = serviceResponse as? HTTPURLResponse, httpResponse.statusCode == 200 else {
464
- self.hideLoadingIndicator()
465
- print("Error: HTTP Status Code \(String(describing: (serviceResponse as? HTTPURLResponse)?.statusCode))")
466
- return
467
- }
468
-
469
- guard let data = serviceData else {
470
- self.hideLoadingIndicator()
471
- print("Error: No data received")
472
- return
473
- }
474
-
475
- do {
476
- if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any],
477
- let countryData = jsonResponse["data"] as? [[String: Any]] {
478
-
479
- self.countryList = countryData // Store the list of countries
480
-
481
- DispatchQueue.main.async {
482
- self.tblViewCountryList.reloadData() // Reload the table view
483
- }
484
- } else {
485
- self.hideLoadingIndicator()
486
- print("Error: Unable to parse JSON or find country data")
487
- }
488
- } catch {
489
- self.hideLoadingIndicator()
490
- print("Error parsing JSON: \(error.localizedDescription)")
491
- }
492
- }
493
- task.resume()
494
- }
495
-
496
- ///**In case of state and city name not found
497
- func getStateListApi(for country: String) {
498
- let urlString = "https://countriesnow.space/api/v0.1/countries/states"
499
- guard let serviceURL = URL(string: urlString) else {
500
- print("Invalid URL")
501
- hideLoadingIndicator()
502
- return
503
- }
504
-
505
- var request = URLRequest(url: serviceURL)
506
- request.httpMethod = "POST"
507
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
508
-
509
- let params: [String: Any] = [
510
- "country": country
511
- ]
512
-
513
- do {
514
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
515
- request.httpBody = jsonData
516
- } catch {
517
- print("Error creating JSON data: \(error)")
518
- return
519
- }
520
-
521
- let session = URLSession.shared
522
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
523
- if let error = error {
524
- print("Error: \(error.localizedDescription)")
525
- return
526
- }
527
-
528
- guard let httpResponse = serviceResponse as? HTTPURLResponse, httpResponse.statusCode == 200 else {
529
- print("Invalid response or status code")
530
- return
531
- }
532
-
533
- if let data = serviceData {
534
- do {
535
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
536
- let dataObject = responseObject["data"] as? [String: Any],
537
- let statesArray = dataObject["states"] as? [[String: Any]] {
538
-
539
- DispatchQueue.main.async {
540
- self.stateList = statesArray
541
- self.tblViewStateList.reloadData()
542
-
543
- // If no state is found, clear the state and city fields and fill country in both
544
- if statesArray.isEmpty {
545
- self.txtFieldState.text = country
546
- self.txtFieldCity.text = country
547
- } else {
548
- // Pick a random state and set it in txtFieldState
549
- if let randomState = statesArray.randomElement(),
550
- let stateName = randomState["name"] as? String {
551
- self.txtFieldState.text = stateName
552
-
553
- // Fetch cities for the randomly selected state and country
554
- self.getCityListListApi(for: country, state: stateName)
555
- }
556
- }
557
- }
558
- } else {
559
- print("Error: Invalid JSON structure")
560
- }
561
- } catch {
562
- print("Error parsing JSON: \(error)")
563
- }
564
- } else {
565
- print("No data received")
566
- }
567
- }
568
- task.resume()
569
- }
570
-
571
- ///**In case of state and city name not found
572
- func getCityListListApi(for country: String, state: String) {
573
- let urlString = "https://countriesnow.space/api/v0.1/countries/state/cities"
574
- guard let serviceURL = URL(string: urlString) else {
575
- print("Invalid URL")
576
- hideLoadingIndicator()
577
- return
578
- }
579
-
580
- var request = URLRequest(url: serviceURL)
581
- request.httpMethod = "POST"
582
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
583
-
584
- let params: [String: Any] = [
585
- "country": country,
586
- "state": state
587
- ]
588
-
589
- do {
590
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
591
- request.httpBody = jsonData
592
- } catch {
593
- print("Error creating JSON data: \(error)")
594
- return
595
- }
596
-
597
- let session = URLSession.shared
598
- let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
599
- if let error = error {
600
- print("Error: \(error.localizedDescription)")
601
- return
602
- }
603
-
604
- guard let httpResponse = serviceResponse as? HTTPURLResponse, httpResponse.statusCode == 200 else {
605
- print("Invalid response or status code")
606
- return
607
- }
608
-
609
- if let data = serviceData {
610
- do {
611
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
612
- let citiesArray = responseObject["data"] as? [String] { // Directly accessing city list array
613
-
614
- DispatchQueue.main.async {
615
- self.cityList = citiesArray.map { ["city_name": $0] } // Converting to expected format
616
- self.tblViewCityList.reloadData()
617
-
618
- // If no cities found, set state name in city field
619
- if citiesArray.isEmpty {
620
- self.txtFieldCity.text = self.txtFieldState.text
621
- } else {
622
- // Pick a random city and set it in txtFieldCity
623
- if let randomCity = citiesArray.randomElement() {
624
- self.txtFieldCity.text = randomCity
625
- }
626
- }
627
- }
628
- } else {
629
- print("Error: Invalid JSON structure")
630
- }
631
- } catch {
632
- print("Error parsing JSON: \(error)")
633
- }
634
- } else {
635
- print("No data received")
636
- }
637
- }
638
- task.resume()
639
- }
640
-
641
- func handleCountrySelection(countryName: String) {
642
- var normalizedCountry = countryName
643
- let lowercased = countryName.lowercased()
644
-
645
- // Normalize common aliases
646
- if lowercased == "usa" {
647
- normalizedCountry = "United States"
648
- }
649
-
650
- if lowercased == "us" {
651
- normalizedCountry = "United States"
652
- }
653
-
654
- // Show/hide state dropdown
655
- if lowercased == "united states" || lowercased == "usa" || lowercased == "canada" {
656
- btnSelectState.isHidden = false
657
- txtFieldState.placeholder = "Select State"
658
- } else {
659
- btnSelectState.isHidden = true
660
- txtFieldState.placeholder = "State"
661
- }
662
-
663
- // Fetch states using normalized country name
664
- getStateListApi(for: normalizedCountry)
665
- }
666
-
667
- func updateBillingInfoData() {
668
- setFieldValue("address", to: txtFieldAddress.text)
669
- setFieldValue("country", to: txtFieldCountry.text)
670
- setFieldValue("state", to: txtFieldState.text)
671
- setFieldValue("city", to: txtFieldCity.text)
672
- setFieldValue("postal_code", to: txtFieldPostalCode.text)
673
-
674
- billingInfo = fieldSection?.billing
675
- }
676
-
677
- @IBAction func actionBtnSelectCountry(_ sender: UIButton) {
678
- UIView.animate(withDuration: 0.3) {
679
- self.viewCountryList.isHidden.toggle()
680
- }
681
- }
682
-
683
- @IBAction func actionBtnSelectState(_ sender: UIButton) {
684
- isSearchingState = false
685
- filteredStateList = stateList // Reset list to full states
686
- tblViewStateList.reloadData() // Reload table with all states
687
-
688
- UIView.animate(withDuration: 0.3) {
689
- self.viewStateList.isHidden.toggle()
690
- }
691
- }
692
-
693
- @IBAction func actionBtnSelectCity(_ sender: UIButton) {
694
- UIView.animate(withDuration: 0.3) {
695
- self.viewCityList.isHidden.toggle()
696
- }
697
- }
698
-
699
- @IBAction func actionBtnPrevious(_ sender: UIButton) {
700
- // Pass the text back to the previous screen
701
- delegate?.didPassTextBack("NewAccount")
702
- self.navigationController?.popViewController(animated: true)
703
- }
704
-
705
- @IBAction func actionBtnNext(_ sender: UIButton) {
706
- guard let request = request else { return }
707
-
708
- // MARK: - Validate Billing Fields
709
- if !lblStarAddressField.isHidden &&
710
- txtFieldAddress.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
711
- showAlert(title: "Missing Information", message: "Please enter your address.")
712
- return
713
- } else if !lblStarCountryField.isHidden &&
714
- txtFieldCountry.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
715
- showAlert(title: "Missing Information", message: "Please select your country.")
716
- return
717
- } else if !lblStarStateField.isHidden &&
718
- txtFieldState.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
719
- showAlert(title: "Missing Information", message: "Please select your state.")
720
- return
721
- } else if !lblStarCityField.isHidden &&
722
- txtFieldCity.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
723
- showAlert(title: "Missing Information", message: "Please select your city.")
724
- return
725
- } else if !lblStarPostalCodeField.isHidden &&
726
- txtFieldPostalCode.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
727
- showAlert(title: "Missing Information", message: "Please enter your postal code.")
728
- return
729
- }
730
-
731
- // Update billing info
732
- updateBillingInfoData()
733
-
734
- guard let updatedFieldSection = fieldSection else {
735
- print("Missing updated fieldSection")
736
- return
737
- }
738
-
739
- let isAdditionalVisible = updatedFieldSection.visibility.additional
740
-
741
- // Get updated billingInfoData after updateBillingInfoData()
742
- guard let updatedBillingData = try? JSONEncoder().encode(fieldSection),
743
- let updatedFieldSection = try? JSONDecoder().decode(FieldSection.self, from: updatedBillingData) else {
744
- print("Failed to encode or decode updated billing info")
745
- return
746
- }
747
-
748
- // MARK: - Card Flow
749
- if selectedPaymentMethod == "Card" {
750
-
751
- if isAdditionalVisible {
752
- // ✅ Go to AdditionalInfoVC
753
- let vc = easymerchantsdk.instantiateViewController(withIdentifier: "AdditionalInfoVC") as! AdditionalInfoVC
754
- vc.cardNumber = cardNumber
755
- vc.expiryDate = expiryDate
756
- vc.cvv = cvv
757
- vc.nameOnCard = nameOnCard
758
- vc.userEmail = userEmail
759
- vc.billingInfoData = updatedBillingData
760
- vc.fieldSection = updatedFieldSection
761
- vc.billingInfo = updatedFieldSection.billing
762
- vc.additionalInfo = updatedFieldSection.additional
763
- vc.visibility = updatedFieldSection.visibility
764
- vc.selectedPaymentMethod = selectedPaymentMethod
765
- vc.isSavedForFuture = isSavedForFuture
766
- vc.amount = Double(self.request.amount ?? 0)
767
- vc.request = request
768
- vc.chosenPlan = chosenPlan
769
- vc.startDate = startDate
770
- vc.isFrom = isFrom
771
- if isFrom == "SavedCards" {
772
- vc.selectedCard = selectedCard
773
- vc.cvvText = cvvText
774
- }
775
- else if isFrom == "AddNewCard" {
776
- vc.isSavedNewCard = isSavedNewCard
777
- }
778
- navigationController?.pushViewController(vc, animated: true)
779
-
780
- }
781
- else if !isAdditionalVisible && isSavedForFuture {
782
- if UserStoreSingleton.shared.isLoggedIn == true {
783
- if request.secureAuthentication == true {
784
- threeDSecurePaymentApi()
785
- } else {
786
- paymentIntentApi()
787
- }
788
- } else {
789
- // let vc = easymerchantsdk.instantiateViewController(withIdentifier: "EmailVerificationVC") as! EmailVerificationVC
790
- let vc = easymerchantsdk.instantiateViewController(withIdentifier: "OTPVerificationVC") as! OTPVerificationVC
791
- vc.cardNumber = cardNumber
792
- vc.expiryDate = expiryDate
793
- vc.cvv = cvv
794
- vc.nameOnCard = nameOnCard
795
- vc.userEmail = userEmail
796
- vc.billingInfoData = updatedBillingData
797
- vc.fieldSection = updatedFieldSection
798
- vc.selectedPaymentMethod = selectedPaymentMethod
799
- vc.easyPayDelegate = easyPayDelegate
800
- vc.request = request
801
- vc.chosenPlan = chosenPlan
802
- vc.startDate = startDate
803
- vc.billingInfo = updatedFieldSection.billing
804
- vc.additionalInfo = updatedFieldSection.additional
805
- vc.visibility = updatedFieldSection.visibility
806
- vc.isSavedForFuture = true
807
- vc.amount = amount
808
- navigationController?.pushViewController(vc, animated: true)
809
- }
810
- }
811
- else if !isAdditionalVisible && isFrom == "SavedCards" {
812
- paymentIntentFromShowCardApi()
813
- }
814
- else if !isAdditionalVisible && isSavedNewCard {
815
- if isFrom == "AddNewCard" {
816
- if request.secureAuthentication == true {
817
- threeDSecurePaymentAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
818
- }
819
- else {
820
- paymentIntentAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
821
- }
822
- }
823
- }
824
- else {
825
- // ✅ Skip to Payment directly
826
- if request.secureAuthentication == true {
827
- threeDSecurePaymentApi()
828
- } else {
829
- paymentIntentApi()
830
- }
831
- }
832
- }
833
- else if selectedPaymentMethod == "Bank" {
834
- if isAdditionalVisible {
835
- // ✅ Go to AdditionalInfoVC
836
- let vc = easymerchantsdk.instantiateViewController(withIdentifier: "AdditionalInfoVC") as! AdditionalInfoVC
837
- vc.userEmail = userEmail
838
- vc.billingInfoData = updatedBillingData
839
- vc.fieldSection = updatedFieldSection
840
- vc.billingInfo = updatedFieldSection.billing
841
- vc.additionalInfo = updatedFieldSection.additional
842
- vc.visibility = updatedFieldSection.visibility
843
- vc.selectedPaymentMethod = selectedPaymentMethod
844
- vc.isSavedForFuture = isSavedForFuture
845
- vc.accountName = accountName
846
- vc.routingNumber = routingNumber
847
- vc.accountType = accountType
848
- vc.accountNumber = accountNumber
849
- vc.customerID = customerID
850
- vc.accountID = accountID
851
- vc.isFrom = isFrom
852
- vc.isSavedNewAccount = isSavedNewAccount
853
- vc.amount = Double(self.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
- if UserStoreSingleton.shared.isLoggedIn == true {
864
- accountChargeWithSaveApi(customerId: UserStoreSingleton.shared.customerId)
865
- } else {
866
- // let vc = easymerchantsdk.instantiateViewController(withIdentifier: "EmailVerificationVC") as! EmailVerificationVC
867
- let vc = easymerchantsdk.instantiateViewController(withIdentifier: "OTPVerificationVC") as! OTPVerificationVC
868
- vc.accountName = accountName
869
- vc.routingNumber = routingNumber
870
- vc.accountType = accountType
871
- vc.accountNumber = accountNumber
872
- vc.userEmail = userEmail
873
- vc.billingInfoData = updatedBillingData
874
- vc.fieldSection = updatedFieldSection
875
- vc.selectedPaymentMethod = selectedPaymentMethod
876
- vc.easyPayDelegate = easyPayDelegate
877
- vc.request = self.request
878
- vc.chosenPlan = chosenPlan
879
- vc.startDate = startDate
880
- vc.billingInfo = updatedFieldSection.billing
881
- vc.additionalInfo = updatedFieldSection.additional
882
- vc.visibility = updatedFieldSection.visibility
883
- vc.isSavedForFuture = isSavedForFuture
884
- vc.isSavedNewAccount = isSavedNewAccount
885
- vc.amount = amount
886
- vc.grailPayAccountID = grailPayAccountID
887
- vc.selectedGrailPayAccountType = selectedGrailPayAccountType
888
- vc.selectedGrailPayAccountName = selectedGrailPayAccountName
889
- vc.email = userEmail
890
- navigationController?.pushViewController(vc, animated: true)
891
- }
892
- }
893
- else if isFrom == "SavedBank" {
894
- accountChargeSavedBankAccountApi()
895
- }
896
- else if isFrom == "NormalBankPayWithoutSave" {
897
- accountChargeApi()
898
- }
899
- else if isFrom == "AddNewAccountWithoutSave" {
900
- accountChargeApi()
901
- }
902
- else if isFrom == "AddNewAccountWithSave" {
903
- accountChargeApi(customerId: UserStoreSingleton.shared.customerId)
904
- }
905
- }
906
- else if selectedPaymentMethod == "GrailPay" {
907
- if isAdditionalVisible {
908
- // ✅ Go to AdditionalInfoVC
909
- let vc = easymerchantsdk.instantiateViewController(withIdentifier: "AdditionalInfoVC") as! AdditionalInfoVC
910
- vc.billingInfoData = updatedBillingData
911
- vc.fieldSection = updatedFieldSection
912
- vc.billingInfo = updatedFieldSection.billing
913
- vc.additionalInfo = updatedFieldSection.additional
914
- vc.visibility = updatedFieldSection.visibility
915
- vc.selectedPaymentMethod = selectedPaymentMethod
916
- vc.isSavedForFuture = isSavedForFuture
917
- vc.isFrom = isFrom
918
- vc.isSavedNewAccount = isSavedNewAccount
919
- vc.amount = Double(self.request.amount ?? 0)
920
- vc.request = request
921
- vc.chosenPlan = chosenPlan
922
- vc.startDate = startDate
923
- vc.grailPayAccountID = grailPayAccountID
924
- vc.selectedGrailPayAccountType = selectedGrailPayAccountType
925
- vc.selectedGrailPayAccountName = selectedGrailPayAccountName
926
- vc.userEmail = userEmail
927
- navigationController?.pushViewController(vc, animated: true)
928
- }
929
- else if !isAdditionalVisible && isSavedForFuture {
930
- if UserStoreSingleton.shared.isLoggedIn == true {
931
- grailPayAccountChargeApi()
932
- } else {
933
- // let vc = easymerchantsdk.instantiateViewController(withIdentifier: "EmailVerificationVC") as! EmailVerificationVC
934
- let vc = easymerchantsdk.instantiateViewController(withIdentifier: "OTPVerificationVC") as! OTPVerificationVC
935
- vc.billingInfoData = updatedBillingData
936
- vc.fieldSection = updatedFieldSection
937
- vc.billingInfo = updatedFieldSection.billing
938
- vc.additionalInfo = updatedFieldSection.additional
939
- vc.visibility = updatedFieldSection.visibility
940
- vc.selectedPaymentMethod = selectedPaymentMethod
941
- vc.isSavedForFuture = isSavedForFuture
942
- vc.isFrom = isFrom
943
- vc.isSavedNewAccount = isSavedNewAccount
944
- vc.amount = Double(self.request.amount ?? 0)
945
- vc.request = request
946
- vc.chosenPlan = chosenPlan
947
- vc.startDate = startDate
948
- vc.grailPayAccountID = grailPayAccountID
949
- vc.selectedGrailPayAccountType = selectedGrailPayAccountType
950
- vc.selectedGrailPayAccountName = selectedGrailPayAccountName
951
- vc.userEmail = userEmail
952
- vc.email = userEmail
953
-
954
- vc.grailPayAccountID = self.grailPayAccountID
955
- vc.selectedGrailPayAccountType = self.selectedGrailPayAccountType
956
- vc.selectedGrailPayAccountName = self.selectedGrailPayAccountName
957
- navigationController?.pushViewController(vc, animated: true)
958
- }
959
- }
960
- else {
961
- grailPayAccountChargeApi()
962
- }
963
- }
964
- else if selectedPaymentMethod == "NewGrailPayAccount" {
965
- if isAdditionalVisible {
966
- // ✅ Go to AdditionalInfoVC
967
- let vc = easymerchantsdk.instantiateViewController(withIdentifier: "AdditionalInfoVC") as! AdditionalInfoVC
968
- vc.billingInfoData = updatedBillingData
969
- vc.fieldSection = updatedFieldSection
970
- vc.billingInfo = updatedFieldSection.billing
971
- vc.additionalInfo = updatedFieldSection.additional
972
- vc.visibility = updatedFieldSection.visibility
973
- vc.selectedPaymentMethod = selectedPaymentMethod
974
- vc.isSavedForFuture = isSavedForFuture
975
- vc.isFrom = isFrom
976
- vc.isSavedNewAccount = isSavedNewAccount
977
- vc.amount = Double(self.request.amount ?? 0)
978
- vc.request = request
979
- vc.chosenPlan = chosenPlan
980
- vc.startDate = startDate
981
- vc.grailPayAccountID = grailPayAccountID
982
- vc.selectedGrailPayAccountType = selectedGrailPayAccountType
983
- vc.selectedGrailPayAccountName = selectedGrailPayAccountName
984
- navigationController?.pushViewController(vc, animated: true)
985
- }
986
- else if !isAdditionalVisible && isSavedForFuture {
987
- grailPayAccountChargeApi(customerId: UserStoreSingleton.shared.customerId)
988
- }
989
- else {
990
- grailPayAccountChargeApi()
991
- }
992
- }
993
- }
994
-
995
- // MARK: - Credit Card Charge Api
996
- func paymentIntentApi() {
997
- showLoadingIndicator()
998
-
999
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
1000
-
1001
- guard let serviceURL = URL(string: fullURL) else {
1002
- print("Invalid URL")
1003
- hideLoadingIndicator()
1004
- return
1005
- }
1006
-
1007
- var urlRequest = URLRequest(url: serviceURL)
1008
- urlRequest.httpMethod = "POST"
1009
- urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1010
-
1011
- let token = UserStoreSingleton.shared.clientToken
1012
- print("Setting clientToken header: \(token ?? "None")")
1013
- urlRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1014
-
1015
- var params: [String: Any] = [
1016
- "name": nameOnCard ?? "",
1017
- "email": userEmail ?? "",
1018
- "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
1019
- "cardholder_name": nameOnCard ?? "",
1020
- "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
1021
- "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
1022
- "cvc": cvv ?? "",
1023
- "currency": "usd",
1024
- "description": "Hosted payment checkout"
1025
- ]
1026
-
1027
- // Conditionally add billing info
1028
- if let visibility = visibility, visibility.billing == true,
1029
- let billing = billingInfo, !billing.isEmpty {
1030
-
1031
- var billingInfoDict: [String: Any] = [:]
1032
- for item in billing {
1033
- billingInfoDict[item.name] = item.value
1034
- }
1035
-
1036
- params["address"] = billingInfoDict["address"] as? String ?? ""
1037
- params["country"] = billingInfoDict["country"] as? String ?? ""
1038
- params["state"] = billingInfoDict["state"] as? String ?? ""
1039
- params["city"] = billingInfoDict["city"] as? String ?? ""
1040
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
1041
- }
1042
-
1043
- // Add these if recurring is enabled
1044
- // if let req = request, req.is_recurring == true {
1045
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
1046
- // // Only send start_date if type is .custom and field is not empty
1047
- // if let startDateText = startDate, !startDateText.isEmpty {
1048
- // let inputFormatter = DateFormatter()
1049
- // inputFormatter.dateFormat = "dd/MM/yyyy"
1050
- //
1051
- // let outputFormatter = DateFormatter()
1052
- // outputFormatter.dateFormat = "MM/dd/yyyy"
1053
- //
1054
- // if let date = inputFormatter.date(from: startDateText) {
1055
- // let apiFormattedDate = outputFormatter.string(from: date)
1056
- // params["start_date"] = apiFormattedDate
1057
- // } else {
1058
- // print("Invalid date format in startDateText")
1059
- // }
1060
- // }
1061
- // }
1062
- //
1063
- // params["interval"] = chosenPlan?.lowercased()
1064
- // }
1065
-
1066
- // Add these if recurring is enabled
1067
- if let req = request, req.is_recurring == true {
1068
- if let startDateText = startDate, !startDateText.isEmpty {
1069
- let inputFormatter = DateFormatter()
1070
- inputFormatter.dateFormat = "dd/MM/yyyy"
1071
-
1072
- let outputFormatter = DateFormatter()
1073
- outputFormatter.dateFormat = "MM/dd/yyyy"
1074
-
1075
- if let date = inputFormatter.date(from: startDateText) {
1076
- let apiFormattedDate = outputFormatter.string(from: date)
1077
- params["start_date"] = apiFormattedDate
1078
- } else {
1079
- print("Invalid date format in startDateText")
1080
- }
1081
- }
1082
-
1083
- // interval is still required
1084
- params["interval"] = chosenPlan?.lowercased()
1085
- }
1086
-
1087
- // ✅ Include metadata only if it has at least 1 key-value pair
1088
- if let metadata = request?.metadata, !metadata.isEmpty {
1089
- params["metadata"] = metadata
1090
- }
1091
-
1092
- // ✅ Include metadata only if it has at least 1 key-value pair
1093
- if let metadata = request?.metadata, !metadata.isEmpty {
1094
- params["metadata"] = metadata
1095
- }
1096
-
1097
- // ✅ Only for logged-in users
1098
- if UserStoreSingleton.shared.isLoggedIn == true {
1099
- let emailText = userEmail
1100
- let emailPrefix = emailText?.components(separatedBy: "@").first ?? ""
1101
-
1102
- params["save_card"] = 1
1103
- params["is_default"] = "1"
1104
- params["tokenize"] = request.tokenOnly ?? ""
1105
- params["username"] = emailPrefix
1106
-
1107
- if let customerId = UserStoreSingleton.shared.customerId {
1108
- params["customer"] = customerId
1109
- params["customer_id"] = customerId
1110
- } else {
1111
- params["create_customer"] = "1"
1112
- }
1113
-
1114
- if UserStoreSingleton.shared.customerId == nil {
1115
- params["create_customer"] = "1"
1116
- }
1117
- }
1118
-
1119
- print(params)
1120
-
1121
- do {
1122
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1123
- urlRequest.httpBody = jsonData
1124
- if let jsonString = String(data: jsonData, encoding: .utf8) {
1125
- print("JSON Payload: \(jsonString)")
1126
- }
1127
- } catch let error {
1128
- print("Error creating JSON data: \(error)")
1129
- hideLoadingIndicator()
1130
- return
1131
- }
1132
-
1133
- let session = URLSession.shared
1134
- let task = session.dataTask(with: urlRequest) { (serviceData, serviceResponse, error) in
1135
-
1136
- DispatchQueue.main.async {
1137
- self.hideLoadingIndicator()
1138
- }
1139
-
1140
- if let error = error {
1141
- print("Error: \(error.localizedDescription)")
1142
- self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
1143
- return
1144
- }
1145
-
1146
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1147
- print("Invalid response")
1148
- self.presentPaymentErrorVC(errorMessage: "Invalid response from server.")
1149
- return
1150
- }
1151
-
1152
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1153
- if let data = serviceData {
1154
- do {
1155
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1156
- print("Response Data: \(responseObject)")
1157
- // ✅ Handle duplicate transaction case
1158
- if let status = responseObject["status"] as? Bool, status == false,
1159
- let message = responseObject["message"] as? String,
1160
- message.lowercased().contains("duplicate transaction") {
1161
- self.presentPaymentErrorVC(errorMessage: message)
1162
- return
1163
- }
1164
-
1165
- if let status = responseObject["status"] as? Int, status == 0,
1166
- let message = responseObject["message"] as? String,
1167
- message.lowercased().contains("duplicate transaction") {
1168
- self.presentPaymentErrorVC(errorMessage: message)
1169
- return
1170
- }
1171
-
1172
- // ✅ Handle generic "status == 0" error case
1173
- if let status = responseObject["status"] as? Int, status == 0 {
1174
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1175
- self.presentPaymentErrorVC(errorMessage: errorMessage)
1176
- return
1177
- }
1178
- else {
1179
- DispatchQueue.main.async {
1180
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
1181
- paymentDoneVC.chargeData = responseObject
1182
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
1183
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1184
- paymentDoneVC.visibility = self.visibility
1185
- paymentDoneVC.request = self.request
1186
-
1187
- // if self.visibility?.billing == true {
1188
- paymentDoneVC.billingInfoData = self.billingInfo
1189
- var billingDict: [String: Any] = [:]
1190
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
1191
- paymentDoneVC.billingInfo = billingDict
1192
- // }
1193
-
1194
- // if self.visibility?.additional == true {
1195
- paymentDoneVC.additionalInfoData = self.additionalInfo
1196
- var additionalDict: [String: Any] = [:]
1197
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
1198
- paymentDoneVC.additionalInfo = additionalDict
1199
- // }
1200
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1201
- }
1202
- }
1203
- }
1204
- } else {
1205
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1206
- }
1207
- } catch let jsonError {
1208
- self.presentPaymentErrorVC(errorMessage: "Error parsing response: \(jsonError.localizedDescription)")
1209
- }
1210
- } else {
1211
- self.presentPaymentErrorVC(errorMessage: "No data received from server.")
1212
- }
1213
- } else {
1214
- if let data = serviceData,
1215
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1216
- let message = responseObj["message"] as? String {
1217
- self.presentPaymentErrorVC(errorMessage: message)
1218
- } else {
1219
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1220
- }
1221
- }
1222
- }
1223
- task.resume()
1224
- }
1225
-
1226
- func presentPaymentErrorVC(errorMessage: String) {
1227
- DispatchQueue.main.async {
1228
- if let paymentErrorVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentErrorVC") as? PaymentErrorVC {
1229
- paymentErrorVC.errorMessage = errorMessage
1230
- paymentErrorVC.easyPayDelegate = self.easyPayDelegate // Pass the reference here
1231
- self.navigationController?.pushViewController(paymentErrorVC, animated: true)
1232
- }
1233
- }
1234
- }
1235
-
1236
- //MARK: - 3DSecure
1237
- // MARK: - Credit Card Charge Api If Billing info is not nil and Without Login.
1238
- func threeDSecurePaymentApi() {
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
- var params: [String: Any] = [
1258
- "name": nameOnCard ?? "",
1259
- "email": userEmail ?? "",
1260
- "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
1261
- "cardholder_name": nameOnCard ?? "",
1262
- "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
1263
- "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
1264
- "cvc": cvv ?? "",
1265
- "currency": "usd",
1266
- "tokenize": request.tokenOnly ?? false
1267
- ]
1268
-
1269
- // ✅ Only for logged-in users
1270
- if UserStoreSingleton.shared.isLoggedIn == true {
1271
- let emailText = userEmail
1272
- let emailPrefix = emailText?.components(separatedBy: "@").first ?? ""
1273
-
1274
- params["save_card"] = 1
1275
- params["is_default"] = "1"
1276
- params["tokenize"] = request.tokenOnly ?? ""
1277
- params["username"] = emailPrefix
1278
-
1279
- if let customerId = UserStoreSingleton.shared.customerId {
1280
- params["customer"] = customerId
1281
- params["customer_id"] = customerId
1282
- } else {
1283
- params["create_customer"] = "1"
1284
- }
1285
-
1286
- if UserStoreSingleton.shared.customerId == nil {
1287
- params["create_customer"] = "1"
1288
- }
1289
- }
1290
-
1291
- // Conditionally add billing info
1292
- if let visibility = visibility, visibility.billing == true,
1293
- let billing = billingInfo, !billing.isEmpty {
1294
-
1295
- var billingInfoDict: [String: Any] = [:]
1296
- for item in billing {
1297
- billingInfoDict[item.name] = item.value
1298
- }
1299
-
1300
- params["address"] = billingInfoDict["address"] as? String ?? ""
1301
- params["country"] = billingInfoDict["country"] as? String ?? ""
1302
- params["state"] = billingInfoDict["state"] as? String ?? ""
1303
- params["city"] = billingInfoDict["city"] as? String ?? ""
1304
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
1305
- }
1306
-
1307
- // Set default description if additional info is not visible
1308
- if let visibility = visibility, visibility.additional == false {
1309
- params["description"] = "Hosted payment checkout"
1310
- }
1311
-
1312
- // Add these if recurring is enabled
1313
- // if let req = request, req.is_recurring == true {
1314
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
1315
- // // Only send start_date if type is .custom and field is not empty
1316
- // if let startDateText = startDate, !startDateText.isEmpty {
1317
- // let inputFormatter = DateFormatter()
1318
- // inputFormatter.dateFormat = "dd/MM/yyyy"
1319
- //
1320
- // let outputFormatter = DateFormatter()
1321
- // outputFormatter.dateFormat = "MM/dd/yyyy"
1322
- //
1323
- // if let date = inputFormatter.date(from: startDateText) {
1324
- // let apiFormattedDate = outputFormatter.string(from: date)
1325
- // params["start_date"] = apiFormattedDate
1326
- // } else {
1327
- // print("Invalid date format in startDateText")
1328
- // }
1329
- // }
1330
- // }
1331
- //
1332
- // params["interval"] = chosenPlan?.lowercased()
1333
- // }
1334
-
1335
- // Add these if recurring is enabled
1336
- if let req = request, req.is_recurring == true {
1337
- if let startDateText = startDate, !startDateText.isEmpty {
1338
- let inputFormatter = DateFormatter()
1339
- inputFormatter.dateFormat = "dd/MM/yyyy"
1340
-
1341
- let outputFormatter = DateFormatter()
1342
- outputFormatter.dateFormat = "MM/dd/yyyy"
1343
-
1344
- if let date = inputFormatter.date(from: startDateText) {
1345
- let apiFormattedDate = outputFormatter.string(from: date)
1346
- params["start_date"] = apiFormattedDate
1347
- } else {
1348
- print("Invalid date format in startDateText")
1349
- }
1350
- }
1351
-
1352
- // interval is still required
1353
- params["interval"] = chosenPlan?.lowercased()
1354
- }
1355
-
1356
- // ✅ Include metadata only if it has at least 1 key-value pair
1357
- if let metadata = request?.metadata, !metadata.isEmpty {
1358
- params["metadata"] = metadata
1359
- }
1360
-
1361
- print(params)
1362
-
1363
- do {
1364
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1365
- uRLRequest.httpBody = jsonData
1366
- if let jsonString = String(data: jsonData, encoding: .utf8) {
1367
- print("JSON Payload: \(jsonString)")
1368
- }
1369
- } catch let error {
1370
- print("Error creating JSON data: \(error)")
1371
- hideLoadingIndicator()
1372
- return
1373
- }
1374
-
1375
- let session = URLSession.shared
1376
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1377
-
1378
- DispatchQueue.main.async {
1379
- self.hideLoadingIndicator() // Stop loader when response is received
1380
- }
1381
-
1382
- if let error = error {
1383
- print("Error: \(error.localizedDescription)")
1384
- return
1385
- }
1386
-
1387
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1388
- print("Invalid response")
1389
- return
1390
- }
1391
-
1392
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1393
- if let data = serviceData {
1394
- do {
1395
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1396
- print("Response Data: \(responseObject)")
1397
-
1398
- // ✅ Handle duplicate transaction case
1399
- if let status = responseObject["status"] as? Bool, status == false,
1400
- let message = responseObject["message"] as? String,
1401
- message.lowercased().contains("duplicate transaction") {
1402
- self.presentPaymentErrorVC(errorMessage: message)
1403
- return
1404
- }
1405
-
1406
- if let status = responseObject["status"] as? Int, status == 0,
1407
- let message = responseObject["message"] as? String,
1408
- message.lowercased().contains("duplicate transaction") {
1409
- self.presentPaymentErrorVC(errorMessage: message)
1410
- return
1411
- }
1412
-
1413
- // ✅ Handle generic "status == 0" error case
1414
- if let status = responseObject["status"] as? Int, status == 0 {
1415
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1416
- self.presentPaymentErrorVC(errorMessage: errorMessage)
1417
- return
1418
- }
1419
- else {
1420
- DispatchQueue.main.async {
1421
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "ThreeDSecurePaymentDoneVC") as? ThreeDSecurePaymentDoneVC {
1422
-
1423
- let urlString = responseObject["redirect_url"] as? String ?? responseObject["location_url"] as? String ?? ""
1424
- paymentDoneVC.redirectURL = urlString
1425
- paymentDoneVC.chargeData = responseObject
1426
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1427
- // Pass billing and additional info
1428
- // Conditionally pass raw FieldItem array
1429
- paymentDoneVC.visibility = self.visibility
1430
- paymentDoneVC.amount = self.amount
1431
- paymentDoneVC.cardApiParams = params
1432
- paymentDoneVC.request = self.request
1433
-
1434
- // if self.visibility?.billing == true {
1435
- paymentDoneVC.billingInfoData = self.billingInfo
1436
- var billingDict: [String: Any] = [:]
1437
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
1438
- paymentDoneVC.billingInfo = billingDict
1439
- // }
1440
-
1441
- // if self.visibility?.additional == true {
1442
- paymentDoneVC.additionalInfoData = self.additionalInfo
1443
- var additionalDict: [String: Any] = [:]
1444
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
1445
- paymentDoneVC.additionalInfo = additionalDict
1446
- // }
1447
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1448
- }
1449
- }
1450
- }
1451
- } else {
1452
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1453
- }
1454
- } catch let jsonError {
1455
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1456
- }
1457
- } else {
1458
- self.presentPaymentErrorVC(errorMessage: "No data received")
1459
- }
1460
- } else {
1461
- if let data = serviceData,
1462
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1463
- let message = responseObj["message"] as? String {
1464
- self.presentPaymentErrorVC(errorMessage: message)
1465
- } else {
1466
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1467
- }
1468
- }
1469
- }
1470
- task.resume()
1471
- }
1472
-
1473
- // MARK: - Credit Card Charge Api If Billing info is not nil With Login from Add New Card.
1474
- func threeDSecurePaymentAddNewCardApi(customerId: String?) {
1475
- showLoadingIndicator()
1476
-
1477
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecure.path()
1478
-
1479
- guard let serviceURL = URL(string: fullURL) else {
1480
- print("Invalid URL")
1481
- hideLoadingIndicator()
1482
- return
1483
- }
1484
-
1485
- var uRLRequest = URLRequest(url: serviceURL)
1486
- uRLRequest.httpMethod = "POST"
1487
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1488
-
1489
- let token = UserStoreSingleton.shared.clientToken
1490
- print("Setting clientToken header: \(token ?? "None")")
1491
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1492
-
1493
- var params: [String: Any] = [
1494
- "name": nameOnCard ?? "",
1495
- "email": userEmail ?? "",
1496
- "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
1497
- "cardholder_name": nameOnCard ?? "",
1498
- "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
1499
- "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
1500
- "cvc": cvv ?? "",
1501
- "description": "Hosted payment checkout",
1502
- "currency": "usd",
1503
- "tokenize": request.tokenOnly ?? false,
1504
- "save_card": isSavedNewCard ? 1 : 0,
1505
- "customer_id": customerId ?? ""
1506
- ]
1507
-
1508
- // Add is_default parameter if save_card is 1
1509
- if isSavedNewCard {
1510
- params["is_default"] = "1"
1511
- }
1512
-
1513
- // Conditionally add billing info
1514
- if let visibility = visibility, visibility.billing == true,
1515
- let billing = billingInfo, !billing.isEmpty {
1516
-
1517
- var billingInfoDict: [String: Any] = [:]
1518
- for item in billing {
1519
- billingInfoDict[item.name] = item.value
1520
- }
1521
-
1522
- params["address"] = billingInfoDict["address"] as? String ?? ""
1523
- params["country"] = billingInfoDict["country"] as? String ?? ""
1524
- params["state"] = billingInfoDict["state"] as? String ?? ""
1525
- params["city"] = billingInfoDict["city"] as? String ?? ""
1526
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
1527
- }
1528
-
1529
- // Add these if recurring is enabled
1530
- // if let req = request, req.is_recurring == true {
1531
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
1532
- // // Only send start_date if type is .custom and field is not empty
1533
- // if let startDateText = startDate, !startDateText.isEmpty {
1534
- // let inputFormatter = DateFormatter()
1535
- // inputFormatter.dateFormat = "dd/MM/yyyy"
1536
- //
1537
- // let outputFormatter = DateFormatter()
1538
- // outputFormatter.dateFormat = "MM/dd/yyyy"
1539
- //
1540
- // if let date = inputFormatter.date(from: startDateText) {
1541
- // let apiFormattedDate = outputFormatter.string(from: date)
1542
- // params["start_date"] = apiFormattedDate
1543
- // } else {
1544
- // print("Invalid date format in startDateText")
1545
- // }
1546
- // }
1547
- // }
1548
- //
1549
- // params["interval"] = chosenPlan?.lowercased()
1550
- // }
1551
-
1552
- // Add these if recurring is enabled
1553
- if let req = request, req.is_recurring == true {
1554
- if let startDateText = startDate, !startDateText.isEmpty {
1555
- let inputFormatter = DateFormatter()
1556
- inputFormatter.dateFormat = "dd/MM/yyyy"
1557
-
1558
- let outputFormatter = DateFormatter()
1559
- outputFormatter.dateFormat = "MM/dd/yyyy"
1560
-
1561
- if let date = inputFormatter.date(from: startDateText) {
1562
- let apiFormattedDate = outputFormatter.string(from: date)
1563
- params["start_date"] = apiFormattedDate
1564
- } else {
1565
- print("Invalid date format in startDateText")
1566
- }
1567
- }
1568
-
1569
- // interval is still required
1570
- params["interval"] = chosenPlan?.lowercased()
1571
- }
1572
-
1573
- // ✅ Include metadata only if it has at least 1 key-value pair
1574
- if let metadata = request?.metadata, !metadata.isEmpty {
1575
- params["metadata"] = metadata
1576
- }
1577
-
1578
- do {
1579
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1580
- uRLRequest.httpBody = jsonData
1581
- if let jsonString = String(data: jsonData, encoding: .utf8) {
1582
- print("JSON Payload: \(jsonString)")
1583
- }
1584
- } catch let error {
1585
- print("Error creating JSON data: \(error)")
1586
- hideLoadingIndicator()
1587
- return
1588
- }
1589
-
1590
- let session = URLSession.shared
1591
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1592
-
1593
- DispatchQueue.main.async {
1594
- self.hideLoadingIndicator() // Stop loader when response is received
1595
- }
1596
-
1597
- if let error = error {
1598
- print("Error: \(error.localizedDescription)")
1599
- return
1600
- }
1601
-
1602
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1603
- print("Invalid response")
1604
- return
1605
- }
1606
-
1607
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1608
- if let data = serviceData {
1609
- do {
1610
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1611
- print("Response Data: \(responseObject)")
1612
-
1613
- // ✅ Handle duplicate transaction case
1614
- if let status = responseObject["status"] as? Bool, status == false,
1615
- let message = responseObject["message"] as? String,
1616
- message.lowercased().contains("duplicate transaction") {
1617
- self.presentPaymentErrorVC(errorMessage: message)
1618
- return
1619
- }
1620
-
1621
- if let status = responseObject["status"] as? Int, status == 0,
1622
- let message = responseObject["message"] as? String,
1623
- message.lowercased().contains("duplicate transaction") {
1624
- self.presentPaymentErrorVC(errorMessage: message)
1625
- return
1626
- }
1627
-
1628
- // ✅ Handle generic "status == 0" error case
1629
- if let status = responseObject["status"] as? Int, status == 0 {
1630
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1631
- self.presentPaymentErrorVC(errorMessage: errorMessage)
1632
- return
1633
- }
1634
- else {
1635
- DispatchQueue.main.async {
1636
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "ThreeDSecurePaymentDoneVC") as? ThreeDSecurePaymentDoneVC {
1637
-
1638
- let urlString = responseObject["redirect_url"] as? String ?? responseObject["location_url"] as? String ?? ""
1639
- paymentDoneVC.redirectURL = urlString
1640
- paymentDoneVC.chargeData = responseObject
1641
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1642
- // Pass billing and additional info
1643
- // Conditionally pass raw FieldItem array
1644
- paymentDoneVC.visibility = self.visibility
1645
- paymentDoneVC.amount = self.amount
1646
- paymentDoneVC.cardApiParams = params
1647
- paymentDoneVC.request = self.request
1648
-
1649
- // if self.visibility?.billing == true {
1650
- paymentDoneVC.billingInfoData = self.billingInfo
1651
- var billingDict: [String: Any] = [:]
1652
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
1653
- paymentDoneVC.billingInfo = billingDict
1654
- // }
1655
-
1656
- // if self.visibility?.additional == true {
1657
- paymentDoneVC.additionalInfoData = self.additionalInfo
1658
- var additionalDict: [String: Any] = [:]
1659
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
1660
- paymentDoneVC.additionalInfo = additionalDict
1661
- // }
1662
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1663
- }
1664
- }
1665
- }
1666
- } else {
1667
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1668
- }
1669
- } catch let jsonError {
1670
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1671
- }
1672
- } else {
1673
- self.presentPaymentErrorVC(errorMessage: "No data received")
1674
- }
1675
- } else {
1676
- if let data = serviceData,
1677
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1678
- let message = responseObj["message"] as? String {
1679
- self.presentPaymentErrorVC(errorMessage: message)
1680
- } else {
1681
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1682
- }
1683
- }
1684
- }
1685
- task.resume()
1686
- }
1687
-
1688
- //MARK: - Credit Card Charge Api from Add new card from saved cards.
1689
- func paymentIntentAddNewCardApi(customerId: String?) {
1690
- showLoadingIndicator()
1691
-
1692
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
1693
-
1694
- guard let serviceURL = URL(string: fullURL) else {
1695
- print("Invalid URL")
1696
- hideLoadingIndicator()
1697
- return
1698
- }
1699
-
1700
- var uRLRequest = URLRequest(url: serviceURL)
1701
- uRLRequest.httpMethod = "POST"
1702
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1703
-
1704
- let token = UserStoreSingleton.shared.clientToken
1705
- print("Setting clientToken header: \(token ?? "None")")
1706
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1707
-
1708
- // let emailPrefix = UserStoreSingleton.shared.verificationEmail?.components(separatedBy: "@").first ?? ""
1709
-
1710
- let finalEmail: String
1711
- if let verificationEmail = UserStoreSingleton.shared.verificationEmail, !verificationEmail.isEmpty {
1712
- finalEmail = verificationEmail
1713
- } else {
1714
- finalEmail = request.email ?? ""
1715
- }
1716
-
1717
- let emailPrefix: String
1718
- if !finalEmail.isEmpty {
1719
- emailPrefix = finalEmail.components(separatedBy: "@").first ?? ""
1720
- } else {
1721
- emailPrefix = ""
1722
- }
1723
-
1724
- var params: [String: Any] = [
1725
- "name": nameOnCard ?? "",
1726
- "email": userEmail ?? "",
1727
- "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
1728
- "cardholder_name": nameOnCard ?? "",
1729
- "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
1730
- "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
1731
- "cvc": cvv ?? "",
1732
- "currency": "usd",
1733
- "payment_method": selectedPaymentMethod ?? "",
1734
- "save_card": isSavedNewCard ? 1 : 0
1735
- ]
1736
-
1737
- // Add is_default parameter if save_card is 1
1738
- if isSavedNewCard {
1739
- params["is_default"] = "1"
1740
- }
1741
-
1742
- // Conditionally add billing info
1743
- if let visibility = visibility, visibility.billing == true,
1744
- let billing = billingInfo, !billing.isEmpty {
1745
-
1746
- var billingInfoDict: [String: Any] = [:]
1747
- for item in billing {
1748
- billingInfoDict[item.name] = item.value
1749
- }
1750
-
1751
- params["address"] = billingInfoDict["address"] as? String ?? ""
1752
- params["country"] = billingInfoDict["country"] as? String ?? ""
1753
- params["state"] = billingInfoDict["state"] as? String ?? ""
1754
- params["city"] = billingInfoDict["city"] as? String ?? ""
1755
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
1756
- }
1757
-
1758
- // Set default description if additional info is not visible
1759
- if let visibility = visibility, visibility.additional == false {
1760
- params["description"] = "Hosted payment checkout"
1761
- }
1762
-
1763
- // Add these if recurring is enabled
1764
- // if let req = request, req.is_recurring == true {
1765
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
1766
- // // Only send start_date if type is .custom and field is not empty
1767
- // if let startDateText = startDate, !startDateText.isEmpty {
1768
- // let inputFormatter = DateFormatter()
1769
- // inputFormatter.dateFormat = "dd/MM/yyyy"
1770
- //
1771
- // let outputFormatter = DateFormatter()
1772
- // outputFormatter.dateFormat = "MM/dd/yyyy"
1773
- //
1774
- // if let date = inputFormatter.date(from: startDateText) {
1775
- // let apiFormattedDate = outputFormatter.string(from: date)
1776
- // params["start_date"] = apiFormattedDate
1777
- // } else {
1778
- // print("Invalid date format in startDateText")
1779
- // }
1780
- // }
1781
- // }
1782
- //
1783
- // params["interval"] = chosenPlan?.lowercased()
1784
- // }
1785
-
1786
- // Add these if recurring is enabled
1787
- if let req = request, req.is_recurring == true {
1788
- if let startDateText = startDate, !startDateText.isEmpty {
1789
- let inputFormatter = DateFormatter()
1790
- inputFormatter.dateFormat = "dd/MM/yyyy"
1791
-
1792
- let outputFormatter = DateFormatter()
1793
- outputFormatter.dateFormat = "MM/dd/yyyy"
1794
-
1795
- if let date = inputFormatter.date(from: startDateText) {
1796
- let apiFormattedDate = outputFormatter.string(from: date)
1797
- params["start_date"] = apiFormattedDate
1798
- } else {
1799
- print("Invalid date format in startDateText")
1800
- }
1801
- }
1802
-
1803
- // interval is still required
1804
- params["interval"] = chosenPlan?.lowercased()
1805
- }
1806
-
1807
- if let customerId = customerId {
1808
- params["customer"] = customerId
1809
- params["customer_id"] = customerId
1810
- } else {
1811
- params["username"] = emailPrefix
1812
- params["email"] = finalEmail
1813
- }
1814
-
1815
- // ✅ Include metadata only if it has at least 1 key-value pair
1816
- if let metadata = request?.metadata, !metadata.isEmpty {
1817
- params["metadata"] = metadata
1818
- }
1819
-
1820
- print(params)
1821
-
1822
- do {
1823
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1824
- uRLRequest.httpBody = jsonData
1825
- if let jsonString = String(data: jsonData, encoding: .utf8) {
1826
- print("JSON Payload: \(jsonString)")
1827
- }
1828
- } catch let error {
1829
- print("Error creating JSON data: \(error)")
1830
- hideLoadingIndicator()
1831
- return
1832
- }
1833
-
1834
- let session = URLSession.shared
1835
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1836
-
1837
- DispatchQueue.main.async {
1838
- self.hideLoadingIndicator() // Stop loader when response is received
1839
- }
1840
-
1841
- if let error = error {
1842
- self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
1843
- return
1844
- }
1845
-
1846
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1847
- self.presentPaymentErrorVC(errorMessage: "Invalid response")
1848
- return
1849
- }
1850
-
1851
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1852
- if let data = serviceData {
1853
- do {
1854
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1855
- print("Response Data: \(responseObject)")
1856
-
1857
- // ✅ Handle duplicate transaction case
1858
- if let status = responseObject["status"] as? Bool, status == false,
1859
- let message = responseObject["message"] as? String,
1860
- message.lowercased().contains("duplicate transaction") {
1861
- self.presentPaymentErrorVC(errorMessage: message)
1862
- return
1863
- }
1864
-
1865
- if let status = responseObject["status"] as? Int, status == 0,
1866
- let message = responseObject["message"] as? String,
1867
- message.lowercased().contains("duplicate transaction") {
1868
- self.presentPaymentErrorVC(errorMessage: message)
1869
- return
1870
- }
1871
-
1872
- // ✅ Handle generic "status == 0" error case
1873
- if let status = responseObject["status"] as? Int, status == 0 {
1874
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1875
- self.presentPaymentErrorVC(errorMessage: errorMessage)
1876
- return
1877
- }
1878
- else {
1879
- DispatchQueue.main.async {
1880
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
1881
- paymentDoneVC.chargeData = responseObject
1882
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
1883
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1884
- // Pass billing and additional info
1885
- // Conditionally pass raw FieldItem array
1886
- paymentDoneVC.visibility = self.visibility
1887
- paymentDoneVC.request = self.request
1888
-
1889
- // if self.visibility?.billing == true {
1890
- paymentDoneVC.billingInfoData = self.billingInfo
1891
- var billingDict: [String: Any] = [:]
1892
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
1893
- paymentDoneVC.billingInfo = billingDict
1894
- // }
1895
-
1896
- // if self.visibility?.additional == true {
1897
- paymentDoneVC.additionalInfoData = self.additionalInfo
1898
- var additionalDict: [String: Any] = [:]
1899
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
1900
- paymentDoneVC.additionalInfo = additionalDict
1901
- // }
1902
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1903
- }
1904
- }
1905
- }
1906
- } else {
1907
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1908
- }
1909
- } catch let jsonError {
1910
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1911
- }
1912
- } else {
1913
- self.presentPaymentErrorVC(errorMessage: "No data received")
1914
- }
1915
- } else {
1916
- if let data = serviceData,
1917
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1918
- let message = responseObj["message"] as? String {
1919
- self.presentPaymentErrorVC(errorMessage: message)
1920
- } else {
1921
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1922
- }
1923
- }
1924
- }
1925
- task.resume()
1926
- }
1927
-
1928
- //MARK: - Credit Card Charge Api from Saved cards
1929
- func paymentIntentFromShowCardApi() {
1930
- showLoadingIndicator()
1931
-
1932
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
1933
-
1934
- guard let serviceURL = URL(string: fullURL) else {
1935
- print("Invalid URL")
1936
- hideLoadingIndicator()
1937
- return
1938
- }
1939
-
1940
- var uRLRequest = URLRequest(url: serviceURL)
1941
- uRLRequest.httpMethod = "POST"
1942
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1943
-
1944
- let token = UserStoreSingleton.shared.clientToken
1945
- print("Setting clientToken header: \(token ?? "None")")
1946
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1947
-
1948
- // let emailText = UserStoreSingleton.shared.verificationEmail ?? ""
1949
- // let emailPrefix = emailText.components(separatedBy: "@").first ?? ""
1950
-
1951
- let finalEmail: String
1952
- if let verificationEmail = UserStoreSingleton.shared.verificationEmail, !verificationEmail.isEmpty {
1953
- finalEmail = verificationEmail
1954
- } else {
1955
- finalEmail = request.email ?? ""
1956
- }
1957
-
1958
- let emailPrefix: String
1959
- if !finalEmail.isEmpty {
1960
- emailPrefix = finalEmail.components(separatedBy: "@").first ?? ""
1961
- } else {
1962
- emailPrefix = ""
1963
- }
1964
-
1965
- // Determine name: use request.name if available, otherwise fallback to email prefix
1966
- let nameParam: String
1967
- if let requestName = request.name, !requestName.trimmingCharacters(in: .whitespaces).isEmpty {
1968
- nameParam = requestName
1969
- } else {
1970
- nameParam = emailPrefix
1971
- }
1972
-
1973
- var params: [String: Any] = [
1974
- "description": "Hosted payment checkout",
1975
- "currency": "usd",
1976
- "payment_method": "card",
1977
- "save_card": 0,
1978
- "customer" : selectedCard?.customerId ?? "",
1979
- "customer_id" : selectedCard?.customerId ?? "",
1980
- "card_id" : selectedCard?.cardId ?? "",
1981
- "cvc" : cvvText ?? "",
1982
- // "name": UserStoreSingleton.shared.merchantName ?? "",
1983
- "name": nameParam,
1984
- "email": finalEmail
1985
- ]
1986
-
1987
- // Conditionally add billing info
1988
- if let visibility = visibility, visibility.billing == true,
1989
- let billing = billingInfo, !billing.isEmpty {
1990
-
1991
- var billingInfoDict: [String: Any] = [:]
1992
- for item in billing {
1993
- billingInfoDict[item.name] = item.value
1994
- }
1995
-
1996
- params["address"] = billingInfoDict["address"] as? String ?? ""
1997
- params["country"] = billingInfoDict["country"] as? String ?? ""
1998
- params["state"] = billingInfoDict["state"] as? String ?? ""
1999
- params["city"] = billingInfoDict["city"] as? String ?? ""
2000
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
2001
- }
2002
-
2003
- // Conditionally add additional info
2004
- if let visibility = visibility, visibility.additional == true,
2005
- let additional = additionalInfo, !additional.isEmpty {
2006
-
2007
- var additionalInfoDict: [String: Any] = [:]
2008
- for item in additional {
2009
- additionalInfoDict[item.name] = item.value
2010
- }
2011
-
2012
- params["description"] = additionalInfoDict["description"] as? String ?? ""
2013
- params["phone_number"] = additionalInfoDict["phone_number"] as? String ?? ""
2014
- params["name"] = additionalInfoDict["name"] as? String ?? ""
2015
- params["email"] = additionalInfoDict["email"] as? String ?? ""
2016
- }
2017
-
2018
- // Set default description if additional info is not visible
2019
- if let visibility = visibility, visibility.additional == false {
2020
- params["description"] = "Hosted payment checkout"
2021
- }
2022
-
2023
- // Add these if recurring is enabled
2024
- // if let req = request, req.is_recurring == true {
2025
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
2026
- // // Only send start_date if type is .custom and field is not empty
2027
- // if let startDateText = startDate, !startDateText.isEmpty {
2028
- // let inputFormatter = DateFormatter()
2029
- // inputFormatter.dateFormat = "dd/MM/yyyy"
2030
- //
2031
- // let outputFormatter = DateFormatter()
2032
- // outputFormatter.dateFormat = "MM/dd/yyyy"
2033
- //
2034
- // if let date = inputFormatter.date(from: startDateText) {
2035
- // let apiFormattedDate = outputFormatter.string(from: date)
2036
- // params["start_date"] = apiFormattedDate
2037
- // } else {
2038
- // print("Invalid date format in startDateText")
2039
- // }
2040
- // }
2041
- // }
2042
- //
2043
- // params["interval"] = chosenPlan?.lowercased()
2044
- // }
2045
-
2046
- // Add these if recurring is enabled
2047
- if let req = request, req.is_recurring == true {
2048
- if let startDateText = startDate, !startDateText.isEmpty {
2049
- let inputFormatter = DateFormatter()
2050
- inputFormatter.dateFormat = "dd/MM/yyyy"
2051
-
2052
- let outputFormatter = DateFormatter()
2053
- outputFormatter.dateFormat = "MM/dd/yyyy"
2054
-
2055
- if let date = inputFormatter.date(from: startDateText) {
2056
- let apiFormattedDate = outputFormatter.string(from: date)
2057
- params["start_date"] = apiFormattedDate
2058
- } else {
2059
- print("Invalid date format in startDateText")
2060
- }
2061
- }
2062
-
2063
- // interval is still required
2064
- params["interval"] = chosenPlan?.lowercased()
2065
- }
2066
-
2067
- // ✅ Include metadata only if it has at least 1 key-value pair
2068
- if let metadata = request?.metadata, !metadata.isEmpty {
2069
- params["metadata"] = metadata
2070
- }
2071
-
2072
- print(params)
2073
-
2074
- do {
2075
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2076
- uRLRequest.httpBody = jsonData
2077
- if let jsonString = String(data: jsonData, encoding: .utf8) {
2078
- print("JSON Payload: \(jsonString)")
2079
- }
2080
- } catch let error {
2081
- print("Error creating JSON data: \(error)")
2082
- hideLoadingIndicator()
2083
- return
2084
- }
2085
-
2086
- let session = URLSession.shared
2087
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
2088
-
2089
- DispatchQueue.main.async {
2090
- self.hideLoadingIndicator() // Stop loader when response is received
2091
- }
2092
-
2093
- if let error = error {
2094
- print("Error: \(error.localizedDescription)")
2095
- return
2096
- }
2097
-
2098
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2099
- print("Invalid response")
2100
- return
2101
- }
2102
-
2103
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2104
- if let data = serviceData {
2105
- do {
2106
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2107
- print("Response Data: \(responseObject)")
2108
-
2109
- // ✅ Handle duplicate transaction case
2110
- if let status = responseObject["status"] as? Bool, status == false,
2111
- let message = responseObject["message"] as? String,
2112
- message.lowercased().contains("duplicate transaction") {
2113
- self.presentPaymentErrorVC(errorMessage: message)
2114
- return
2115
- }
2116
-
2117
- if let status = responseObject["status"] as? Int, status == 0,
2118
- let message = responseObject["message"] as? String,
2119
- message.lowercased().contains("duplicate transaction") {
2120
- self.presentPaymentErrorVC(errorMessage: message)
2121
- return
2122
- }
2123
-
2124
- // ✅ Handle generic "status == 0" error case
2125
- if let status = responseObject["status"] as? Int, status == 0 {
2126
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
2127
- self.presentPaymentErrorVC(errorMessage: errorMessage)
2128
- return
2129
- }
2130
- else {
2131
- DispatchQueue.main.async {
2132
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
2133
- paymentDoneVC.chargeData = responseObject
2134
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
2135
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
2136
- // Pass billing and additional info
2137
- // Conditionally pass raw FieldItem array
2138
- paymentDoneVC.visibility = self.visibility
2139
- paymentDoneVC.request = self.request
2140
-
2141
- // if self.visibility?.billing == true {
2142
- paymentDoneVC.billingInfoData = self.billingInfo
2143
- var billingDict: [String: Any] = [:]
2144
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
2145
- paymentDoneVC.billingInfo = billingDict
2146
- // }
2147
-
2148
- // if self.visibility?.additional == true {
2149
- paymentDoneVC.additionalInfoData = self.additionalInfo
2150
- var additionalDict: [String: Any] = [:]
2151
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
2152
- paymentDoneVC.additionalInfo = additionalDict
2153
- // }
2154
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2155
- }
2156
- }
2157
- }
2158
- } else {
2159
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2160
- }
2161
- } catch let jsonError {
2162
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2163
- }
2164
- } else {
2165
- self.presentPaymentErrorVC(errorMessage: "No data received")
2166
- }
2167
- } else {
2168
- if let data = serviceData,
2169
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
2170
- let message = responseObj["message"] as? String {
2171
- self.presentPaymentErrorVC(errorMessage: message)
2172
- } else {
2173
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2174
- }
2175
- }
2176
- }
2177
- task.resume()
2178
- }
2179
-
2180
- //MARK: - Banking Account Charge Api from Regular saved bank account
2181
- func accountChargeSavedBankAccountApi() {
2182
- showLoadingIndicator()
2183
-
2184
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
2185
-
2186
- guard let serviceURL = URL(string: fullURL) else {
2187
- print("Invalid URL")
2188
- hideLoadingIndicator()
2189
- return
2190
- }
2191
-
2192
- var uRLRequest = URLRequest(url: serviceURL)
2193
- uRLRequest.httpMethod = "POST"
2194
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
2195
-
2196
- let token = UserStoreSingleton.shared.clientToken
2197
- print("Setting clientToken header: \(token ?? "None")")
2198
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2199
-
2200
- // let emailText = UserStoreSingleton.shared.verificationEmail ?? ""
2201
- // let emailPrefix = emailText.components(separatedBy: "@").first ?? ""
2202
-
2203
- let finalEmail: String
2204
- if let verificationEmail = UserStoreSingleton.shared.verificationEmail, !verificationEmail.isEmpty {
2205
- finalEmail = verificationEmail
2206
- } else {
2207
- finalEmail = request.email ?? ""
2208
- }
2209
-
2210
- let emailPrefix: String
2211
- if !finalEmail.isEmpty {
2212
- emailPrefix = finalEmail.components(separatedBy: "@").first ?? ""
2213
- } else {
2214
- emailPrefix = ""
2215
- }
2216
-
2217
- // Determine name: use request.name if available, otherwise fallback to email prefix
2218
- let nameParam: String
2219
- if let requestName = request.name, !requestName.trimmingCharacters(in: .whitespaces).isEmpty {
2220
- nameParam = requestName
2221
- } else {
2222
- nameParam = emailPrefix
2223
- }
2224
-
2225
- var params: [String: Any] = [
2226
- // "name": UserStoreSingleton.shared.merchantName ?? "",
2227
- "name": nameParam,
2228
- "account_id": accountID ?? "",
2229
- "payment_method": "ach",
2230
- "customer": customerID ?? "",
2231
- "currency": "usd",
2232
- "email": finalEmail
2233
- ]
2234
-
2235
- // Conditionally add billing info
2236
- if let visibility = visibility, visibility.billing == true,
2237
- let billing = billingInfo, !billing.isEmpty {
2238
-
2239
- var billingInfoDict: [String: Any] = [:]
2240
- for item in billing {
2241
- billingInfoDict[item.name] = item.value
2242
- }
2243
-
2244
- params["address"] = billingInfoDict["address"] as? String ?? ""
2245
- params["country"] = billingInfoDict["country"] as? String ?? ""
2246
- params["state"] = billingInfoDict["state"] as? String ?? ""
2247
- params["city"] = billingInfoDict["city"] as? String ?? ""
2248
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
2249
- }
2250
-
2251
- // Set default description if additional info is not visible
2252
- if let visibility = visibility, visibility.additional == false {
2253
- params["description"] = "Hosted payment checkout"
2254
- }
2255
-
2256
- // Add these if recurring is enabled
2257
- // if let req = request, req.is_recurring == true {
2258
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
2259
- // // Only send start_date if type is .custom and field is not empty
2260
- // if let startDateText = startDate, !startDateText.isEmpty {
2261
- // let inputFormatter = DateFormatter()
2262
- // inputFormatter.dateFormat = "dd/MM/yyyy"
2263
- //
2264
- // let outputFormatter = DateFormatter()
2265
- // outputFormatter.dateFormat = "MM/dd/yyyy"
2266
- //
2267
- // if let date = inputFormatter.date(from: startDateText) {
2268
- // let apiFormattedDate = outputFormatter.string(from: date)
2269
- // params["start_date"] = apiFormattedDate
2270
- // } else {
2271
- // print("Invalid date format in startDateText")
2272
- // }
2273
- // }
2274
- // }
2275
- //
2276
- // params["interval"] = chosenPlan?.lowercased()
2277
- // }
2278
-
2279
- // Add these if recurring is enabled
2280
- if let req = request, req.is_recurring == true {
2281
- if let startDateText = startDate, !startDateText.isEmpty {
2282
- let inputFormatter = DateFormatter()
2283
- inputFormatter.dateFormat = "dd/MM/yyyy"
2284
-
2285
- let outputFormatter = DateFormatter()
2286
- outputFormatter.dateFormat = "MM/dd/yyyy"
2287
-
2288
- if let date = inputFormatter.date(from: startDateText) {
2289
- let apiFormattedDate = outputFormatter.string(from: date)
2290
- params["start_date"] = apiFormattedDate
2291
- } else {
2292
- print("Invalid date format in startDateText")
2293
- }
2294
- }
2295
-
2296
- // interval is still required
2297
- params["interval"] = chosenPlan?.lowercased()
2298
- }
2299
-
2300
- // ✅ Include metadata only if it has at least 1 key-value pair
2301
- if let metadata = request?.metadata, !metadata.isEmpty {
2302
- params["metadata"] = metadata
2303
- }
2304
-
2305
- print(params)
2306
-
2307
- do {
2308
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2309
- uRLRequest.httpBody = jsonData
2310
- if let jsonString = String(data: jsonData, encoding: .utf8) {
2311
- print("JSON Payload: \(jsonString)")
2312
- }
2313
- } catch let error {
2314
- print("Error creating JSON data: \(error)")
2315
- hideLoadingIndicator()
2316
- return
2317
- }
2318
-
2319
- let session = URLSession.shared
2320
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
2321
-
2322
- DispatchQueue.main.async {
2323
- self.hideLoadingIndicator() // Stop loader when response is received
2324
- }
2325
-
2326
- if let error = error {
2327
- print("Error: \(error.localizedDescription)")
2328
- return
2329
- }
2330
-
2331
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2332
- print("Invalid response")
2333
- return
2334
- }
2335
-
2336
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2337
- if let data = serviceData {
2338
- do {
2339
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2340
- print("Response Data: \(responseObject)")
2341
-
2342
- // ✅ Handle duplicate transaction case
2343
- if let status = responseObject["status"] as? Bool, status == false,
2344
- let message = responseObject["message"] as? String,
2345
- message.lowercased().contains("duplicate transaction") {
2346
- self.presentPaymentErrorVC(errorMessage: message)
2347
- return
2348
- }
2349
-
2350
- if let status = responseObject["status"] as? Int, status == 0,
2351
- let message = responseObject["message"] as? String,
2352
- message.lowercased().contains("duplicate transaction") {
2353
- self.presentPaymentErrorVC(errorMessage: message)
2354
- return
2355
- }
2356
-
2357
- // ✅ Handle generic "status == 0" error case
2358
- if let status = responseObject["status"] as? Int, status == 0 {
2359
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
2360
- self.presentPaymentErrorVC(errorMessage: errorMessage)
2361
- return
2362
- }
2363
- else {
2364
- DispatchQueue.main.async {
2365
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
2366
- paymentDoneVC.chargeData = responseObject
2367
- // Pass the selected payment method
2368
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
2369
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate // Pass the delegate
2370
- paymentDoneVC.bankPaymentParams = params
2371
- // Pass billing and additional info
2372
- // Conditionally pass raw FieldItem array
2373
- paymentDoneVC.visibility = self.visibility
2374
- paymentDoneVC.request = self.request
2375
-
2376
- // if self.visibility?.billing == true {
2377
- paymentDoneVC.billingInfoData = self.billingInfo
2378
- var billingDict: [String: Any] = [:]
2379
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
2380
- paymentDoneVC.billingInfo = billingDict
2381
- // }
2382
-
2383
- // if self.visibility?.additional == true {
2384
- paymentDoneVC.additionalInfoData = self.additionalInfo
2385
- var additionalDict: [String: Any] = [:]
2386
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
2387
- paymentDoneVC.additionalInfo = additionalDict
2388
- // }
2389
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2390
- }
2391
- }
2392
- }
2393
- } else {
2394
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2395
- }
2396
- } catch let jsonError {
2397
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2398
- }
2399
- } else {
2400
- self.presentPaymentErrorVC(errorMessage: "No data received")
2401
- }
2402
- } else {
2403
- if let data = serviceData,
2404
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
2405
- let message = responseObj["message"] as? String {
2406
- self.presentPaymentErrorVC(errorMessage: message)
2407
- } else {
2408
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2409
- }
2410
- }
2411
- }
2412
- task.resume()
2413
- }
2414
-
2415
- //MARK: - Banking Account Charge Api
2416
- func accountChargeApi() {
2417
- showLoadingIndicator()
2418
-
2419
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
2420
-
2421
- guard let serviceURL = URL(string: fullURL) else {
2422
- print("Invalid URL")
2423
- hideLoadingIndicator()
2424
- return
2425
- }
2426
-
2427
- var uRLRequest = URLRequest(url: serviceURL)
2428
- uRLRequest.httpMethod = "POST"
2429
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
2430
-
2431
- let token = UserStoreSingleton.shared.clientToken
2432
- print("Setting clientToken header: \(token ?? "None")")
2433
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2434
-
2435
- var params: [String: Any] = [
2436
- //"name": accountName ?? "",
2437
- "name": !(request.name?.isEmpty ?? true) ? request.name! : (accountName ?? ""),
2438
- "email": userEmail ?? "",
2439
- "currency": "usd",
2440
- "account_type": accountType?.lowercased() ?? "",
2441
- "routing_number": routingNumber ?? "",
2442
- "account_number": accountNumber ?? "",
2443
- "payment_mode": "auth_and_capture",
2444
- "levelIndicator": 1,
2445
- ]
2446
-
2447
- // Conditionally add billing info
2448
- if let visibility = visibility, visibility.billing == true,
2449
- let billing = billingInfo, !billing.isEmpty {
2450
-
2451
- var billingInfoDict: [String: Any] = [:]
2452
- for item in billing {
2453
- billingInfoDict[item.name] = item.value
2454
- }
2455
-
2456
- params["address"] = billingInfoDict["address"] as? String ?? ""
2457
- params["country"] = billingInfoDict["country"] as? String ?? ""
2458
- params["state"] = billingInfoDict["state"] as? String ?? ""
2459
- params["city"] = billingInfoDict["city"] as? String ?? ""
2460
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
2461
- }
2462
-
2463
- // Set default description if additional info is not visible
2464
- if let visibility = visibility, visibility.additional == false {
2465
- params["description"] = "Hosted payment checkout"
2466
- }
2467
-
2468
- // Add these if recurring is enabled
2469
- // if let req = request, req.is_recurring == true {
2470
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
2471
- // // Only send start_date if type is .custom and field is not empty
2472
- // if let startDateText = startDate, !startDateText.isEmpty {
2473
- // let inputFormatter = DateFormatter()
2474
- // inputFormatter.dateFormat = "dd/MM/yyyy"
2475
- //
2476
- // let outputFormatter = DateFormatter()
2477
- // outputFormatter.dateFormat = "MM/dd/yyyy"
2478
- //
2479
- // if let date = inputFormatter.date(from: startDateText) {
2480
- // let apiFormattedDate = outputFormatter.string(from: date)
2481
- // params["start_date"] = apiFormattedDate
2482
- // } else {
2483
- // print("Invalid date format in startDateText")
2484
- // }
2485
- // }
2486
- // }
2487
- //
2488
- // params["interval"] = chosenPlan?.lowercased()
2489
- // }
2490
-
2491
- // Add these if recurring is enabled
2492
- if let req = request, req.is_recurring == true {
2493
- if let startDateText = startDate, !startDateText.isEmpty {
2494
- let inputFormatter = DateFormatter()
2495
- inputFormatter.dateFormat = "dd/MM/yyyy"
2496
-
2497
- let outputFormatter = DateFormatter()
2498
- outputFormatter.dateFormat = "MM/dd/yyyy"
2499
-
2500
- if let date = inputFormatter.date(from: startDateText) {
2501
- let apiFormattedDate = outputFormatter.string(from: date)
2502
- params["start_date"] = apiFormattedDate
2503
- } else {
2504
- print("Invalid date format in startDateText")
2505
- }
2506
- }
2507
-
2508
- // interval is still required
2509
- params["interval"] = chosenPlan?.lowercased()
2510
- }
2511
-
2512
- // ✅ Include metadata only if it has at least 1 key-value pair
2513
- if let metadata = request?.metadata, !metadata.isEmpty {
2514
- params["metadata"] = metadata
2515
- }
2516
-
2517
- print(params)
2518
-
2519
- do {
2520
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2521
- uRLRequest.httpBody = jsonData
2522
- if let jsonString = String(data: jsonData, encoding: .utf8) {
2523
- print("JSON Payload: \(jsonString)")
2524
- }
2525
- } catch let error {
2526
- print("Error creating JSON data: \(error)")
2527
- hideLoadingIndicator()
2528
- return
2529
- }
2530
-
2531
- let session = URLSession.shared
2532
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
2533
-
2534
- DispatchQueue.main.async {
2535
- self.hideLoadingIndicator() // Stop loader when response is received
2536
- }
2537
-
2538
- if let error = error {
2539
- print("Error: \(error.localizedDescription)")
2540
- return
2541
- }
2542
-
2543
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2544
- print("Invalid response")
2545
- return
2546
- }
2547
-
2548
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2549
- if let data = serviceData {
2550
- do {
2551
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2552
- print("Response Data: \(responseObject)")
2553
-
2554
- // ✅ Handle duplicate transaction case
2555
- if let status = responseObject["status"] as? Bool, status == false,
2556
- let message = responseObject["message"] as? String,
2557
- message.lowercased().contains("duplicate transaction") {
2558
- self.presentPaymentErrorVC(errorMessage: message)
2559
- return
2560
- }
2561
-
2562
- if let status = responseObject["status"] as? Int, status == 0,
2563
- let message = responseObject["message"] as? String,
2564
- message.lowercased().contains("duplicate transaction") {
2565
- self.presentPaymentErrorVC(errorMessage: message)
2566
- return
2567
- }
2568
-
2569
- // ✅ Handle generic "status == 0" error case
2570
- if let status = responseObject["status"] as? Int, status == 0 {
2571
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
2572
- self.presentPaymentErrorVC(errorMessage: errorMessage)
2573
- return
2574
- }
2575
- else {
2576
- DispatchQueue.main.async {
2577
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
2578
- paymentDoneVC.chargeData = responseObject
2579
- // Pass the selected payment method
2580
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
2581
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate // Pass the delegate
2582
- paymentDoneVC.bankPaymentParams = params
2583
- // Pass billing and additional info
2584
- // Conditionally pass raw FieldItem array
2585
- paymentDoneVC.visibility = self.visibility
2586
- paymentDoneVC.request = self.request
2587
-
2588
- // if self.visibility?.billing == true {
2589
- paymentDoneVC.billingInfoData = self.billingInfo
2590
- var billingDict: [String: Any] = [:]
2591
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
2592
- paymentDoneVC.billingInfo = billingDict
2593
- // }
2594
-
2595
- // if self.visibility?.additional == true {
2596
- paymentDoneVC.additionalInfoData = self.additionalInfo
2597
- var additionalDict: [String: Any] = [:]
2598
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
2599
- paymentDoneVC.additionalInfo = additionalDict
2600
- // }
2601
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2602
- }
2603
- }
2604
- }
2605
- } else {
2606
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2607
- }
2608
- } catch let jsonError {
2609
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2610
- }
2611
- } else {
2612
- self.presentPaymentErrorVC(errorMessage: "No data received")
2613
- }
2614
- } else {
2615
- if let data = serviceData,
2616
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
2617
- let message = responseObj["message"] as? String {
2618
- self.presentPaymentErrorVC(errorMessage: message)
2619
- } else {
2620
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2621
- }
2622
- }
2623
- }
2624
- task.resume()
2625
- }
2626
-
2627
- //MARK: - Account Charge Api if user saved the account.
2628
- func accountChargeApi(customerId: String?) {
2629
- showLoadingIndicator()
2630
-
2631
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
2632
-
2633
- guard let serviceURL = URL(string: fullURL) else {
2634
- print("Invalid URL")
2635
- hideLoadingIndicator()
2636
- return
2637
- }
2638
-
2639
- var uRLRequest = URLRequest(url: serviceURL)
2640
- uRLRequest.httpMethod = "POST"
2641
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
2642
-
2643
- let token = UserStoreSingleton.shared.clientToken
2644
- print("Setting clientToken header: \(token ?? "None")")
2645
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2646
-
2647
- // let emailPrefix = UserStoreSingleton.shared.verificationEmail?.components(separatedBy: "@").first ?? ""
2648
-
2649
- let finalEmail: String
2650
- if let verificationEmail = UserStoreSingleton.shared.verificationEmail, !verificationEmail.isEmpty {
2651
- finalEmail = verificationEmail
2652
- } else {
2653
- finalEmail = request.email ?? ""
2654
- }
2655
-
2656
- let emailPrefix: String
2657
- if !finalEmail.isEmpty {
2658
- emailPrefix = finalEmail.components(separatedBy: "@").first ?? ""
2659
- } else {
2660
- emailPrefix = ""
2661
- }
2662
-
2663
- var params: [String: Any] = [
2664
- // "name": accountName ?? "",
2665
- "name": !(request.name?.isEmpty ?? true) ? request.name! : (accountName ?? ""),
2666
- "email": userEmail ?? "",
2667
- "currency": "usd",
2668
- "account_type": accountType?.lowercased() ?? "",
2669
- "routing_number": routingNumber ?? "",
2670
- "account_number": accountNumber ?? "",
2671
- "payment_mode": "auth_and_capture",
2672
- "levelIndicator": 1,
2673
- "save_account": (isSavedNewAccount ?? false) ? 1 : 0,
2674
- "payment_method": "ach"
2675
- ]
2676
-
2677
- if let customerId = customerId {
2678
- params["customer"] = customerId
2679
- } else {
2680
- params["username"] = emailPrefix
2681
- }
2682
-
2683
- // Conditionally add billing info
2684
- if let visibility = visibility, visibility.billing == true,
2685
- let billing = billingInfo, !billing.isEmpty {
2686
-
2687
- var billingInfoDict: [String: Any] = [:]
2688
- for item in billing {
2689
- billingInfoDict[item.name] = item.value
2690
- }
2691
-
2692
- params["address"] = billingInfoDict["address"] as? String ?? ""
2693
- params["country"] = billingInfoDict["country"] as? String ?? ""
2694
- params["state"] = billingInfoDict["state"] as? String ?? ""
2695
- params["city"] = billingInfoDict["city"] as? String ?? ""
2696
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
2697
- }
2698
-
2699
- // Set default description if additional info is not visible
2700
- if let visibility = visibility, visibility.additional == false {
2701
- params["description"] = "Hosted payment checkout"
2702
- }
2703
-
2704
- // Add these if recurring is enabled
2705
- // if let req = request, req.is_recurring == true {
2706
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
2707
- // // Only send start_date if type is .custom and field is not empty
2708
- // if let startDateText = startDate, !startDateText.isEmpty {
2709
- // let inputFormatter = DateFormatter()
2710
- // inputFormatter.dateFormat = "dd/MM/yyyy"
2711
- //
2712
- // let outputFormatter = DateFormatter()
2713
- // outputFormatter.dateFormat = "MM/dd/yyyy"
2714
- //
2715
- // if let date = inputFormatter.date(from: startDateText) {
2716
- // let apiFormattedDate = outputFormatter.string(from: date)
2717
- // params["start_date"] = apiFormattedDate
2718
- // } else {
2719
- // print("Invalid date format in startDateText")
2720
- // }
2721
- // }
2722
- // }
2723
- //
2724
- // params["interval"] = chosenPlan?.lowercased()
2725
- // }
2726
-
2727
- // Add these if recurring is enabled
2728
- if let req = request, req.is_recurring == true {
2729
- if let startDateText = startDate, !startDateText.isEmpty {
2730
- let inputFormatter = DateFormatter()
2731
- inputFormatter.dateFormat = "dd/MM/yyyy"
2732
-
2733
- let outputFormatter = DateFormatter()
2734
- outputFormatter.dateFormat = "MM/dd/yyyy"
2735
-
2736
- if let date = inputFormatter.date(from: startDateText) {
2737
- let apiFormattedDate = outputFormatter.string(from: date)
2738
- params["start_date"] = apiFormattedDate
2739
- } else {
2740
- print("Invalid date format in startDateText")
2741
- }
2742
- }
2743
-
2744
- // interval is still required
2745
- params["interval"] = chosenPlan?.lowercased()
2746
- }
2747
-
2748
- // ✅ Include metadata only if it has at least 1 key-value pair
2749
- if let metadata = request?.metadata, !metadata.isEmpty {
2750
- params["metadata"] = metadata
2751
- }
2752
-
2753
- print(params)
2754
-
2755
- do {
2756
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2757
- uRLRequest.httpBody = jsonData
2758
- if let jsonString = String(data: jsonData, encoding: .utf8) {
2759
- print("JSON Payload: \(jsonString)")
2760
- }
2761
- } catch let error {
2762
- print("Error creating JSON data: \(error)")
2763
- hideLoadingIndicator()
2764
- return
2765
- }
2766
-
2767
- let session = URLSession.shared
2768
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
2769
-
2770
- DispatchQueue.main.async {
2771
- self.hideLoadingIndicator() // Stop loader when response is received
2772
- }
2773
-
2774
- if let error = error {
2775
- self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
2776
- return
2777
- }
2778
-
2779
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2780
- self.presentPaymentErrorVC(errorMessage: "Invalid response")
2781
- return
2782
- }
2783
-
2784
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2785
- if let data = serviceData {
2786
- do {
2787
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2788
- print("Response Data: \(responseObject)")
2789
-
2790
- // ✅ Handle duplicate transaction case
2791
- if let status = responseObject["status"] as? Bool, status == false,
2792
- let message = responseObject["message"] as? String,
2793
- message.lowercased().contains("duplicate transaction") {
2794
- self.presentPaymentErrorVC(errorMessage: message)
2795
- return
2796
- }
2797
-
2798
- if let status = responseObject["status"] as? Int, status == 0,
2799
- let message = responseObject["message"] as? String,
2800
- message.lowercased().contains("duplicate transaction") {
2801
- self.presentPaymentErrorVC(errorMessage: message)
2802
- return
2803
- }
2804
-
2805
- // ✅ Handle generic "status == 0" error case
2806
- if let status = responseObject["status"] as? Int, status == 0 {
2807
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
2808
- self.presentPaymentErrorVC(errorMessage: errorMessage)
2809
- return
2810
- }
2811
- else {
2812
- DispatchQueue.main.async {
2813
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
2814
- paymentDoneVC.chargeData = responseObject
2815
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
2816
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
2817
- paymentDoneVC.bankPaymentParams = params
2818
- // Pass billing and additional info
2819
- // Conditionally pass raw FieldItem array
2820
- paymentDoneVC.visibility = self.visibility
2821
- paymentDoneVC.request = self.request
2822
-
2823
- // if self.visibility?.billing == true {
2824
- paymentDoneVC.billingInfoData = self.billingInfo
2825
- var billingDict: [String: Any] = [:]
2826
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
2827
- paymentDoneVC.billingInfo = billingDict
2828
- // }
2829
-
2830
- // if self.visibility?.additional == true {
2831
- paymentDoneVC.additionalInfoData = self.additionalInfo
2832
- var additionalDict: [String: Any] = [:]
2833
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
2834
- paymentDoneVC.additionalInfo = additionalDict
2835
- // }
2836
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2837
- }
2838
- }
2839
- }
2840
- } else {
2841
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2842
- }
2843
- } catch let jsonError {
2844
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2845
- }
2846
- } else {
2847
- self.presentPaymentErrorVC(errorMessage: "No data received")
2848
- }
2849
- } else {
2850
- if let data = serviceData,
2851
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
2852
- let message = responseObj["message"] as? String {
2853
- self.presentPaymentErrorVC(errorMessage: message)
2854
- } else {
2855
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2856
- }
2857
- }
2858
- }
2859
- task.resume()
2860
- }
2861
-
2862
- //MARK: - Account Charge Api if user logged in and saved the account.
2863
- func accountChargeWithSaveApi(customerId: String?) {
2864
- showLoadingIndicator()
2865
-
2866
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
2867
-
2868
- guard let serviceURL = URL(string: fullURL) else {
2869
- print("Invalid URL")
2870
- hideLoadingIndicator()
2871
- return
2872
- }
2873
-
2874
- var uRLRequest = URLRequest(url: serviceURL)
2875
- uRLRequest.httpMethod = "POST"
2876
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
2877
-
2878
- let token = UserStoreSingleton.shared.clientToken
2879
- print("Setting clientToken header: \(token ?? "None")")
2880
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2881
-
2882
- // let emailPrefix = UserStoreSingleton.shared.verificationEmail?.components(separatedBy: "@").first ?? ""
2883
-
2884
- let finalEmail: String
2885
- if let verificationEmail = UserStoreSingleton.shared.verificationEmail, !verificationEmail.isEmpty {
2886
- finalEmail = verificationEmail
2887
- } else {
2888
- finalEmail = request.email ?? ""
2889
- }
2890
-
2891
- let emailPrefix: String
2892
- if !finalEmail.isEmpty {
2893
- emailPrefix = finalEmail.components(separatedBy: "@").first ?? ""
2894
- } else {
2895
- emailPrefix = ""
2896
- }
2897
-
2898
- var params: [String: Any] = [
2899
- // "name": accountName ?? "",
2900
- "name": !(request.name?.isEmpty ?? true) ? request.name! : (accountName ?? ""),
2901
- "email": userEmail ?? "",
2902
- "currency": "usd",
2903
- "account_type": accountType?.lowercased() ?? "",
2904
- "routing_number": routingNumber ?? "",
2905
- "account_number": accountNumber ?? "",
2906
- "payment_mode": "auth_and_capture",
2907
- "levelIndicator": 1,
2908
- "save_account": (isSavedNewAccount ?? false) ? 1 : 0,
2909
- "payment_method": "ach"
2910
- ]
2911
-
2912
- if let customerId = customerId {
2913
- params["customer"] = customerId
2914
- } else {
2915
- params["username"] = emailPrefix
2916
- }
2917
-
2918
- if customerId == nil {
2919
- params["create_customer"] = "1"
2920
- }
2921
-
2922
- // Conditionally add billing info
2923
- if let visibility = visibility, visibility.billing == true,
2924
- let billing = billingInfo, !billing.isEmpty {
2925
-
2926
- var billingInfoDict: [String: Any] = [:]
2927
- for item in billing {
2928
- billingInfoDict[item.name] = item.value
2929
- }
2930
-
2931
- params["address"] = billingInfoDict["address"] as? String ?? ""
2932
- params["country"] = billingInfoDict["country"] as? String ?? ""
2933
- params["state"] = billingInfoDict["state"] as? String ?? ""
2934
- params["city"] = billingInfoDict["city"] as? String ?? ""
2935
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
2936
- }
2937
-
2938
- // Set default description if additional info is not visible
2939
- if let visibility = visibility, visibility.additional == false {
2940
- params["description"] = "Hosted payment checkout"
2941
- }
2942
-
2943
- // Add these if recurring is enabled
2944
- // if let req = request, req.is_recurring == true {
2945
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
2946
- // // Only send start_date if type is .custom and field is not empty
2947
- // if let startDateText = startDate, !startDateText.isEmpty {
2948
- // let inputFormatter = DateFormatter()
2949
- // inputFormatter.dateFormat = "dd/MM/yyyy"
2950
- //
2951
- // let outputFormatter = DateFormatter()
2952
- // outputFormatter.dateFormat = "MM/dd/yyyy"
2953
- //
2954
- // if let date = inputFormatter.date(from: startDateText) {
2955
- // let apiFormattedDate = outputFormatter.string(from: date)
2956
- // params["start_date"] = apiFormattedDate
2957
- // } else {
2958
- // print("Invalid date format in startDateText")
2959
- // }
2960
- // }
2961
- // }
2962
- //
2963
- // params["interval"] = chosenPlan?.lowercased()
2964
- // }
2965
-
2966
- // Add these if recurring is enabled
2967
- if let req = request, req.is_recurring == true {
2968
- if let startDateText = startDate, !startDateText.isEmpty {
2969
- let inputFormatter = DateFormatter()
2970
- inputFormatter.dateFormat = "dd/MM/yyyy"
2971
-
2972
- let outputFormatter = DateFormatter()
2973
- outputFormatter.dateFormat = "MM/dd/yyyy"
2974
-
2975
- if let date = inputFormatter.date(from: startDateText) {
2976
- let apiFormattedDate = outputFormatter.string(from: date)
2977
- params["start_date"] = apiFormattedDate
2978
- } else {
2979
- print("Invalid date format in startDateText")
2980
- }
2981
- }
2982
-
2983
- // interval is still required
2984
- params["interval"] = chosenPlan?.lowercased()
2985
- }
2986
-
2987
- // ✅ Include metadata only if it has at least 1 key-value pair
2988
- if let metadata = request?.metadata, !metadata.isEmpty {
2989
- params["metadata"] = metadata
2990
- }
2991
-
2992
- print(params)
2993
-
2994
- do {
2995
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2996
- uRLRequest.httpBody = jsonData
2997
- if let jsonString = String(data: jsonData, encoding: .utf8) {
2998
- print("JSON Payload: \(jsonString)")
2999
- }
3000
- } catch let error {
3001
- print("Error creating JSON data: \(error)")
3002
- hideLoadingIndicator()
3003
- return
3004
- }
3005
-
3006
- let session = URLSession.shared
3007
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
3008
-
3009
- DispatchQueue.main.async {
3010
- self.hideLoadingIndicator() // Stop loader when response is received
3011
- }
3012
-
3013
- if let error = error {
3014
- self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
3015
- return
3016
- }
3017
-
3018
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
3019
- self.presentPaymentErrorVC(errorMessage: "Invalid response")
3020
- return
3021
- }
3022
-
3023
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
3024
- if let data = serviceData {
3025
- do {
3026
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
3027
- print("Response Data: \(responseObject)")
3028
-
3029
- // ✅ Handle duplicate transaction case
3030
- if let status = responseObject["status"] as? Bool, status == false,
3031
- let message = responseObject["message"] as? String,
3032
- message.lowercased().contains("duplicate transaction") {
3033
- self.presentPaymentErrorVC(errorMessage: message)
3034
- return
3035
- }
3036
-
3037
- if let status = responseObject["status"] as? Int, status == 0,
3038
- let message = responseObject["message"] as? String,
3039
- message.lowercased().contains("duplicate transaction") {
3040
- self.presentPaymentErrorVC(errorMessage: message)
3041
- return
3042
- }
3043
-
3044
- // ✅ Handle generic "status == 0" error case
3045
- if let status = responseObject["status"] as? Int, status == 0 {
3046
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
3047
- self.presentPaymentErrorVC(errorMessage: errorMessage)
3048
- return
3049
- }
3050
- else {
3051
- DispatchQueue.main.async {
3052
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
3053
- paymentDoneVC.chargeData = responseObject
3054
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
3055
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
3056
- paymentDoneVC.bankPaymentParams = params
3057
- // Pass billing and additional info
3058
- // Conditionally pass raw FieldItem array
3059
- paymentDoneVC.visibility = self.visibility
3060
- paymentDoneVC.request = self.request
3061
-
3062
- // if self.visibility?.billing == true {
3063
- paymentDoneVC.billingInfoData = self.billingInfo
3064
- var billingDict: [String: Any] = [:]
3065
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
3066
- paymentDoneVC.billingInfo = billingDict
3067
- // }
3068
-
3069
- // if self.visibility?.additional == true {
3070
- paymentDoneVC.additionalInfoData = self.additionalInfo
3071
- var additionalDict: [String: Any] = [:]
3072
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
3073
- paymentDoneVC.additionalInfo = additionalDict
3074
- // }
3075
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
3076
- }
3077
- }
3078
- }
3079
- } else {
3080
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
3081
- }
3082
- } catch let jsonError {
3083
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
3084
- }
3085
- } else {
3086
- self.presentPaymentErrorVC(errorMessage: "No data received")
3087
- }
3088
- } else {
3089
- if let data = serviceData,
3090
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
3091
- let message = responseObj["message"] as? String {
3092
- self.presentPaymentErrorVC(errorMessage: message)
3093
- } else {
3094
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
3095
- }
3096
- }
3097
- }
3098
- task.resume()
3099
- }
3100
-
3101
- //MARK: - GrailPay Account Charge Api if user not saved account but billing info available
3102
- func grailPayAccountChargeApi() {
3103
- showLoadingIndicator()
3104
-
3105
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
3106
-
3107
- guard let serviceURL = URL(string: fullURL) else {
3108
- print("Invalid URL")
3109
- hideLoadingIndicator()
3110
- return
3111
- }
3112
-
3113
- var uRLRequest = URLRequest(url: serviceURL)
3114
- uRLRequest.httpMethod = "POST"
3115
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
3116
-
3117
- let token = UserStoreSingleton.shared.clientToken
3118
- print("Setting clientToken header: \(token ?? "None")")
3119
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
3120
-
3121
- var params: [String: Any] = [
3122
- "account_id": self.grailPayAccountID ?? "",
3123
- "account_type": self.selectedGrailPayAccountType ?? "",
3124
- "name": self.selectedGrailPayAccountName ?? "",
3125
- "description": "payment checkout",
3126
- "email": userEmail ?? ""
3127
- ]
3128
-
3129
- // ✅ Only add these params for logged-in users
3130
- if UserStoreSingleton.shared.isLoggedIn == true {
3131
- let emailText = userEmail
3132
- let emailPrefix = emailText?.components(separatedBy: "@").first ?? ""
3133
-
3134
- params["save_account"] = (isSavedNewAccount ?? false) ? 1 : 0
3135
- params["is_default"] = (isSavedNewAccount ?? false) ? 1 : 0
3136
- params["customer_id"] = UserStoreSingleton.shared.customerId ?? ""
3137
-
3138
- if let customerId = UserStoreSingleton.shared.customerId, !customerId.isEmpty {
3139
- params["customer"] = customerId
3140
- } else {
3141
- params["username"] = emailPrefix
3142
- }
3143
-
3144
- if UserStoreSingleton.shared.customerId == nil {
3145
- params["create_customer"] = "1"
3146
- }
3147
- }
3148
-
3149
- // Conditionally add billing info
3150
- if let visibility = visibility, visibility.billing == true,
3151
- let billing = billingInfo, !billing.isEmpty {
3152
-
3153
- var billingInfoDict: [String: Any] = [:]
3154
- for item in billing {
3155
- billingInfoDict[item.name] = item.value
3156
- }
3157
-
3158
- params["address"] = billingInfoDict["address"] as? String ?? ""
3159
- params["country"] = billingInfoDict["country"] as? String ?? ""
3160
- params["state"] = billingInfoDict["state"] as? String ?? ""
3161
- params["city"] = billingInfoDict["city"] as? String ?? ""
3162
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
3163
- }
3164
-
3165
- // Set default description if additional info is not visible
3166
- if let visibility = visibility, visibility.additional == false {
3167
- params["description"] = "Hosted payment checkout"
3168
- }
3169
-
3170
- // Add these if recurring is enabled
3171
- // if let req = request, req.is_recurring == true {
3172
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
3173
- // // Only send start_date if type is .custom and field is not empty
3174
- // if let startDateText = startDate, !startDateText.isEmpty {
3175
- // let inputFormatter = DateFormatter()
3176
- // inputFormatter.dateFormat = "dd/MM/yyyy"
3177
- //
3178
- // let outputFormatter = DateFormatter()
3179
- // outputFormatter.dateFormat = "MM/dd/yyyy"
3180
- //
3181
- // if let date = inputFormatter.date(from: startDateText) {
3182
- // let apiFormattedDate = outputFormatter.string(from: date)
3183
- // params["start_date"] = apiFormattedDate
3184
- // } else {
3185
- // print("Invalid date format in startDateText")
3186
- // }
3187
- // }
3188
- // }
3189
- //
3190
- // params["interval"] = chosenPlan?.lowercased()
3191
- // }
3192
-
3193
- // Add these if recurring is enabled
3194
- if let req = request, req.is_recurring == true {
3195
- if let startDateText = startDate, !startDateText.isEmpty {
3196
- let inputFormatter = DateFormatter()
3197
- inputFormatter.dateFormat = "dd/MM/yyyy"
3198
-
3199
- let outputFormatter = DateFormatter()
3200
- outputFormatter.dateFormat = "MM/dd/yyyy"
3201
-
3202
- if let date = inputFormatter.date(from: startDateText) {
3203
- let apiFormattedDate = outputFormatter.string(from: date)
3204
- params["start_date"] = apiFormattedDate
3205
- } else {
3206
- print("Invalid date format in startDateText")
3207
- }
3208
- }
3209
-
3210
- // interval is still required
3211
- params["interval"] = chosenPlan?.lowercased()
3212
- }
3213
-
3214
- // ✅ Include metadata only if it has at least 1 key-value pair
3215
- if let metadata = request?.metadata, !metadata.isEmpty {
3216
- params["metadata"] = metadata
3217
- }
3218
-
3219
- print(params)
3220
-
3221
- do {
3222
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
3223
- uRLRequest.httpBody = jsonData
3224
- if let jsonString = String(data: jsonData, encoding: .utf8) {
3225
- print("JSON Payload: \(jsonString)")
3226
- }
3227
- } catch let error {
3228
- print("Error creating JSON data: \(error)")
3229
- hideLoadingIndicator()
3230
- return
3231
- }
3232
-
3233
- let session = URLSession.shared
3234
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
3235
-
3236
- DispatchQueue.main.async {
3237
- self.hideLoadingIndicator() // Stop loader when response is received
3238
- }
3239
-
3240
- if let error = error {
3241
- self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
3242
- return
3243
- }
3244
-
3245
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
3246
- self.presentPaymentErrorVC(errorMessage: "Invalid response")
3247
- return
3248
- }
3249
-
3250
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
3251
- if let data = serviceData {
3252
- do {
3253
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
3254
- print("Response Data: \(responseObject)")
3255
-
3256
- // ✅ Handle duplicate transaction case
3257
- if let status = responseObject["status"] as? Bool, status == false,
3258
- let message = responseObject["message"] as? String,
3259
- message.lowercased().contains("duplicate transaction") {
3260
- self.presentPaymentErrorVC(errorMessage: message)
3261
- return
3262
- }
3263
-
3264
- if let status = responseObject["status"] as? Int, status == 0,
3265
- let message = responseObject["message"] as? String,
3266
- message.lowercased().contains("duplicate transaction") {
3267
- self.presentPaymentErrorVC(errorMessage: message)
3268
- return
3269
- }
3270
-
3271
- // ✅ Handle generic "status == 0" error case
3272
- if let status = responseObject["status"] as? Int, status == 0 {
3273
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
3274
- self.presentPaymentErrorVC(errorMessage: errorMessage)
3275
- return
3276
- }
3277
- else {
3278
- DispatchQueue.main.async {
3279
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
3280
- paymentDoneVC.chargeData = responseObject
3281
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
3282
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
3283
- paymentDoneVC.bankPaymentParams = params
3284
- // Pass billing and additional info
3285
- // Conditionally pass raw FieldItem array
3286
- paymentDoneVC.visibility = self.visibility
3287
- paymentDoneVC.request = self.request
3288
-
3289
- // if self.visibility?.billing == true {
3290
- paymentDoneVC.billingInfoData = self.billingInfo
3291
- var billingDict: [String: Any] = [:]
3292
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
3293
- paymentDoneVC.billingInfo = billingDict
3294
- // }
3295
-
3296
- // if self.visibility?.additional == true {
3297
- paymentDoneVC.additionalInfoData = self.additionalInfo
3298
- var additionalDict: [String: Any] = [:]
3299
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
3300
- paymentDoneVC.additionalInfo = additionalDict
3301
- // }
3302
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
3303
- }
3304
- }
3305
- }
3306
- } else {
3307
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
3308
- }
3309
- } catch let jsonError {
3310
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
3311
- }
3312
- } else {
3313
- self.presentPaymentErrorVC(errorMessage: "No data received")
3314
- }
3315
- } else {
3316
- if let data = serviceData,
3317
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
3318
- let message = responseObj["message"] as? String {
3319
- self.presentPaymentErrorVC(errorMessage: message)
3320
- } else {
3321
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
3322
- }
3323
- }
3324
- }
3325
- task.resume()
3326
- }
3327
-
3328
- //MARK: - GrailPay Account Charge Api if user saved account
3329
- func grailPayAccountChargeApi(customerId: String?) {
3330
- showLoadingIndicator()
3331
-
3332
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
3333
-
3334
- guard let serviceURL = URL(string: fullURL) else {
3335
- print("Invalid URL")
3336
- hideLoadingIndicator()
3337
- return
3338
- }
3339
-
3340
- var uRLRequest = URLRequest(url: serviceURL)
3341
- uRLRequest.httpMethod = "POST"
3342
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
3343
-
3344
- let token = UserStoreSingleton.shared.clientToken
3345
- print("Setting clientToken header: \(token ?? "None")")
3346
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
3347
-
3348
- let emailPrefix = userEmail?.components(separatedBy: "@").first ?? ""
3349
-
3350
- var params: [String: Any] = [
3351
- "account_id": self.grailPayAccountID ?? "",
3352
- "account_type": self.selectedGrailPayAccountType ?? "",
3353
- "name": self.selectedGrailPayAccountName ?? "",
3354
- "save_account": (isSavedForFuture ?? false) ? 1 : 0,
3355
- "is_default": (isSavedForFuture ?? false) ? 1 : 0,
3356
- "customer_id": customerId ?? "",
3357
- "email": userEmail ?? "",
3358
- "create_customer": "1",
3359
- ]
3360
-
3361
- if let customerId = customerId {
3362
- params["customer"] = customerId
3363
- } else {
3364
- params["username"] = emailPrefix
3365
- }
3366
-
3367
- // // Billing Info
3368
- // if let visibility = visibility, visibility.billing == true,
3369
- // let billing = billingInfo, !billing.isEmpty {
3370
- // var billingDict: [String: Any] = [:]
3371
- // billing.forEach { billingDict[$0.name] = $0.value }
3372
- //
3373
- // params["address"] = billingDict["address"] as? String ?? ""
3374
- // params["country"] = billingDict["country"] as? String ?? ""
3375
- // params["state"] = billingDict["state"] as? String ?? ""
3376
- // params["city"] = billingDict["city"] as? String ?? ""
3377
- // params["zip"] = billingDict["postal_code"] as? String ?? ""
3378
- // }
3379
-
3380
- // Always include Billing Info if available
3381
- if let billing = billingInfo, !billing.isEmpty {
3382
- var billingDict: [String: Any] = [:]
3383
- billing.forEach { billingDict[$0.name] = $0.value }
3384
-
3385
- params["address"] = billingDict["address"] as? String ?? ""
3386
- params["country"] = billingDict["country"] as? String ?? ""
3387
- params["state"] = billingDict["state"] as? String ?? ""
3388
- params["city"] = billingDict["city"] as? String ?? ""
3389
- params["zip"] = billingDict["postal_code"] as? String ?? ""
3390
- }
3391
-
3392
- // // Additional Info or default description
3393
- // var descriptionValue: String = "Hosted payment checkout" // default
3394
- // if let visibility = visibility, visibility.additional == true,
3395
- // let additional = additionalInfo, !additional.isEmpty {
3396
- //
3397
- // var additionalDict: [String: Any] = [:]
3398
- // additional.forEach { additionalDict[$0.name] = $0.value }
3399
- //
3400
- // if let desc = additionalDict["description"] as? String, !desc.isEmpty {
3401
- // descriptionValue = desc
3402
- // }
3403
- //
3404
- // if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
3405
- // params["phone_number"] = phone
3406
- // }
3407
- // }
3408
- // params["description"] = descriptionValue
3409
-
3410
- // Always include Additional Info if available
3411
- var descriptionValue: String = "Hosted payment checkout"
3412
- if let additional = additionalInfo, !additional.isEmpty {
3413
- var additionalDict: [String: Any] = [:]
3414
- additional.forEach { additionalDict[$0.name] = $0.value }
3415
-
3416
- if let desc = additionalDict["description"] as? String, !desc.isEmpty {
3417
- descriptionValue = desc
3418
- }
3419
-
3420
- if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
3421
- params["phone_number"] = phone
3422
- }
3423
- }
3424
- params["description"] = descriptionValue
3425
-
3426
- // Add these if recurring is enabled
3427
- // if let req = request, req.is_recurring == true {
3428
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
3429
- // // Only send start_date if type is .custom and field is not empty
3430
- // if let startDateText = startDate, !startDateText.isEmpty {
3431
- // let inputFormatter = DateFormatter()
3432
- // inputFormatter.dateFormat = "dd/MM/yyyy"
3433
- //
3434
- // let outputFormatter = DateFormatter()
3435
- // outputFormatter.dateFormat = "MM/dd/yyyy"
3436
- //
3437
- // if let date = inputFormatter.date(from: startDateText) {
3438
- // let apiFormattedDate = outputFormatter.string(from: date)
3439
- // params["start_date"] = apiFormattedDate
3440
- // } else {
3441
- // print("Invalid date format in startDateText")
3442
- // }
3443
- // }
3444
- // }
3445
- //
3446
- // params["interval"] = chosenPlan?.lowercased()
3447
- // }
3448
-
3449
- // Add these if recurring is enabled
3450
- if let req = request, req.is_recurring == true {
3451
- if let startDateText = startDate, !startDateText.isEmpty {
3452
- let inputFormatter = DateFormatter()
3453
- inputFormatter.dateFormat = "dd/MM/yyyy"
3454
-
3455
- let outputFormatter = DateFormatter()
3456
- outputFormatter.dateFormat = "MM/dd/yyyy"
3457
-
3458
- if let date = inputFormatter.date(from: startDateText) {
3459
- let apiFormattedDate = outputFormatter.string(from: date)
3460
- params["start_date"] = apiFormattedDate
3461
- } else {
3462
- print("Invalid date format in startDateText")
3463
- }
3464
- }
3465
-
3466
- // interval is still required
3467
- params["interval"] = chosenPlan?.lowercased()
3468
- }
3469
-
3470
- // ✅ Include metadata only if it has at least 1 key-value pair
3471
- if let metadata = request?.metadata, !metadata.isEmpty {
3472
- params["metadata"] = metadata
3473
- }
3474
-
3475
- print(params)
3476
-
3477
- do {
3478
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
3479
- uRLRequest.httpBody = jsonData
3480
- if let jsonString = String(data: jsonData, encoding: .utf8) {
3481
- print("JSON Payload: \(jsonString)")
3482
- }
3483
- } catch let error {
3484
- print("Error creating JSON data: \(error)")
3485
- hideLoadingIndicator()
3486
- return
3487
- }
3488
-
3489
- let session = URLSession.shared
3490
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
3491
-
3492
- DispatchQueue.main.async {
3493
- self.hideLoadingIndicator() // Stop loader when response is received
3494
- }
3495
-
3496
- if let error = error {
3497
- self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
3498
- return
3499
- }
3500
-
3501
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
3502
- self.presentPaymentErrorVC(errorMessage: "Invalid response")
3503
- return
3504
- }
3505
-
3506
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
3507
- if let data = serviceData {
3508
- do {
3509
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
3510
- print("Response Data: \(responseObject)")
3511
-
3512
- // ✅ Handle duplicate transaction case
3513
- if let status = responseObject["status"] as? Bool, status == false,
3514
- let message = responseObject["message"] as? String,
3515
- message.lowercased().contains("duplicate transaction") {
3516
- self.presentPaymentErrorVC(errorMessage: message)
3517
- return
3518
- }
3519
-
3520
- if let status = responseObject["status"] as? Int, status == 0,
3521
- let message = responseObject["message"] as? String,
3522
- message.lowercased().contains("duplicate transaction") {
3523
- self.presentPaymentErrorVC(errorMessage: message)
3524
- return
3525
- }
3526
-
3527
- // ✅ Handle generic "status == 0" error case
3528
- if let status = responseObject["status"] as? Int, status == 0 {
3529
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
3530
- self.presentPaymentErrorVC(errorMessage: errorMessage)
3531
- return
3532
- }
3533
- else {
3534
- DispatchQueue.main.async {
3535
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
3536
- paymentDoneVC.chargeData = responseObject
3537
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
3538
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
3539
- paymentDoneVC.bankPaymentParams = params
3540
- // Pass billing and additional info
3541
- // Conditionally pass raw FieldItem array
3542
- paymentDoneVC.visibility = self.visibility
3543
- paymentDoneVC.request = self.request
3544
-
3545
- // if self.visibility?.billing == true {
3546
- paymentDoneVC.billingInfoData = self.billingInfo
3547
- var billingDict: [String: Any] = [:]
3548
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
3549
- paymentDoneVC.billingInfo = billingDict
3550
- // }
3551
-
3552
- // if self.visibility?.additional == true {
3553
- paymentDoneVC.additionalInfoData = self.additionalInfo
3554
- var additionalDict: [String: Any] = [:]
3555
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
3556
- paymentDoneVC.additionalInfo = additionalDict
3557
- // }
3558
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
3559
- }
3560
- }
3561
- }
3562
- } else {
3563
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
3564
- }
3565
- } catch let jsonError {
3566
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
3567
- }
3568
- } else {
3569
- self.presentPaymentErrorVC(errorMessage: "No data received")
3570
- }
3571
- } else {
3572
- if let data = serviceData,
3573
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
3574
- let message = responseObj["message"] as? String {
3575
- self.presentPaymentErrorVC(errorMessage: message)
3576
- } else {
3577
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
3578
- }
3579
- }
3580
- }
3581
- task.resume()
3582
- }
3583
-
3584
- }
3585
-
3586
- //MARK: - Table View
3587
- @available(iOS 16.0, *)
3588
- extension BillingInfoVC: UITableViewDelegate, UITableViewDataSource {
3589
- func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
3590
- if tableView == tblViewCountryList {
3591
- // return countryList.count
3592
- let count = isSearching ? filteredCountryList.count : countryList.count
3593
- return count
3594
- }
3595
- else if tableView == tblViewStateList {
3596
- // return stateList.count
3597
- return isSearchingState ? filteredStateList.count : stateList.count
3598
- }
3599
- else if tableView == tblViewCityList {
3600
- return cityList.count
3601
- }
3602
- else {
3603
- return 0
3604
- }
3605
- }
3606
-
3607
- func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
3608
- if tableView == tblViewCountryList {
3609
- let currentList = isSearching ? filteredCountryList : countryList
3610
- guard indexPath.row < currentList.count else {
3611
- return UITableViewCell()
3612
- }
3613
-
3614
- let cell = tableView.dequeueReusableCell(withIdentifier: "CountryListTVC") as! CountryListTVC
3615
- let country = currentList[indexPath.row]
3616
- cell.lblCountryName.text = country["name"] as? String
3617
- return cell
3618
-
3619
- } else if tableView == tblViewStateList {
3620
- // let cell = tableView.dequeueReusableCell(withIdentifier: "StateListTVC") as! StateListTVC
3621
- // let state = stateList[indexPath.row]
3622
- // cell.lblStateName.text = state["name"] as? String
3623
- // return cell
3624
-
3625
- let currentList = isSearchingState ? filteredStateList : stateList
3626
- guard indexPath.row < currentList.count else { return UITableViewCell() }
3627
-
3628
- let cell = tableView.dequeueReusableCell(withIdentifier: "StateListTVC") as! StateListTVC
3629
- let state = currentList[indexPath.row]
3630
- cell.lblStateName.text = state["name"] as? String
3631
- return cell
3632
- } else if tableView == tblViewCityList {
3633
- let cell = tableView.dequeueReusableCell(withIdentifier: "CityListTVC") as! CityListTVC
3634
- let city = cityList[indexPath.row]
3635
- cell.lblCityName.text = city["city_name"] as? String
3636
- return cell
3637
- } else {
3638
- return UITableViewCell()
3639
- }
3640
- }
3641
-
3642
- func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
3643
- if tableView == tblViewCountryList {
3644
-
3645
- let currentList = isSearching ? filteredCountryList : countryList
3646
-
3647
- // Safety check to avoid index out of range
3648
- guard indexPath.row < currentList.count else { return }
3649
-
3650
- let selectedCountry = currentList[indexPath.row]["name"] as? String
3651
-
3652
- txtFieldCountry.text = selectedCountry
3653
- viewCountryList.isHidden = true
3654
-
3655
- // Show/hide state dropdown and update placeholder
3656
- if let country = selectedCountry {
3657
- // if country.lowercased() == "united states" || country.lowercased() == "usa" || country.lowercased() == "canada" {
3658
- // btnSelectState.isHidden = false
3659
- // txtFieldState.placeholder = "Select State"
3660
- // } else {
3661
- // btnSelectState.isHidden = true
3662
- // txtFieldState.placeholder = "State"
3663
- // }
3664
- //
3665
- // getStateListApi(for: country)
3666
-
3667
- handleCountrySelection(countryName: country)
3668
- }
3669
-
3670
-
3671
- // Reset search state after selection
3672
- isSearching = false
3673
- filteredCountryList.removeAll()
3674
- searchBarCountryList.text = ""
3675
- searchBarCountryList.resignFirstResponder()
3676
-
3677
- tblViewCountryList.reloadData()
3678
-
3679
- } else if tableView == tblViewStateList {
3680
- // let selectedState = stateList[indexPath.row]["name"] as? String
3681
- // txtFieldState.text = selectedState
3682
- // viewStateList.isHidden = true
3683
- //
3684
- // // Fetch cities for the selected state and country
3685
- // if let state = selectedState, let country = txtFieldCountry.text {
3686
- // getCityListListApi(for: country, state: state)
3687
- // }
3688
-
3689
- let currentList = isSearchingState ? filteredStateList : stateList
3690
- guard indexPath.row < currentList.count else { return }
3691
-
3692
- let selectedState = currentList[indexPath.row]["name"] as? String
3693
- txtFieldState.text = selectedState
3694
- viewStateList.isHidden = true
3695
-
3696
- isSearchingState = false
3697
- filteredStateList.removeAll()
3698
- searchBarStateList.text = ""
3699
- searchBarStateList.resignFirstResponder()
3700
-
3701
- if let state = selectedState, let country = txtFieldCountry.text {
3702
- getCityListListApi(for: country, state: state)
3703
- }
3704
-
3705
- } else if tableView == tblViewCityList {
3706
- let selectedCity = cityList[indexPath.row]["city_name"] as? String
3707
- txtFieldCity.text = selectedCity
3708
- viewCityList.isHidden = true
3709
-
3710
- // Fetch the city list again based on selected state & country
3711
- if let state = txtFieldState.text, let country = txtFieldCountry.text {
3712
- getCityListListApi(for: country, state: state)
3713
- }
3714
- }
3715
-
3716
- updateBillingInfoData()
3717
- }
3718
-
3719
- func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
3720
- return 50
3721
- }
3722
-
3723
- }
3724
-
3725
- extension BillingInfoVC: UISearchBarDelegate {
3726
-
3727
- func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
3728
- if searchBar == searchBarCountryList {
3729
- if searchText.isEmpty {
3730
- isSearching = false
3731
- filteredCountryList.removeAll()
3732
- view.endEditing(true)
3733
- } else {
3734
- isSearching = true
3735
- filteredCountryList = countryList.filter {
3736
- ($0["name"] as? String)?.lowercased().contains(searchText.lowercased()) ?? false
3737
- }
3738
- }
3739
- tblViewCountryList.reloadData()
3740
- } else if searchBar == searchBarStateList {
3741
- if searchText.isEmpty {
3742
- isSearchingState = false
3743
- filteredStateList.removeAll()
3744
- view.endEditing(true)
3745
- } else {
3746
- isSearchingState = true
3747
- filteredStateList = stateList.filter {
3748
- ($0["name"] as? String)?.lowercased().contains(searchText.lowercased()) ?? false
3749
- }
3750
- }
3751
- tblViewStateList.reloadData()
3752
- }
3753
- }
3754
-
3755
- func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
3756
- if searchBar == searchBarCountryList {
3757
- isSearching = true
3758
- } else if searchBar == searchBarStateList {
3759
- isSearchingState = true
3760
- }
3761
- }
3762
-
3763
- func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
3764
- if searchBar == searchBarCountryList {
3765
- isSearching = false
3766
- searchBar.text = ""
3767
- searchBar.resignFirstResponder()
3768
- tblViewCountryList.reloadData()
3769
- } else if searchBar == searchBarStateList {
3770
- isSearchingState = false
3771
- searchBar.text = ""
3772
- searchBar.resignFirstResponder()
3773
- tblViewStateList.reloadData()
3774
- }
3775
- }
3776
-
3777
- }
3778
-
3779
- @available(iOS 16.0, *)
3780
- extension BillingInfoVC: UITextFieldDelegate {
3781
- func textFieldShouldReturn(_ textField: UITextField) -> Bool {
3782
- textField.resignFirstResponder() // Dismiss the keyboard
3783
- return true
3784
- }
3785
-
3786
- // Update billingInfoData when user finishes editing a field
3787
- func textFieldDidEndEditing(_ textField: UITextField) {
3788
- switch textField {
3789
- case txtFieldAddress:
3790
- setFieldValue("address", to: textField.text)
3791
- case txtFieldCountry:
3792
- setFieldValue("country", to: textField.text)
3793
- case txtFieldState:
3794
- setFieldValue("state", to: textField.text)
3795
- case txtFieldCity:
3796
- setFieldValue("city", to: textField.text)
3797
- case txtFieldPostalCode:
3798
- setFieldValue("postal_code", to: textField.text)
3799
- default:
3800
- break
3801
- }
3802
-
3803
- billingInfo = fieldSection?.billing
3804
- }
3805
-
3806
- }
3807
-